Climate 1.0 (#23899)
* Climate 1.0 / part 1/2/3 * fix flake * Lint * Update Google Assistant * ambiclimate to climate 1.0 (#24911) * Fix Alexa * Lint * Migrate zhong_hong * Migrate tuya * Migrate honeywell to new climate schema (#24257) * Update one * Fix model climate v2 * Cleanup p4 * Add comfort hold mode * Fix old code * Update homeassistant/components/climate/__init__.py Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io> * Update homeassistant/components/climate/const.py Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io> * First renaming * Rename operation to hvac for paulus * Rename hold mode to preset mode * Cleanup & update comments * Remove on/off * Fix supported feature count * Update services * Update demo * Fix tests & use current_hvac * Update comment * Fix tests & add typing * Add more typing * Update modes * Fix tests * Cleanup low/high with range * Update homematic part 1 * Finish homematic * Fix lint * fix hm mapping * Support simple devices * convert lcn * migrate oem * Fix xs1 * update hive * update mil * Update toon * migrate deconz * cleanup * update tesla * Fix lint * Fix vera * Migrate zwave * Migrate velbus * Cleanup humity feature * Cleanup * Migrate wink * migrate dyson * Fix current hvac * Renaming * Fix lint * Migrate tfiac * migrate tado * Fix PRESET can be None * apply PR#23913 from dev * remove EU component, etc. * remove EU component, etc. * ready to test now * de-linted * some tweaks * de-lint * better handling of edge cases * delint * fix set_mode typos * apply PR#23913 from dev * remove EU component, etc. * ready to test now * de-linted * some tweaks * de-lint * better handling of edge cases * delint * fix set_mode typos * delint, move debug code * away preset now working * code tidy-up * code tidy-up 2 * code tidy-up 3 * address issues #18932, #15063 * address issues #18932, #15063 - 2/2 * refactor MODE_AUTO to MODE_HEAT_COOL and use F not C * add low/high to set_temp * add low/high to set_temp 2 * add low/high to set_temp - delint * run HA scripts * port changes from PR #24402 * manual rebase * manual rebase 2 * delint * minor change * remove SUPPORT_HVAC_ACTION * Migrate radiotherm * Convert touchline * Migrate flexit * Migrate nuheat * Migrate maxcube * Fix names maxcube const * Migrate proliphix * Migrate heatmiser * Migrate fritzbox * Migrate opentherm_gw * Migrate venstar * Migrate daikin * Migrate modbus * Fix elif * Migrate Homematic IP Cloud to climate-1.0 (#24913) * hmip climate fix * Update hvac_mode and preset_mode * fix lint * Fix lint * Migrate generic_thermostat * Migrate incomfort to new climate schema (#24915) * initial commit * Update climate.py * Migrate eq3btsmart * Lint * cleanup PRESET_MANUAL * Migrate ecobee * No conditional features * KNX: Migrate climate component to new climate platform (#24931) * Migrate climate component * Remove unused code * Corrected line length * Lint * Lint * fix tests * Fix value * Migrate geniushub to new climate schema (#24191) * Update one * Fix model climate v2 * Cleanup p4 * Add comfort hold mode * Fix old code * Update homeassistant/components/climate/__init__.py Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io> * Update homeassistant/components/climate/const.py Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io> * First renaming * Rename operation to hvac for paulus * Rename hold mode to preset mode * Cleanup & update comments * Remove on/off * Fix supported feature count * Update services * Update demo * Fix tests & use current_hvac * Update comment * Fix tests & add typing * Add more typing * Update modes * Fix tests * Cleanup low/high with range * Update homematic part 1 * Finish homematic * Fix lint * fix hm mapping * Support simple devices * convert lcn * migrate oem * Fix xs1 * update hive * update mil * Update toon * migrate deconz * cleanup * update tesla * Fix lint * Fix vera * Migrate zwave * Migrate velbus * Cleanup humity feature * Cleanup * Migrate wink * migrate dyson * Fix current hvac * Renaming * Fix lint * Migrate tfiac * migrate tado * delinted * delinted * use latest client * clean up mappings * clean up mappings * add duration to set_temperature * add duration to set_temperature * manual rebase * tweak * fix regression * small fix * fix rebase mixup * address comments * finish refactor * fix regression * tweak type hints * delint * manual rebase * WIP: Fixes for honeywell migration to climate-1.0 (#24938) * add type hints * code tidy-up * Fixes for incomfort migration to climate-1.0 (#24936) * delint type hints * no async unless await * revert: no async unless await * revert: no async unless await 2 * delint * fix typo * Fix homekit_controller on climate-1.0 (#24948) * Fix tests on climate-1.0 branch * As part of climate-1.0, make state return the heating-cooling.current characteristic * Fixes from review * lint * Fix imports * Migrate stibel_eltron * Fix lint * Migrate coolmaster to climate 1.0 (#24967) * Migrate coolmaster to climate 1.0 * fix lint errors * More lint fixes * Fix demo to work with UI * Migrate spider * Demo update * Updated frontend to 20190705.0 * Fix boost mode (#24980) * Prepare Netatmo for climate 1.0 (#24973) * Migration Netatmo * Address comments * Update climate.py * Migrate ephember * Migrate Sensibo * Implemented review comments (#24942) * Migrate ESPHome * Migrate MQTT * Migrate Nest * Migrate melissa * Initial/partial migration of ST * Migrate ST * Remove Away mode (#24995) * Migrate evohome, cache access tokens (#24491) * add water_heater, add storage - initial commit * add water_heater, add storage - initial commit delint add missing code desiderata update honeywell client library & CODEOWNER add auth_tokens code, refactor & delint refactor for broker delint * Add Broker, Water Heater & Refactor add missing code desiderata * update honeywell client library & CODEOWNER add auth_tokens code, refactor & delint refactor for broker * bugfix - loc_idx may not be 0 more refactor - ensure pure async more refactoring appears all r/o attributes are working tweak precsion, DHW & delint remove unused code remove unused code 2 remove unused code, refactor _save_auth_tokens() * support RoundThermostat bugfix opmode, switch to util.dt, add until=1h revert breaking change * store at_expires as naive UTC remove debug code delint tidy up exception handling delint add water_heater, add storage - initial commit delint add missing code desiderata update honeywell client library & CODEOWNER add auth_tokens code, refactor & delint refactor for broker add water_heater, add storage - initial commit delint add missing code desiderata update honeywell client library & CODEOWNER add auth_tokens code, refactor & delint refactor for broker delint bugfix - loc_idx may not be 0 more refactor - ensure pure async more refactoring appears all r/o attributes are working tweak precsion, DHW & delint remove unused code remove unused code 2 remove unused code, refactor _save_auth_tokens() support RoundThermostat bugfix opmode, switch to util.dt, add until=1h revert breaking change store at_expires as naive UTC remove debug code delint tidy up exception handling delint * update CODEOWNERS * fix regression * fix requirements * migrate to climate-1.0 * tweaking * de-lint * TCS working? & delint * tweaking * TCS code finalised * remove available() logic * refactor _switchpoints() * tidy up switchpoint code * tweak * teaking device_state_attributes * some refactoring * move PRESET_CUSTOM back to evohome * move CONF_ACCESS_TOKEN_EXPIRES CONF_REFRESH_TOKEN back to evohome * refactor SP code and dt conversion * delinted * delinted * remove water_heater * fix regression * Migrate homekit * Cleanup away mode * Fix tests * add helpers * fix tests melissa * Fix nehueat * fix zwave * add more tests * fix deconz * Fix climate test emulate_hue * fix tests * fix dyson tests * fix demo with new layout * fix honeywell * Switch homekit_controller to use HVAC_MODE_HEAT_COOL instead of HVAC_MODE_AUTO (#25009) * Lint * PyLint * Pylint * fix fritzbox tests * Fix google * Fix all tests * Fix lint * Fix auto for homekit like controler * Fix lint * fix lint
This commit is contained in:
parent
c2f1c4b981
commit
84cf76ba36
119 changed files with 5240 additions and 5525 deletions
|
@ -18,6 +18,7 @@
|
|||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"editor.rulers": [80]
|
||||
"editor.rulers": [80],
|
||||
"terminal.integrated.shell.linux": "/bin/bash"
|
||||
}
|
||||
}
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -94,7 +94,10 @@ virtualization/vagrant/.vagrant
|
|||
virtualization/vagrant/config
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode
|
||||
.vscode/*
|
||||
!.vscode/cSpell.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/tasks.json
|
||||
|
||||
# Built docs
|
||||
docs/build
|
||||
|
|
92
.vscode/tasks.json
vendored
Normal file
92
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Preview",
|
||||
"type": "shell",
|
||||
"command": "hass -c ./config",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true,
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Pytest",
|
||||
"type": "shell",
|
||||
"command": "pytest --timeout=10 tests",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true,
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Flake8",
|
||||
"type": "shell",
|
||||
"command": "flake8 homeassistant tests",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true,
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Pylint",
|
||||
"type": "shell",
|
||||
"command": "pylint homeassistant",
|
||||
"dependsOn": [
|
||||
"Install all Requirements"
|
||||
],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true,
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Generate Requirements",
|
||||
"type": "shell",
|
||||
"command": "./script/gen_requirements_all.py",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Install all Requirements",
|
||||
"type": "shell",
|
||||
"command": "pip3 install -r requirements_all.txt -c homeassistant/package_constraints.txt",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
|
@ -23,6 +23,7 @@ import homeassistant.util.color as color_util
|
|||
from .const import (
|
||||
API_TEMP_UNITS,
|
||||
API_THERMOSTAT_MODES,
|
||||
API_THERMOSTAT_PRESETS,
|
||||
DATE_FORMAT,
|
||||
PERCENTAGE_FAN_MAP,
|
||||
)
|
||||
|
@ -180,9 +181,13 @@ class AlexaPowerController(AlexaCapibility):
|
|||
if name != 'powerState':
|
||||
raise UnsupportedProperty(name)
|
||||
|
||||
if self.entity.state == STATE_OFF:
|
||||
return 'OFF'
|
||||
return 'ON'
|
||||
if self.entity.domain == climate.DOMAIN:
|
||||
is_on = self.entity.state != climate.HVAC_MODE_OFF
|
||||
|
||||
else:
|
||||
is_on = self.entity.state != STATE_OFF
|
||||
|
||||
return 'ON' if is_on else 'OFF'
|
||||
|
||||
|
||||
class AlexaLockController(AlexaCapibility):
|
||||
|
@ -546,16 +551,13 @@ class AlexaThermostatController(AlexaCapibility):
|
|||
|
||||
def properties_supported(self):
|
||||
"""Return what properties this entity supports."""
|
||||
properties = []
|
||||
properties = [{'name': 'thermostatMode'}]
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & climate.SUPPORT_TARGET_TEMPERATURE:
|
||||
properties.append({'name': 'targetSetpoint'})
|
||||
if supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW:
|
||||
if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE:
|
||||
properties.append({'name': 'lowerSetpoint'})
|
||||
if supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH:
|
||||
properties.append({'name': 'upperSetpoint'})
|
||||
if supported & climate.SUPPORT_OPERATION_MODE:
|
||||
properties.append({'name': 'thermostatMode'})
|
||||
return properties
|
||||
|
||||
def properties_proactively_reported(self):
|
||||
|
@ -569,13 +571,18 @@ class AlexaThermostatController(AlexaCapibility):
|
|||
def get_property(self, name):
|
||||
"""Read and return a property."""
|
||||
if name == 'thermostatMode':
|
||||
ha_mode = self.entity.attributes.get(climate.ATTR_OPERATION_MODE)
|
||||
mode = API_THERMOSTAT_MODES.get(ha_mode)
|
||||
if mode is None:
|
||||
_LOGGER.error("%s (%s) has unsupported %s value '%s'",
|
||||
self.entity.entity_id, type(self.entity),
|
||||
climate.ATTR_OPERATION_MODE, ha_mode)
|
||||
raise UnsupportedProperty(name)
|
||||
preset = self.entity.attributes.get(climate.ATTR_PRESET_MODE)
|
||||
|
||||
if preset in API_THERMOSTAT_PRESETS:
|
||||
mode = API_THERMOSTAT_PRESETS[preset]
|
||||
else:
|
||||
mode = API_THERMOSTAT_MODES.get(self.entity.state)
|
||||
if mode is None:
|
||||
_LOGGER.error(
|
||||
"%s (%s) has unsupported state value '%s'",
|
||||
self.entity.entity_id, type(self.entity),
|
||||
self.entity.state)
|
||||
raise UnsupportedProperty(name)
|
||||
return mode
|
||||
|
||||
unit = self.hass.config.units.temperature_unit
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
from homeassistant.const import (
|
||||
STATE_OFF,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
|
@ -57,16 +56,17 @@ API_TEMP_UNITS = {
|
|||
# reverse mapping of this dict and we want to map the first occurrance of OFF
|
||||
# back to HA state.
|
||||
API_THERMOSTAT_MODES = OrderedDict([
|
||||
(climate.STATE_HEAT, 'HEAT'),
|
||||
(climate.STATE_COOL, 'COOL'),
|
||||
(climate.STATE_AUTO, 'AUTO'),
|
||||
(climate.STATE_ECO, 'ECO'),
|
||||
(climate.STATE_MANUAL, 'AUTO'),
|
||||
(STATE_OFF, 'OFF'),
|
||||
(climate.STATE_IDLE, 'OFF'),
|
||||
(climate.STATE_FAN_ONLY, 'OFF'),
|
||||
(climate.STATE_DRY, 'OFF'),
|
||||
(climate.HVAC_MODE_HEAT, 'HEAT'),
|
||||
(climate.HVAC_MODE_COOL, 'COOL'),
|
||||
(climate.HVAC_MODE_HEAT_COOL, 'AUTO'),
|
||||
(climate.HVAC_MODE_AUTO, 'AUTO'),
|
||||
(climate.HVAC_MODE_OFF, 'OFF'),
|
||||
(climate.HVAC_MODE_FAN_ONLY, 'OFF'),
|
||||
(climate.HVAC_MODE_DRY, 'OFF'),
|
||||
])
|
||||
API_THERMOSTAT_PRESETS = {
|
||||
climate.PRESET_ECO: 'ECO'
|
||||
}
|
||||
|
||||
PERCENTAGE_FAN_MAP = {
|
||||
fan.SPEED_LOW: 33,
|
||||
|
|
|
@ -248,9 +248,11 @@ class ClimateCapabilities(AlexaEntity):
|
|||
|
||||
def interfaces(self):
|
||||
"""Yield the supported interfaces."""
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & climate.SUPPORT_ON_OFF:
|
||||
# If we support two modes, one being off, we allow turning on too.
|
||||
if len([v for v in self.entity.attributes[climate.ATTR_HVAC_MODES]
|
||||
if v != climate.HVAC_MODE_OFF]) == 1:
|
||||
yield AlexaPowerController(self.entity)
|
||||
|
||||
yield AlexaThermostatController(self.hass, self.entity)
|
||||
yield AlexaTemperatureSensor(self.hass, self.entity)
|
||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
|
|
@ -33,6 +33,7 @@ from homeassistant.util.temperature import convert as convert_temperature
|
|||
from .const import (
|
||||
API_TEMP_UNITS,
|
||||
API_THERMOSTAT_MODES,
|
||||
API_THERMOSTAT_PRESETS,
|
||||
Cause,
|
||||
)
|
||||
from .entities import async_get_entities
|
||||
|
@ -686,23 +687,45 @@ async def async_api_set_thermostat_mode(hass, config, directive, context):
|
|||
mode = directive.payload['thermostatMode']
|
||||
mode = mode if isinstance(mode, str) else mode['value']
|
||||
|
||||
operation_list = entity.attributes.get(climate.ATTR_OPERATION_LIST)
|
||||
ha_mode = next(
|
||||
(k for k, v in API_THERMOSTAT_MODES.items() if v == mode),
|
||||
None
|
||||
)
|
||||
if ha_mode not in operation_list:
|
||||
msg = 'The requested thermostat mode {} is not supported'.format(mode)
|
||||
raise AlexaUnsupportedThermostatModeError(msg)
|
||||
|
||||
data = {
|
||||
ATTR_ENTITY_ID: entity.entity_id,
|
||||
climate.ATTR_OPERATION_MODE: ha_mode,
|
||||
}
|
||||
|
||||
ha_preset = next(
|
||||
(k for k, v in API_THERMOSTAT_PRESETS.items() if v == mode),
|
||||
None
|
||||
)
|
||||
|
||||
if ha_preset:
|
||||
presets = entity.attributes.get(climate.ATTR_PRESET_MODES, [])
|
||||
|
||||
if ha_preset not in presets:
|
||||
msg = 'The requested thermostat mode {} is not supported'.format(
|
||||
ha_preset
|
||||
)
|
||||
raise AlexaUnsupportedThermostatModeError(msg)
|
||||
|
||||
service = climate.SERVICE_SET_PRESET_MODE
|
||||
data[climate.ATTR_PRESET_MODE] = climate.PRESET_ECO
|
||||
|
||||
else:
|
||||
operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES)
|
||||
ha_mode = next(
|
||||
(k for k, v in API_THERMOSTAT_MODES.items() if v == mode),
|
||||
None
|
||||
)
|
||||
if ha_mode not in operation_list:
|
||||
msg = 'The requested thermostat mode {} is not supported'.format(
|
||||
mode
|
||||
)
|
||||
raise AlexaUnsupportedThermostatModeError(msg)
|
||||
|
||||
service = climate.SERVICE_SET_HVAC_MODE
|
||||
data[climate.ATTR_HVAC_MODE] = ha_mode
|
||||
|
||||
response = directive.response()
|
||||
await hass.services.async_call(
|
||||
entity.domain, climate.SERVICE_SET_OPERATION_MODE, data,
|
||||
climate.DOMAIN, service, data,
|
||||
blocking=False, context=context)
|
||||
response.add_context_property({
|
||||
'name': 'thermostatMode',
|
||||
|
|
|
@ -7,11 +7,8 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_ON_OFF, STATE_HEAT)
|
||||
from homeassistant.const import ATTR_NAME
|
||||
from homeassistant.const import (ATTR_TEMPERATURE,
|
||||
STATE_OFF, TEMP_CELSIUS)
|
||||
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, HVAC_MODE_HEAT)
|
||||
from homeassistant.const import ATTR_NAME, ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from .const import (ATTR_VALUE, CONF_CLIENT_ID, CONF_CLIENT_SECRET,
|
||||
|
@ -20,8 +17,7 @@ from .const import (ATTR_VALUE, CONF_CLIENT_ID, CONF_CLIENT_SECRET,
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
||||
SUPPORT_ON_OFF)
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
SEND_COMFORT_FEEDBACK_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_NAME): cv.string,
|
||||
|
@ -177,11 +173,6 @@ class AmbiclimateEntity(ClimateDevice):
|
|||
"""Return the current humidity."""
|
||||
return self._data.get('humidity')
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if heater is on."""
|
||||
return self._data.get('power', '').lower() == 'on'
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature."""
|
||||
|
@ -198,9 +189,12 @@ class AmbiclimateEntity(ClimateDevice):
|
|||
return SUPPORT_FLAGS
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation."""
|
||||
return STATE_HEAT if self.is_on else STATE_OFF
|
||||
if self._data.get('power', '').lower() == 'on':
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
@ -209,13 +203,13 @@ class AmbiclimateEntity(ClimateDevice):
|
|||
return
|
||||
await self._heater.set_target_temperature(temperature)
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn device on."""
|
||||
await self._heater.turn_on()
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn device off."""
|
||||
await self._heater.turn_off()
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
if hvac_mode == HVAC_MODE_HEAT:
|
||||
await self._heater.turn_on()
|
||||
return
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
await self._heater.turn_off()
|
||||
|
||||
async def async_update(self):
|
||||
"""Retrieve latest state."""
|
||||
|
|
|
@ -1,68 +1,41 @@
|
|||
"""Provides functionality to interact with climate devices."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import functools as ft
|
||||
import logging
|
||||
from typing import Any, Awaitable, Dict, List, Optional
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.helpers.temperature import display_temp as show_temp
|
||||
from homeassistant.util.temperature import convert as convert_temperature
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, PRECISION_TENTHS, PRECISION_WHOLE,
|
||||
STATE_OFF, STATE_ON, TEMP_CELSIUS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.config_validation import ( # noqa
|
||||
PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
||||
STATE_ON, STATE_OFF, TEMP_CELSIUS, PRECISION_WHOLE,
|
||||
PRECISION_TENTHS)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.temperature import display_temp as show_temp
|
||||
from homeassistant.helpers.typing import (
|
||||
ConfigType, HomeAssistantType, ServiceDataType)
|
||||
from homeassistant.util.temperature import convert as convert_temperature
|
||||
|
||||
from .const import (
|
||||
ATTR_AUX_HEAT,
|
||||
ATTR_AWAY_MODE,
|
||||
ATTR_CURRENT_HUMIDITY,
|
||||
ATTR_CURRENT_TEMPERATURE,
|
||||
ATTR_FAN_LIST,
|
||||
ATTR_FAN_MODE,
|
||||
ATTR_HOLD_MODE,
|
||||
ATTR_HUMIDITY,
|
||||
ATTR_MAX_HUMIDITY,
|
||||
ATTR_MAX_TEMP,
|
||||
ATTR_MIN_HUMIDITY,
|
||||
ATTR_MIN_TEMP,
|
||||
ATTR_OPERATION_LIST,
|
||||
ATTR_OPERATION_MODE,
|
||||
ATTR_SWING_LIST,
|
||||
ATTR_SWING_MODE,
|
||||
ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW,
|
||||
ATTR_TARGET_TEMP_STEP,
|
||||
DOMAIN,
|
||||
SERVICE_SET_AUX_HEAT,
|
||||
SERVICE_SET_AWAY_MODE,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
SERVICE_SET_HOLD_MODE,
|
||||
SERVICE_SET_HUMIDITY,
|
||||
SERVICE_SET_OPERATION_MODE,
|
||||
SERVICE_SET_SWING_MODE,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH,
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW,
|
||||
SUPPORT_TARGET_HUMIDITY,
|
||||
SUPPORT_TARGET_HUMIDITY_HIGH,
|
||||
SUPPORT_TARGET_HUMIDITY_LOW,
|
||||
SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_HOLD_MODE,
|
||||
SUPPORT_SWING_MODE,
|
||||
SUPPORT_AWAY_MODE,
|
||||
SUPPORT_AUX_HEAT,
|
||||
)
|
||||
ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE,
|
||||
ATTR_FAN_MODE, ATTR_FAN_MODES, ATTR_HUMIDITY, ATTR_HVAC_ACTIONS,
|
||||
ATTR_HVAC_MODE, ATTR_HVAC_MODES, ATTR_MAX_HUMIDITY, ATTR_MAX_TEMP,
|
||||
ATTR_MIN_HUMIDITY, ATTR_MIN_TEMP, ATTR_PRESET_MODE, ATTR_PRESET_MODES,
|
||||
ATTR_SWING_MODE, ATTR_SWING_MODES, ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_STEP, DOMAIN, HVAC_MODES,
|
||||
SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY,
|
||||
SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_SWING_MODE,
|
||||
SERVICE_SET_TEMPERATURE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE,
|
||||
SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_HUMIDITY,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||
from .reproduce_state import async_reproduce_states # noqa
|
||||
|
||||
DEFAULT_MIN_TEMP = 7
|
||||
DEFAULT_MAX_TEMP = 35
|
||||
DEFAULT_MIN_HUMITIDY = 30
|
||||
DEFAULT_MIN_HUMIDITY = 30
|
||||
DEFAULT_MAX_HUMIDITY = 99
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
@ -76,14 +49,6 @@ CONVERTIBLE_ATTRIBUTE = [
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ON_OFF_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
||||
})
|
||||
|
||||
SET_AWAY_MODE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
||||
vol.Required(ATTR_AWAY_MODE): cv.boolean,
|
||||
})
|
||||
SET_AUX_HEAT_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
||||
vol.Required(ATTR_AUX_HEAT): cv.boolean,
|
||||
|
@ -96,20 +61,20 @@ SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All(
|
|||
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
|
||||
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
||||
vol.Optional(ATTR_OPERATION_MODE): cv.string,
|
||||
vol.Optional(ATTR_HVAC_MODE): vol.In(HVAC_MODES),
|
||||
}
|
||||
))
|
||||
SET_FAN_MODE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
||||
vol.Required(ATTR_FAN_MODE): cv.string,
|
||||
})
|
||||
SET_HOLD_MODE_SCHEMA = vol.Schema({
|
||||
SET_PRESET_MODE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
||||
vol.Required(ATTR_HOLD_MODE): cv.string,
|
||||
vol.Required(ATTR_PRESET_MODE): vol.Maybe(cv.string),
|
||||
})
|
||||
SET_OPERATION_MODE_SCHEMA = vol.Schema({
|
||||
SET_HVAC_MODE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
||||
vol.Required(ATTR_OPERATION_MODE): cv.string,
|
||||
vol.Required(ATTR_HVAC_MODE): vol.In(HVAC_MODES),
|
||||
})
|
||||
SET_HUMIDITY_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
||||
|
@ -121,19 +86,19 @@ SET_SWING_MODE_SCHEMA = vol.Schema({
|
|||
})
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
|
||||
"""Set up climate devices."""
|
||||
component = hass.data[DOMAIN] = \
|
||||
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
||||
await component.async_setup(config)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SET_AWAY_MODE, SET_AWAY_MODE_SCHEMA,
|
||||
async_service_away_mode
|
||||
SERVICE_SET_HVAC_MODE, SET_HVAC_MODE_SCHEMA,
|
||||
'async_set_hvac_mode'
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SET_HOLD_MODE, SET_HOLD_MODE_SCHEMA,
|
||||
'async_set_hold_mode'
|
||||
SERVICE_SET_PRESET_MODE, SET_PRESET_MODE_SCHEMA,
|
||||
'async_set_preset_mode'
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SET_AUX_HEAT, SET_AUX_HEAT_SCHEMA,
|
||||
|
@ -151,32 +116,20 @@ async def async_setup(hass, config):
|
|||
SERVICE_SET_FAN_MODE, SET_FAN_MODE_SCHEMA,
|
||||
'async_set_fan_mode'
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SET_OPERATION_MODE, SET_OPERATION_MODE_SCHEMA,
|
||||
'async_set_operation_mode'
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SET_SWING_MODE, SET_SWING_MODE_SCHEMA,
|
||||
'async_set_swing_mode'
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_TURN_OFF, ON_OFF_SERVICE_SCHEMA,
|
||||
'async_turn_off'
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_TURN_ON, ON_OFF_SERVICE_SCHEMA,
|
||||
'async_turn_on'
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, entry):
|
||||
async def async_setup_entry(hass: HomeAssistantType, entry):
|
||||
"""Set up a config entry."""
|
||||
return await hass.data[DOMAIN].async_setup_entry(entry)
|
||||
|
||||
|
||||
async def async_unload_entry(hass, entry):
|
||||
async def async_unload_entry(hass: HomeAssistantType, entry):
|
||||
"""Unload a config entry."""
|
||||
return await hass.data[DOMAIN].async_unload_entry(entry)
|
||||
|
||||
|
@ -185,27 +138,23 @@ class ClimateDevice(Entity):
|
|||
"""Representation of a climate device."""
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def state(self) -> str:
|
||||
"""Return the current state."""
|
||||
if self.is_on is False:
|
||||
return STATE_OFF
|
||||
if self.current_operation:
|
||||
return self.current_operation
|
||||
if self.is_on:
|
||||
return STATE_ON
|
||||
return None
|
||||
return self.hvac_mode
|
||||
|
||||
@property
|
||||
def precision(self):
|
||||
def precision(self) -> float:
|
||||
"""Return the precision of the system."""
|
||||
if self.hass.config.units.temperature_unit == TEMP_CELSIUS:
|
||||
return PRECISION_TENTHS
|
||||
return PRECISION_WHOLE
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
def state_attributes(self) -> Dict[str, Any]:
|
||||
"""Return the optional state attributes."""
|
||||
supported_features = self.supported_features
|
||||
data = {
|
||||
ATTR_HVAC_MODES: self.hvac_modes,
|
||||
ATTR_CURRENT_TEMPERATURE: show_temp(
|
||||
self.hass, self.current_temperature, self.temperature_unit,
|
||||
self.precision),
|
||||
|
@ -220,16 +169,13 @@ class ClimateDevice(Entity):
|
|||
self.precision),
|
||||
}
|
||||
|
||||
supported_features = self.supported_features
|
||||
if self.target_temperature_step is not None:
|
||||
if self.target_temperature_step:
|
||||
data[ATTR_TARGET_TEMP_STEP] = self.target_temperature_step
|
||||
|
||||
if supported_features & SUPPORT_TARGET_TEMPERATURE_HIGH:
|
||||
if supported_features & SUPPORT_TARGET_TEMPERATURE_RANGE:
|
||||
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
|
||||
self.hass, self.target_temperature_high, self.temperature_unit,
|
||||
self.precision)
|
||||
|
||||
if supported_features & SUPPORT_TARGET_TEMPERATURE_LOW:
|
||||
data[ATTR_TARGET_TEMP_LOW] = show_temp(
|
||||
self.hass, self.target_temperature_low, self.temperature_unit,
|
||||
self.precision)
|
||||
|
@ -239,136 +185,160 @@ class ClimateDevice(Entity):
|
|||
|
||||
if supported_features & SUPPORT_TARGET_HUMIDITY:
|
||||
data[ATTR_HUMIDITY] = self.target_humidity
|
||||
|
||||
if supported_features & SUPPORT_TARGET_HUMIDITY_LOW:
|
||||
data[ATTR_MIN_HUMIDITY] = self.min_humidity
|
||||
|
||||
if supported_features & SUPPORT_TARGET_HUMIDITY_HIGH:
|
||||
data[ATTR_MAX_HUMIDITY] = self.max_humidity
|
||||
data[ATTR_MIN_HUMIDITY] = self.min_humidity
|
||||
data[ATTR_MAX_HUMIDITY] = self.max_humidity
|
||||
|
||||
if supported_features & SUPPORT_FAN_MODE:
|
||||
data[ATTR_FAN_MODE] = self.current_fan_mode
|
||||
if self.fan_list:
|
||||
data[ATTR_FAN_LIST] = self.fan_list
|
||||
data[ATTR_FAN_MODE] = self.fan_mode
|
||||
data[ATTR_FAN_MODES] = self.fan_modes
|
||||
|
||||
if supported_features & SUPPORT_OPERATION_MODE:
|
||||
data[ATTR_OPERATION_MODE] = self.current_operation
|
||||
if self.operation_list:
|
||||
data[ATTR_OPERATION_LIST] = self.operation_list
|
||||
if self.hvac_action:
|
||||
data[ATTR_HVAC_ACTIONS] = self.hvac_action
|
||||
|
||||
if supported_features & SUPPORT_HOLD_MODE:
|
||||
data[ATTR_HOLD_MODE] = self.current_hold_mode
|
||||
if supported_features & SUPPORT_PRESET_MODE:
|
||||
data[ATTR_PRESET_MODE] = self.preset_mode
|
||||
data[ATTR_PRESET_MODES] = self.preset_modes
|
||||
|
||||
if supported_features & SUPPORT_SWING_MODE:
|
||||
data[ATTR_SWING_MODE] = self.current_swing_mode
|
||||
if self.swing_list:
|
||||
data[ATTR_SWING_LIST] = self.swing_list
|
||||
|
||||
if supported_features & SUPPORT_AWAY_MODE:
|
||||
is_away = self.is_away_mode_on
|
||||
data[ATTR_AWAY_MODE] = STATE_ON if is_away else STATE_OFF
|
||||
data[ATTR_SWING_MODE] = self.swing_mode
|
||||
data[ATTR_SWING_MODES] = self.swing_modes
|
||||
|
||||
if supported_features & SUPPORT_AUX_HEAT:
|
||||
is_aux_heat = self.is_aux_heat_on
|
||||
data[ATTR_AUX_HEAT] = STATE_ON if is_aux_heat else STATE_OFF
|
||||
data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF
|
||||
|
||||
return data
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the unit of measurement used by the platform."""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def current_humidity(self):
|
||||
def current_humidity(self) -> Optional[int]:
|
||||
"""Return the current humidity."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_humidity(self):
|
||||
def target_humidity(self) -> Optional[int]:
|
||||
"""Return the humidity we try to reach."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> List[str]:
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> Optional[str]:
|
||||
"""Return the current running hvac operation if supported.
|
||||
|
||||
Need to be one of CURRENT_HVAC_*.
|
||||
"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
def current_temperature(self) -> Optional[float]:
|
||||
"""Return the current temperature."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
def target_temperature(self) -> Optional[float]:
|
||||
"""Return the temperature we try to reach."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature_step(self):
|
||||
def target_temperature_step(self) -> Optional[float]:
|
||||
"""Return the supported step of target temperature."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature_high(self):
|
||||
"""Return the highbound target temperature we try to reach."""
|
||||
return None
|
||||
def target_temperature_high(self) -> Optional[float]:
|
||||
"""Return the highbound target temperature we try to reach.
|
||||
|
||||
Requires SUPPORT_TARGET_TEMPERATURE_RANGE.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def target_temperature_low(self):
|
||||
"""Return the lowbound target temperature we try to reach."""
|
||||
return None
|
||||
def target_temperature_low(self) -> Optional[float]:
|
||||
"""Return the lowbound target temperature we try to reach.
|
||||
|
||||
Requires SUPPORT_TARGET_TEMPERATURE_RANGE.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return None
|
||||
def preset_mode(self) -> Optional[str]:
|
||||
"""Return the current preset mode, e.g., home, away, temp.
|
||||
|
||||
Requires SUPPORT_PRESET_MODE.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def current_hold_mode(self):
|
||||
"""Return the current hold mode, e.g., home, away, temp."""
|
||||
return None
|
||||
def preset_modes(self) -> Optional[List[str]]:
|
||||
"""Return a list of available preset modes.
|
||||
|
||||
Requires SUPPORT_PRESET_MODE.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if on."""
|
||||
return None
|
||||
def is_aux_heat(self) -> Optional[str]:
|
||||
"""Return true if aux heater.
|
||||
|
||||
Requires SUPPORT_AUX_HEAT.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
"""Return true if aux heater."""
|
||||
return None
|
||||
def fan_mode(self) -> Optional[str]:
|
||||
"""Return the fan setting.
|
||||
|
||||
Requires SUPPORT_FAN_MODE.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return None
|
||||
def fan_modes(self) -> Optional[List[str]]:
|
||||
"""Return the list of available fan modes.
|
||||
|
||||
Requires SUPPORT_FAN_MODE.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return None
|
||||
def swing_mode(self) -> Optional[str]:
|
||||
"""Return the swing setting.
|
||||
|
||||
Requires SUPPORT_SWING_MODE.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def current_swing_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return None
|
||||
def swing_modes(self) -> Optional[List[str]]:
|
||||
"""Return the list of available swing modes.
|
||||
|
||||
@property
|
||||
def swing_list(self):
|
||||
"""Return the list of available swing modes."""
|
||||
return None
|
||||
Requires SUPPORT_SWING_MODE.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
def set_temperature(self, **kwargs) -> None:
|
||||
"""Set new target temperature."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_set_temperature(self, **kwargs):
|
||||
def async_set_temperature(self, **kwargs) -> Awaitable[None]:
|
||||
"""Set new target temperature.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
|
@ -376,164 +346,114 @@ class ClimateDevice(Entity):
|
|||
return self.hass.async_add_job(
|
||||
ft.partial(self.set_temperature, **kwargs))
|
||||
|
||||
def set_humidity(self, humidity):
|
||||
def set_humidity(self, humidity: int) -> None:
|
||||
"""Set new target humidity."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_set_humidity(self, humidity):
|
||||
def async_set_humidity(self, humidity: int) -> Awaitable[None]:
|
||||
"""Set new target humidity.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.set_humidity, humidity)
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
def set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set new target fan mode."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_set_fan_mode(self, fan_mode):
|
||||
def async_set_fan_mode(self, fan_mode: str) -> Awaitable[None]:
|
||||
"""Set new target fan mode.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.set_fan_mode, fan_mode)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new target operation mode."""
|
||||
def set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set new target hvac mode."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_set_operation_mode(self, operation_mode):
|
||||
"""Set new target operation mode.
|
||||
def async_set_hvac_mode(self, hvac_mode: str) -> Awaitable[None]:
|
||||
"""Set new target hvac mode.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.set_operation_mode, operation_mode)
|
||||
return self.hass.async_add_job(self.set_hvac_mode, hvac_mode)
|
||||
|
||||
def set_swing_mode(self, swing_mode):
|
||||
def set_swing_mode(self, swing_mode: str) -> None:
|
||||
"""Set new target swing operation."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_set_swing_mode(self, swing_mode):
|
||||
def async_set_swing_mode(self, swing_mode: str) -> Awaitable[None]:
|
||||
"""Set new target swing operation.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.set_swing_mode, swing_mode)
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away mode on."""
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_turn_away_mode_on(self):
|
||||
"""Turn away mode on.
|
||||
def async_set_preset_mode(self, preset_mode: str) -> Awaitable[None]:
|
||||
"""Set new preset mode.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.turn_away_mode_on)
|
||||
return self.hass.async_add_job(self.set_preset_mode, preset_mode)
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away mode off."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_turn_away_mode_off(self):
|
||||
"""Turn away mode off.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.turn_away_mode_off)
|
||||
|
||||
def set_hold_mode(self, hold_mode):
|
||||
"""Set new target hold mode."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_set_hold_mode(self, hold_mode):
|
||||
"""Set new target hold mode.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.set_hold_mode, hold_mode)
|
||||
|
||||
def turn_aux_heat_on(self):
|
||||
def turn_aux_heat_on(self) -> None:
|
||||
"""Turn auxiliary heater on."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_turn_aux_heat_on(self):
|
||||
def async_turn_aux_heat_on(self) -> Awaitable[None]:
|
||||
"""Turn auxiliary heater on.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.turn_aux_heat_on)
|
||||
|
||||
def turn_aux_heat_off(self):
|
||||
def turn_aux_heat_off(self) -> None:
|
||||
"""Turn auxiliary heater off."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_turn_aux_heat_off(self):
|
||||
def async_turn_aux_heat_off(self) -> Awaitable[None]:
|
||||
"""Turn auxiliary heater off.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.turn_aux_heat_off)
|
||||
|
||||
def turn_on(self):
|
||||
"""Turn device on."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_turn_on(self):
|
||||
"""Turn device on.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.turn_on)
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn device off."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_turn_off(self):
|
||||
"""Turn device off.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.turn_off)
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
def min_temp(self) -> float:
|
||||
"""Return the minimum temperature."""
|
||||
return convert_temperature(DEFAULT_MIN_TEMP, TEMP_CELSIUS,
|
||||
self.temperature_unit)
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
def max_temp(self) -> float:
|
||||
"""Return the maximum temperature."""
|
||||
return convert_temperature(DEFAULT_MAX_TEMP, TEMP_CELSIUS,
|
||||
self.temperature_unit)
|
||||
|
||||
@property
|
||||
def min_humidity(self):
|
||||
def min_humidity(self) -> int:
|
||||
"""Return the minimum humidity."""
|
||||
return DEFAULT_MIN_HUMITIDY
|
||||
return DEFAULT_MIN_HUMIDITY
|
||||
|
||||
@property
|
||||
def max_humidity(self):
|
||||
def max_humidity(self) -> int:
|
||||
"""Return the maximum humidity."""
|
||||
return DEFAULT_MAX_HUMIDITY
|
||||
|
||||
|
||||
async def async_service_away_mode(entity, service):
|
||||
"""Handle away mode service."""
|
||||
if service.data[ATTR_AWAY_MODE]:
|
||||
await entity.async_turn_away_mode_on()
|
||||
else:
|
||||
await entity.async_turn_away_mode_off()
|
||||
|
||||
|
||||
async def async_service_aux_heat(entity, service):
|
||||
async def async_service_aux_heat(
|
||||
entity: ClimateDevice, service: ServiceDataType
|
||||
) -> None:
|
||||
"""Handle aux heat service."""
|
||||
if service.data[ATTR_AUX_HEAT]:
|
||||
await entity.async_turn_aux_heat_on()
|
||||
|
@ -541,7 +461,9 @@ async def async_service_aux_heat(entity, service):
|
|||
await entity.async_turn_aux_heat_off()
|
||||
|
||||
|
||||
async def async_service_temperature_set(entity, service):
|
||||
async def async_service_temperature_set(
|
||||
entity: ClimateDevice, service: ServiceDataType
|
||||
) -> None:
|
||||
"""Handle set temperature service."""
|
||||
hass = entity.hass
|
||||
kwargs = {}
|
||||
|
|
|
@ -1,20 +1,103 @@
|
|||
"""Provides the constants needed for component."""
|
||||
|
||||
# All activity disabled / Device is off/standby
|
||||
HVAC_MODE_OFF = 'off'
|
||||
|
||||
# Heating
|
||||
HVAC_MODE_HEAT = 'heat'
|
||||
|
||||
# Cooling
|
||||
HVAC_MODE_COOL = 'cool'
|
||||
|
||||
# The device supports heating/cooling to a range
|
||||
HVAC_MODE_HEAT_COOL = 'heat_cool'
|
||||
|
||||
# The temperature is set based on a schedule, learned behavior, AI or some
|
||||
# other related mechanism. User is not able to adjust the temperature
|
||||
HVAC_MODE_AUTO = 'auto'
|
||||
|
||||
# Device is in Dry/Humidity mode
|
||||
HVAC_MODE_DRY = 'dry'
|
||||
|
||||
# Only the fan is on, not fan and another mode like cool
|
||||
HVAC_MODE_FAN_ONLY = 'fan_only'
|
||||
|
||||
HVAC_MODES = [
|
||||
HVAC_MODE_OFF,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_AUTO,
|
||||
HVAC_MODE_DRY,
|
||||
HVAC_MODE_FAN_ONLY,
|
||||
]
|
||||
|
||||
|
||||
# Device is running an energy-saving mode
|
||||
PRESET_ECO = 'eco'
|
||||
|
||||
# Device is in away mode
|
||||
PRESET_AWAY = 'away'
|
||||
|
||||
# Device turn all valve full up
|
||||
PRESET_BOOST = 'boost'
|
||||
|
||||
# Device is in comfort mode
|
||||
PRESET_COMFORT = 'comfort'
|
||||
|
||||
# Device is in home mode
|
||||
PRESET_HOME = 'home'
|
||||
|
||||
# Device is prepared for sleep
|
||||
PRESET_SLEEP = 'sleep'
|
||||
|
||||
# Device is reacting to activity (e.g. movement sensors)
|
||||
PRESET_ACTIVITY = 'activity'
|
||||
|
||||
|
||||
# Possible fan state
|
||||
FAN_ON = "on"
|
||||
FAN_OFF = "off"
|
||||
FAN_AUTO = "auto"
|
||||
FAN_LOW = "low"
|
||||
FAN_MEDIUM = "medium"
|
||||
FAN_HIGH = "high"
|
||||
FAN_MIDDLE = "middle"
|
||||
FAN_FOCUS = "focus"
|
||||
FAN_DIFFUSE = "diffuse"
|
||||
|
||||
|
||||
# Possible swing state
|
||||
SWING_OFF = "off"
|
||||
SWING_BOTH = "both"
|
||||
SWING_VERTICAL = "vertical"
|
||||
SWING_HORIZONTAL = "horizontal"
|
||||
|
||||
|
||||
# This are support current states of HVAC
|
||||
CURRENT_HVAC_OFF = 'off'
|
||||
CURRENT_HVAC_HEAT = 'heating'
|
||||
CURRENT_HVAC_COOL = 'cooling'
|
||||
CURRENT_HVAC_DRY = 'drying'
|
||||
CURRENT_HVAC_IDLE = 'idle'
|
||||
|
||||
|
||||
ATTR_AUX_HEAT = 'aux_heat'
|
||||
ATTR_AWAY_MODE = 'away_mode'
|
||||
ATTR_CURRENT_HUMIDITY = 'current_humidity'
|
||||
ATTR_CURRENT_TEMPERATURE = 'current_temperature'
|
||||
ATTR_FAN_LIST = 'fan_list'
|
||||
ATTR_FAN_MODES = 'fan_modes'
|
||||
ATTR_FAN_MODE = 'fan_mode'
|
||||
ATTR_HOLD_MODE = 'hold_mode'
|
||||
ATTR_PRESET_MODE = 'preset_mode'
|
||||
ATTR_PRESET_MODES = 'preset_modes'
|
||||
ATTR_HUMIDITY = 'humidity'
|
||||
ATTR_MAX_HUMIDITY = 'max_humidity'
|
||||
ATTR_MAX_TEMP = 'max_temp'
|
||||
ATTR_MIN_HUMIDITY = 'min_humidity'
|
||||
ATTR_MAX_TEMP = 'max_temp'
|
||||
ATTR_MIN_TEMP = 'min_temp'
|
||||
ATTR_OPERATION_LIST = 'operation_list'
|
||||
ATTR_OPERATION_MODE = 'operation_mode'
|
||||
ATTR_SWING_LIST = 'swing_list'
|
||||
ATTR_HVAC_ACTIONS = 'hvac_action'
|
||||
ATTR_HVAC_MODES = 'hvac_modes'
|
||||
ATTR_HVAC_MODE = 'hvac_mode'
|
||||
ATTR_SWING_MODES = 'swing_modes'
|
||||
ATTR_SWING_MODE = 'swing_mode'
|
||||
ATTR_TARGET_TEMP_HIGH = 'target_temp_high'
|
||||
ATTR_TARGET_TEMP_LOW = 'target_temp_low'
|
||||
|
@ -28,33 +111,17 @@ DEFAULT_MAX_HUMIDITY = 99
|
|||
DOMAIN = 'climate'
|
||||
|
||||
SERVICE_SET_AUX_HEAT = 'set_aux_heat'
|
||||
SERVICE_SET_AWAY_MODE = 'set_away_mode'
|
||||
SERVICE_SET_FAN_MODE = 'set_fan_mode'
|
||||
SERVICE_SET_HOLD_MODE = 'set_hold_mode'
|
||||
SERVICE_SET_PRESET_MODE = 'set_preset_mode'
|
||||
SERVICE_SET_HUMIDITY = 'set_humidity'
|
||||
SERVICE_SET_OPERATION_MODE = 'set_operation_mode'
|
||||
SERVICE_SET_HVAC_MODE = 'set_hvac_mode'
|
||||
SERVICE_SET_SWING_MODE = 'set_swing_mode'
|
||||
SERVICE_SET_TEMPERATURE = 'set_temperature'
|
||||
|
||||
STATE_HEAT = 'heat'
|
||||
STATE_COOL = 'cool'
|
||||
STATE_IDLE = 'idle'
|
||||
STATE_AUTO = 'auto'
|
||||
STATE_MANUAL = 'manual'
|
||||
STATE_DRY = 'dry'
|
||||
STATE_FAN_ONLY = 'fan_only'
|
||||
STATE_ECO = 'eco'
|
||||
|
||||
SUPPORT_TARGET_TEMPERATURE = 1
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH = 2
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW = 4
|
||||
SUPPORT_TARGET_HUMIDITY = 8
|
||||
SUPPORT_TARGET_HUMIDITY_HIGH = 16
|
||||
SUPPORT_TARGET_HUMIDITY_LOW = 32
|
||||
SUPPORT_FAN_MODE = 64
|
||||
SUPPORT_OPERATION_MODE = 128
|
||||
SUPPORT_HOLD_MODE = 256
|
||||
SUPPORT_SWING_MODE = 512
|
||||
SUPPORT_AWAY_MODE = 1024
|
||||
SUPPORT_AUX_HEAT = 2048
|
||||
SUPPORT_ON_OFF = 4096
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE = 2
|
||||
SUPPORT_TARGET_HUMIDITY = 4
|
||||
SUPPORT_FAN_MODE = 8
|
||||
SUPPORT_PRESET_MODE = 16
|
||||
SUPPORT_SWING_MODE = 32
|
||||
SUPPORT_AUX_HEAT = 64
|
||||
|
|
|
@ -2,27 +2,24 @@
|
|||
import asyncio
|
||||
from typing import Iterable, Optional
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON, STATE_OFF, STATE_ON)
|
||||
from homeassistant.const import ATTR_TEMPERATURE
|
||||
from homeassistant.core import Context, State
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
from homeassistant.loader import bind_hass
|
||||
|
||||
from .const import (
|
||||
ATTR_AUX_HEAT,
|
||||
ATTR_AWAY_MODE,
|
||||
ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW,
|
||||
ATTR_HOLD_MODE,
|
||||
ATTR_OPERATION_MODE,
|
||||
ATTR_PRESET_MODE,
|
||||
ATTR_HVAC_MODE,
|
||||
ATTR_SWING_MODE,
|
||||
ATTR_HUMIDITY,
|
||||
SERVICE_SET_AWAY_MODE,
|
||||
HVAC_MODES,
|
||||
SERVICE_SET_AUX_HEAT,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
SERVICE_SET_HOLD_MODE,
|
||||
SERVICE_SET_OPERATION_MODE,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
SERVICE_SET_SWING_MODE,
|
||||
SERVICE_SET_HUMIDITY,
|
||||
DOMAIN,
|
||||
|
@ -33,9 +30,9 @@ async def _async_reproduce_states(hass: HomeAssistantType,
|
|||
state: State,
|
||||
context: Optional[Context] = None) -> None:
|
||||
"""Reproduce component states."""
|
||||
async def call_service(service: str, keys: Iterable):
|
||||
async def call_service(service: str, keys: Iterable, data=None):
|
||||
"""Call service with set of attributes given."""
|
||||
data = {}
|
||||
data = data or {}
|
||||
data['entity_id'] = state.entity_id
|
||||
for key in keys:
|
||||
if key in state.attributes:
|
||||
|
@ -45,17 +42,13 @@ async def _async_reproduce_states(hass: HomeAssistantType,
|
|||
DOMAIN, service, data,
|
||||
blocking=True, context=context)
|
||||
|
||||
if state.state == STATE_ON:
|
||||
await call_service(SERVICE_TURN_ON, [])
|
||||
elif state.state == STATE_OFF:
|
||||
await call_service(SERVICE_TURN_OFF, [])
|
||||
if state.state in HVAC_MODES:
|
||||
await call_service(
|
||||
SERVICE_SET_HVAC_MODE, [], {ATTR_HVAC_MODE: state.state})
|
||||
|
||||
if ATTR_AUX_HEAT in state.attributes:
|
||||
await call_service(SERVICE_SET_AUX_HEAT, [ATTR_AUX_HEAT])
|
||||
|
||||
if ATTR_AWAY_MODE in state.attributes:
|
||||
await call_service(SERVICE_SET_AWAY_MODE, [ATTR_AWAY_MODE])
|
||||
|
||||
if (ATTR_TEMPERATURE in state.attributes) or \
|
||||
(ATTR_TARGET_TEMP_HIGH in state.attributes) or \
|
||||
(ATTR_TARGET_TEMP_LOW in state.attributes):
|
||||
|
@ -64,21 +57,14 @@ async def _async_reproduce_states(hass: HomeAssistantType,
|
|||
ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW])
|
||||
|
||||
if ATTR_HOLD_MODE in state.attributes:
|
||||
await call_service(SERVICE_SET_HOLD_MODE,
|
||||
[ATTR_HOLD_MODE])
|
||||
|
||||
if ATTR_OPERATION_MODE in state.attributes:
|
||||
await call_service(SERVICE_SET_OPERATION_MODE,
|
||||
[ATTR_OPERATION_MODE])
|
||||
if ATTR_PRESET_MODE in state.attributes:
|
||||
await call_service(SERVICE_SET_PRESET_MODE, [ATTR_PRESET_MODE])
|
||||
|
||||
if ATTR_SWING_MODE in state.attributes:
|
||||
await call_service(SERVICE_SET_SWING_MODE,
|
||||
[ATTR_SWING_MODE])
|
||||
await call_service(SERVICE_SET_SWING_MODE, [ATTR_SWING_MODE])
|
||||
|
||||
if ATTR_HUMIDITY in state.attributes:
|
||||
await call_service(SERVICE_SET_HUMIDITY,
|
||||
[ATTR_HUMIDITY])
|
||||
await call_service(SERVICE_SET_HUMIDITY, [ATTR_HUMIDITY])
|
||||
|
||||
|
||||
@bind_hass
|
||||
|
|
|
@ -9,23 +9,14 @@ set_aux_heat:
|
|||
aux_heat:
|
||||
description: New value of axillary heater.
|
||||
example: true
|
||||
set_away_mode:
|
||||
description: Turn away mode on/off for climate device.
|
||||
set_preset_mode:
|
||||
description: Set preset mode for climate device.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change.
|
||||
example: 'climate.kitchen'
|
||||
away_mode:
|
||||
description: New value of away mode.
|
||||
example: true
|
||||
set_hold_mode:
|
||||
description: Turn hold mode for climate device.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change.
|
||||
example: 'climate.kitchen'
|
||||
hold_mode:
|
||||
description: New value of hold mode
|
||||
preset_mode:
|
||||
description: New value of preset mode
|
||||
example: 'away'
|
||||
set_temperature:
|
||||
description: Set target temperature of climate device.
|
||||
|
@ -42,9 +33,9 @@ set_temperature:
|
|||
target_temp_low:
|
||||
description: New target low temperature for HVAC.
|
||||
example: 20
|
||||
operation_mode:
|
||||
description: Operation mode to set temperature to. This defaults to current_operation mode if not set, or set incorrectly.
|
||||
example: 'Heat'
|
||||
hvac_mode:
|
||||
description: HVAC operation mode to set temperature to.
|
||||
example: 'heat'
|
||||
set_humidity:
|
||||
description: Set target humidity of climate device.
|
||||
fields:
|
||||
|
@ -63,15 +54,15 @@ set_fan_mode:
|
|||
fan_mode:
|
||||
description: New value of fan mode.
|
||||
example: On Low
|
||||
set_operation_mode:
|
||||
description: Set operation mode for climate device.
|
||||
set_hvac_mode:
|
||||
description: Set HVAC operation mode for climate device.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change.
|
||||
example: 'climate.nest'
|
||||
operation_mode:
|
||||
hvac_mode:
|
||||
description: New value of operation mode.
|
||||
example: Heat
|
||||
example: heat
|
||||
set_swing_mode:
|
||||
description: Set swing operation for climate device.
|
||||
fields:
|
||||
|
@ -81,20 +72,6 @@ set_swing_mode:
|
|||
swing_mode:
|
||||
description: New value of swing mode.
|
||||
|
||||
turn_on:
|
||||
description: Turn climate device on.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change.
|
||||
example: 'climate.kitchen'
|
||||
|
||||
turn_off:
|
||||
description: Turn climate device off.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change.
|
||||
example: 'climate.kitchen'
|
||||
|
||||
ecobee_set_fan_min_on_time:
|
||||
description: Set the minimum fan on time.
|
||||
fields:
|
||||
|
@ -137,13 +114,3 @@ nuheat_resume_program:
|
|||
entity_id:
|
||||
description: Name(s) of entities to change.
|
||||
example: 'climate.kitchen'
|
||||
|
||||
sensibo_assume_state:
|
||||
description: Set Sensibo device to external state.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change.
|
||||
example: 'climate.kitchen'
|
||||
state:
|
||||
description: State to set.
|
||||
example: 'idle'
|
||||
|
|
|
@ -6,27 +6,26 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY,
|
||||
STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
|
||||
HVAC_MODE_OFF, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY,
|
||||
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE |
|
||||
SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF)
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE)
|
||||
|
||||
DEFAULT_PORT = 10102
|
||||
|
||||
AVAILABLE_MODES = [STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_DRY,
|
||||
STATE_FAN_ONLY]
|
||||
AVAILABLE_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL,
|
||||
HVAC_MODE_DRY, HVAC_MODE_AUTO, HVAC_MODE_FAN_ONLY]
|
||||
|
||||
CM_TO_HA_STATE = {
|
||||
'heat': STATE_HEAT,
|
||||
'cool': STATE_COOL,
|
||||
'auto': STATE_AUTO,
|
||||
'dry': STATE_DRY,
|
||||
'fan': STATE_FAN_ONLY,
|
||||
'heat': HVAC_MODE_HEAT,
|
||||
'cool': HVAC_MODE_COOL,
|
||||
'auto': HVAC_MODE_AUTO,
|
||||
'dry': HVAC_MODE_DRY,
|
||||
'fan': HVAC_MODE_FAN_ONLY,
|
||||
}
|
||||
|
||||
HA_STATE_TO_CM = {value: key for key, value in CM_TO_HA_STATE.items()}
|
||||
|
@ -72,7 +71,8 @@ class CoolmasterClimate(ClimateDevice):
|
|||
"""Initialize the climate device."""
|
||||
self._device = device
|
||||
self._uid = device.uid
|
||||
self._operation_list = supported_modes
|
||||
self._hvac_modes = supported_modes
|
||||
self._hvac_mode = None
|
||||
self._target_temperature = None
|
||||
self._current_temperature = None
|
||||
self._current_fan_mode = None
|
||||
|
@ -89,7 +89,10 @@ class CoolmasterClimate(ClimateDevice):
|
|||
self._on = status['is_on']
|
||||
|
||||
device_mode = status['mode']
|
||||
self._current_operation = CM_TO_HA_STATE[device_mode]
|
||||
if self._on:
|
||||
self._hvac_mode = CM_TO_HA_STATE[device_mode]
|
||||
else:
|
||||
self._hvac_mode = HVAC_MODE_OFF
|
||||
|
||||
if status['unit'] == 'celsius':
|
||||
self._unit = TEMP_CELSIUS
|
||||
|
@ -127,27 +130,22 @@ class CoolmasterClimate(ClimateDevice):
|
|||
return self._target_temperature
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._current_operation
|
||||
def hvac_mode(self):
|
||||
"""Return hvac target hvac state."""
|
||||
return self._hvac_mode
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return self._operation_list
|
||||
return self._hvac_modes
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the device is on."""
|
||||
return self._on
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._current_fan_mode
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return FAN_MODES
|
||||
|
||||
|
@ -165,18 +163,13 @@ class CoolmasterClimate(ClimateDevice):
|
|||
fan_mode)
|
||||
self._device.set_fan_speed(fan_mode)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new operation mode."""
|
||||
_LOGGER.debug("Setting operation mode of %s to %s", self.unique_id,
|
||||
operation_mode)
|
||||
self._device.set_mode(HA_STATE_TO_CM[operation_mode])
|
||||
hvac_mode)
|
||||
|
||||
def turn_on(self):
|
||||
"""Turn on."""
|
||||
_LOGGER.debug("Turning %s on", self.unique_id)
|
||||
self._device.turn_on()
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn off."""
|
||||
_LOGGER.debug("Turning %s off", self.unique_id)
|
||||
self._device.turn_off()
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
self._device.turn_off()
|
||||
else:
|
||||
self._device.set_mode(HA_STATE_TO_CM[hvac_mode])
|
||||
self._device.turn_on()
|
||||
|
|
|
@ -5,14 +5,17 @@ import re
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_AWAY_MODE, ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE,
|
||||
ATTR_OPERATION_MODE, ATTR_SWING_MODE, STATE_AUTO, STATE_COOL, STATE_DRY,
|
||||
STATE_FAN_ONLY, STATE_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
|
||||
SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, STATE_OFF, TEMP_CELSIUS)
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS)
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE,
|
||||
SUPPORT_SWING_MODE,
|
||||
HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
|
||||
PRESET_AWAY, PRESET_HOME,
|
||||
ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE,
|
||||
ATTR_HVAC_MODE, ATTR_SWING_MODE,
|
||||
ATTR_PRESET_MODE)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . import DOMAIN as DAIKIN_DOMAIN
|
||||
|
@ -27,26 +30,31 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
})
|
||||
|
||||
HA_STATE_TO_DAIKIN = {
|
||||
STATE_FAN_ONLY: 'fan',
|
||||
STATE_DRY: 'dry',
|
||||
STATE_COOL: 'cool',
|
||||
STATE_HEAT: 'hot',
|
||||
STATE_AUTO: 'auto',
|
||||
STATE_OFF: 'off',
|
||||
HVAC_MODE_FAN_ONLY: 'fan',
|
||||
HVAC_MODE_DRY: 'dry',
|
||||
HVAC_MODE_COOL: 'cool',
|
||||
HVAC_MODE_HEAT: 'hot',
|
||||
HVAC_MODE_HEAT_COOL: 'auto',
|
||||
HVAC_MODE_OFF: 'off',
|
||||
}
|
||||
|
||||
DAIKIN_TO_HA_STATE = {
|
||||
'fan': STATE_FAN_ONLY,
|
||||
'dry': STATE_DRY,
|
||||
'cool': STATE_COOL,
|
||||
'hot': STATE_HEAT,
|
||||
'auto': STATE_AUTO,
|
||||
'off': STATE_OFF,
|
||||
'fan': HVAC_MODE_FAN_ONLY,
|
||||
'dry': HVAC_MODE_DRY,
|
||||
'cool': HVAC_MODE_COOL,
|
||||
'hot': HVAC_MODE_HEAT,
|
||||
'auto': HVAC_MODE_HEAT_COOL,
|
||||
'off': HVAC_MODE_OFF,
|
||||
}
|
||||
|
||||
HA_PRESET_TO_DAIKIN = {
|
||||
PRESET_AWAY: 'on',
|
||||
PRESET_HOME: 'off'
|
||||
}
|
||||
|
||||
HA_ATTR_TO_DAIKIN = {
|
||||
ATTR_AWAY_MODE: 'en_hol',
|
||||
ATTR_OPERATION_MODE: 'mode',
|
||||
ATTR_PRESET_MODE: 'en_hol',
|
||||
ATTR_HVAC_MODE: 'mode',
|
||||
ATTR_FAN_MODE: 'f_rate',
|
||||
ATTR_SWING_MODE: 'f_dir',
|
||||
ATTR_INSIDE_TEMPERATURE: 'htemp',
|
||||
|
@ -80,7 +88,7 @@ class DaikinClimate(ClimateDevice):
|
|||
|
||||
self._api = api
|
||||
self._list = {
|
||||
ATTR_OPERATION_MODE: list(HA_STATE_TO_DAIKIN),
|
||||
ATTR_HVAC_MODE: list(HA_STATE_TO_DAIKIN),
|
||||
ATTR_FAN_MODE: self._api.device.fan_rate,
|
||||
ATTR_SWING_MODE: list(
|
||||
map(
|
||||
|
@ -90,12 +98,10 @@ class DaikinClimate(ClimateDevice):
|
|||
),
|
||||
}
|
||||
|
||||
self._supported_features = (SUPPORT_ON_OFF
|
||||
| SUPPORT_OPERATION_MODE
|
||||
| SUPPORT_TARGET_TEMPERATURE)
|
||||
self._supported_features = SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
if self._api.device.support_away_mode:
|
||||
self._supported_features |= SUPPORT_AWAY_MODE
|
||||
self._supported_features |= SUPPORT_PRESET_MODE
|
||||
|
||||
if self._api.device.support_fan_rate:
|
||||
self._supported_features |= SUPPORT_FAN_MODE
|
||||
|
@ -127,7 +133,7 @@ class DaikinClimate(ClimateDevice):
|
|||
value = self._api.device.represent(daikin_attr)[1].title()
|
||||
elif key == ATTR_SWING_MODE:
|
||||
value = self._api.device.represent(daikin_attr)[1].title()
|
||||
elif key == ATTR_OPERATION_MODE:
|
||||
elif key == ATTR_HVAC_MODE:
|
||||
# Daikin can return also internal states auto-1 or auto-7
|
||||
# and we need to translate them as AUTO
|
||||
daikin_mode = re.sub(
|
||||
|
@ -135,6 +141,10 @@ class DaikinClimate(ClimateDevice):
|
|||
self._api.device.represent(daikin_attr)[1])
|
||||
ha_mode = DAIKIN_TO_HA_STATE.get(daikin_mode)
|
||||
value = ha_mode
|
||||
elif key == ATTR_PRESET_MODE:
|
||||
away = (self._api.device.represent(daikin_attr)[1]
|
||||
!= HA_STATE_TO_DAIKIN[HVAC_MODE_OFF])
|
||||
value = PRESET_AWAY if away else PRESET_HOME
|
||||
|
||||
if value is None:
|
||||
_LOGGER.error("Invalid value requested for key %s", key)
|
||||
|
@ -154,15 +164,17 @@ class DaikinClimate(ClimateDevice):
|
|||
values = {}
|
||||
|
||||
for attr in [ATTR_TEMPERATURE, ATTR_FAN_MODE, ATTR_SWING_MODE,
|
||||
ATTR_OPERATION_MODE]:
|
||||
ATTR_HVAC_MODE, ATTR_PRESET_MODE]:
|
||||
value = settings.get(attr)
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
daikin_attr = HA_ATTR_TO_DAIKIN.get(attr)
|
||||
if daikin_attr is not None:
|
||||
if attr == ATTR_OPERATION_MODE:
|
||||
if attr == ATTR_HVAC_MODE:
|
||||
values[daikin_attr] = HA_STATE_TO_DAIKIN[value]
|
||||
elif attr == ATTR_PRESET_MODE:
|
||||
values[daikin_attr] = HA_PRESET_TO_DAIKIN[value]
|
||||
elif value in self._list[attr]:
|
||||
values[daikin_attr] = value.lower()
|
||||
else:
|
||||
|
@ -218,21 +230,21 @@ class DaikinClimate(ClimateDevice):
|
|||
await self._set(kwargs)
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self.get(ATTR_OPERATION_MODE)
|
||||
return self.get(ATTR_HVAC_MODE)
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return self._list.get(ATTR_OPERATION_MODE)
|
||||
return self._list.get(ATTR_HVAC_MODE)
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set HVAC mode."""
|
||||
await self._set({ATTR_OPERATION_MODE: operation_mode})
|
||||
await self._set({ATTR_HVAC_MODE: hvac_mode})
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self.get(ATTR_FAN_MODE)
|
||||
|
||||
|
@ -241,12 +253,12 @@ class DaikinClimate(ClimateDevice):
|
|||
await self._set({ATTR_FAN_MODE: fan_mode})
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""List of available fan modes."""
|
||||
return self._list.get(ATTR_FAN_MODE)
|
||||
|
||||
@property
|
||||
def current_swing_mode(self):
|
||||
def swing_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self.get(ATTR_SWING_MODE)
|
||||
|
||||
|
@ -255,10 +267,24 @@ class DaikinClimate(ClimateDevice):
|
|||
await self._set({ATTR_SWING_MODE: swing_mode})
|
||||
|
||||
@property
|
||||
def swing_list(self):
|
||||
def swing_modes(self):
|
||||
"""List of available swing modes."""
|
||||
return self._list.get(ATTR_SWING_MODE)
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self.get(ATTR_PRESET_MODE)
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode):
|
||||
"""Set new target temperature."""
|
||||
await self._set({ATTR_PRESET_MODE: preset_mode})
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""List of available swing modes."""
|
||||
return list(HA_PRESET_TO_DAIKIN)
|
||||
|
||||
async def async_update(self):
|
||||
"""Retrieve latest state."""
|
||||
await self._api.async_update()
|
||||
|
@ -267,36 +293,3 @@ class DaikinClimate(ClimateDevice):
|
|||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
return self._api.device_info
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if on."""
|
||||
return self._api.device.represent(
|
||||
HA_ATTR_TO_DAIKIN[ATTR_OPERATION_MODE]
|
||||
)[1] != HA_STATE_TO_DAIKIN[STATE_OFF]
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn device on."""
|
||||
await self._api.device.set({})
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn device off."""
|
||||
await self._api.device.set({
|
||||
HA_ATTR_TO_DAIKIN[ATTR_OPERATION_MODE]:
|
||||
HA_STATE_TO_DAIKIN[STATE_OFF]
|
||||
})
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._api.device.represent(
|
||||
HA_ATTR_TO_DAIKIN[ATTR_AWAY_MODE]
|
||||
)[1] != HA_STATE_TO_DAIKIN[STATE_OFF]
|
||||
|
||||
async def async_turn_away_mode_on(self):
|
||||
"""Turn away mode on."""
|
||||
await self._api.device.set({HA_ATTR_TO_DAIKIN[ATTR_AWAY_MODE]: '1'})
|
||||
|
||||
async def async_turn_away_mode_off(self):
|
||||
"""Turn away mode off."""
|
||||
await self._api.device.set({HA_ATTR_TO_DAIKIN[ATTR_AWAY_MODE]: '0'})
|
||||
|
|
|
@ -3,7 +3,7 @@ from pydeconz.sensor import Thermostat
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_ON_OFF, SUPPORT_TARGET_TEMPERATURE)
|
||||
HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, TEMP_CELSIUS)
|
||||
from homeassistant.core import callback
|
||||
|
@ -13,6 +13,8 @@ from .const import ATTR_OFFSET, ATTR_VALVE, NEW_SENSOR
|
|||
from .deconz_device import DeconzDevice
|
||||
from .gateway import get_gateway_from_config_entry
|
||||
|
||||
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
|
@ -51,32 +53,28 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
class DeconzThermostat(DeconzDevice, ClimateDevice):
|
||||
"""Representation of a deCONZ thermostat."""
|
||||
|
||||
def __init__(self, device, gateway):
|
||||
"""Set up thermostat device."""
|
||||
super().__init__(device, gateway)
|
||||
|
||||
self._features = SUPPORT_ON_OFF
|
||||
self._features |= SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return self._features
|
||||
return SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if on."""
|
||||
return self._device.state_on
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn on switch."""
|
||||
data = {'mode': 'auto'}
|
||||
await self._device.async_set_config(data)
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
if self._device.on:
|
||||
return HVAC_MODE_HEAT
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn off switch."""
|
||||
data = {'mode': 'off'}
|
||||
await self._device.async_set_config(data)
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return SUPPORT_HVAC
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
|
@ -97,6 +95,15 @@ class DeconzThermostat(DeconzDevice, ClimateDevice):
|
|||
|
||||
await self._device.async_set_config(data)
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
if hvac_mode == HVAC_MODE_HEAT:
|
||||
data = {'mode': 'auto'}
|
||||
elif hvac_mode == HVAC_MODE_OFF:
|
||||
data = {'mode': 'off'}
|
||||
|
||||
await self._device.async_set_config(data)
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement."""
|
||||
|
|
|
@ -1,85 +1,138 @@
|
|||
"""Demo platform that offers a fake climate device."""
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, SUPPORT_AUX_HEAT,
|
||||
SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, SUPPORT_HOLD_MODE, SUPPORT_ON_OFF,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_HUMIDITY,
|
||||
SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH,
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL,
|
||||
CURRENT_HVAC_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT,
|
||||
HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, HVAC_MODES, SUPPORT_AUX_HEAT,
|
||||
SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE,
|
||||
SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE, HVAC_MODE_AUTO)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH
|
||||
SUPPORT_FLAGS = 0
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Demo climate devices."""
|
||||
add_entities([
|
||||
DemoClimate('HeatPump', 68, TEMP_FAHRENHEIT, None, None, 77,
|
||||
None, None, None, None, 'heat', None, None,
|
||||
None, True),
|
||||
DemoClimate('Hvac', 21, TEMP_CELSIUS, True, None, 22, 'On High',
|
||||
67, 54, 'Off', 'cool', False, None, None, None),
|
||||
DemoClimate('Ecobee', None, TEMP_CELSIUS, None, 'home', 23, 'Auto Low',
|
||||
None, None, 'Auto', 'auto', None, 24, 21, None)
|
||||
DemoClimate(
|
||||
name='HeatPump',
|
||||
target_temperature=68,
|
||||
unit_of_measurement=TEMP_FAHRENHEIT,
|
||||
preset=None,
|
||||
current_temperature=77,
|
||||
fan_mode=None,
|
||||
target_humidity=None,
|
||||
current_humidity=None,
|
||||
swing_mode=None,
|
||||
hvac_mode=HVAC_MODE_HEAT,
|
||||
hvac_action=CURRENT_HVAC_HEAT,
|
||||
aux=None,
|
||||
target_temp_high=None,
|
||||
target_temp_low=None,
|
||||
hvac_modes=[HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
),
|
||||
DemoClimate(
|
||||
name='Hvac',
|
||||
target_temperature=21,
|
||||
unit_of_measurement=TEMP_CELSIUS,
|
||||
preset=None,
|
||||
current_temperature=22,
|
||||
fan_mode='On High',
|
||||
target_humidity=67,
|
||||
current_humidity=54,
|
||||
swing_mode='Off',
|
||||
hvac_mode=HVAC_MODE_COOL,
|
||||
hvac_action=CURRENT_HVAC_COOL,
|
||||
aux=False,
|
||||
target_temp_high=None,
|
||||
target_temp_low=None,
|
||||
hvac_modes=[mode for mode in HVAC_MODES
|
||||
if mode != HVAC_MODE_HEAT_COOL]
|
||||
),
|
||||
DemoClimate(
|
||||
name='Ecobee',
|
||||
target_temperature=None,
|
||||
unit_of_measurement=TEMP_CELSIUS,
|
||||
preset='home',
|
||||
preset_modes=['home', 'eco'],
|
||||
current_temperature=23,
|
||||
fan_mode='Auto Low',
|
||||
target_humidity=None,
|
||||
current_humidity=None,
|
||||
swing_mode='Auto',
|
||||
hvac_mode=HVAC_MODE_HEAT_COOL,
|
||||
hvac_action=None,
|
||||
aux=None,
|
||||
target_temp_high=24,
|
||||
target_temp_low=21,
|
||||
hvac_modes=[HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL,
|
||||
HVAC_MODE_HEAT])
|
||||
])
|
||||
|
||||
|
||||
class DemoClimate(ClimateDevice):
|
||||
"""Representation of a demo climate device."""
|
||||
|
||||
def __init__(self, name, target_temperature, unit_of_measurement,
|
||||
away, hold, current_temperature, current_fan_mode,
|
||||
target_humidity, current_humidity, current_swing_mode,
|
||||
current_operation, aux, target_temp_high, target_temp_low,
|
||||
is_on):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
target_temperature,
|
||||
unit_of_measurement,
|
||||
preset,
|
||||
current_temperature,
|
||||
fan_mode,
|
||||
target_humidity,
|
||||
current_humidity,
|
||||
swing_mode,
|
||||
hvac_mode,
|
||||
hvac_action,
|
||||
aux,
|
||||
target_temp_high,
|
||||
target_temp_low,
|
||||
hvac_modes,
|
||||
preset_modes=None,
|
||||
):
|
||||
"""Initialize the climate device."""
|
||||
self._name = name
|
||||
self._support_flags = SUPPORT_FLAGS
|
||||
if target_temperature is not None:
|
||||
self._support_flags = \
|
||||
self._support_flags | SUPPORT_TARGET_TEMPERATURE
|
||||
if away is not None:
|
||||
self._support_flags = self._support_flags | SUPPORT_AWAY_MODE
|
||||
if hold is not None:
|
||||
self._support_flags = self._support_flags | SUPPORT_HOLD_MODE
|
||||
if current_fan_mode is not None:
|
||||
if preset is not None:
|
||||
self._support_flags = self._support_flags | SUPPORT_PRESET_MODE
|
||||
if fan_mode is not None:
|
||||
self._support_flags = self._support_flags | SUPPORT_FAN_MODE
|
||||
if target_humidity is not None:
|
||||
self._support_flags = \
|
||||
self._support_flags | SUPPORT_TARGET_HUMIDITY
|
||||
if current_swing_mode is not None:
|
||||
if swing_mode is not None:
|
||||
self._support_flags = self._support_flags | SUPPORT_SWING_MODE
|
||||
if current_operation is not None:
|
||||
self._support_flags = self._support_flags | SUPPORT_OPERATION_MODE
|
||||
if hvac_action is not None:
|
||||
self._support_flags = self._support_flags
|
||||
if aux is not None:
|
||||
self._support_flags = self._support_flags | SUPPORT_AUX_HEAT
|
||||
if target_temp_high is not None:
|
||||
if (HVAC_MODE_HEAT_COOL in hvac_modes or
|
||||
HVAC_MODE_AUTO in hvac_modes):
|
||||
self._support_flags = \
|
||||
self._support_flags | SUPPORT_TARGET_TEMPERATURE_HIGH
|
||||
if target_temp_low is not None:
|
||||
self._support_flags = \
|
||||
self._support_flags | SUPPORT_TARGET_TEMPERATURE_LOW
|
||||
if is_on is not None:
|
||||
self._support_flags = self._support_flags | SUPPORT_ON_OFF
|
||||
self._support_flags | SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||
self._target_temperature = target_temperature
|
||||
self._target_humidity = target_humidity
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self._away = away
|
||||
self._hold = hold
|
||||
self._preset = preset
|
||||
self._preset_modes = preset_modes
|
||||
self._current_temperature = current_temperature
|
||||
self._current_humidity = current_humidity
|
||||
self._current_fan_mode = current_fan_mode
|
||||
self._current_operation = current_operation
|
||||
self._current_fan_mode = fan_mode
|
||||
self._hvac_action = hvac_action
|
||||
self._hvac_mode = hvac_mode
|
||||
self._aux = aux
|
||||
self._current_swing_mode = current_swing_mode
|
||||
self._fan_list = ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off']
|
||||
self._operation_list = ['heat', 'cool', 'auto', 'off']
|
||||
self._swing_list = ['Auto', '1', '2', '3', 'Off']
|
||||
self._current_swing_mode = swing_mode
|
||||
self._fan_modes = ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off']
|
||||
self._hvac_modes = hvac_modes
|
||||
self._swing_modes = ['Auto', '1', '2', '3', 'Off']
|
||||
self._target_temperature_high = target_temp_high
|
||||
self._target_temperature_low = target_temp_low
|
||||
self._on = is_on
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
@ -132,46 +185,56 @@ class DemoClimate(ClimateDevice):
|
|||
return self._target_humidity
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_action(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._current_operation
|
||||
return self._hvac_action
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_mode(self):
|
||||
"""Return hvac target hvac state."""
|
||||
return self._hvac_mode
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return self._operation_list
|
||||
return self._hvac_modes
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return if away mode is on."""
|
||||
return self._away
|
||||
def preset_mode(self):
|
||||
"""Return preset mode."""
|
||||
return self._preset
|
||||
|
||||
@property
|
||||
def current_hold_mode(self):
|
||||
"""Return hold mode setting."""
|
||||
return self._hold
|
||||
def preset_modes(self):
|
||||
"""Return preset modes."""
|
||||
return self._preset_modes
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
def is_aux_heat(self):
|
||||
"""Return true if aux heat is on."""
|
||||
return self._aux
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the device is on."""
|
||||
return self._on
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._current_fan_mode
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return self._fan_list
|
||||
return self._fan_modes
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
@property
|
||||
def swing_mode(self):
|
||||
"""Return the swing setting."""
|
||||
return self._current_swing_mode
|
||||
|
||||
@property
|
||||
def swing_modes(self):
|
||||
"""List of available swing modes."""
|
||||
return self._swing_modes
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperatures."""
|
||||
if kwargs.get(ATTR_TEMPERATURE) is not None:
|
||||
self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
@ -179,69 +242,39 @@ class DemoClimate(ClimateDevice):
|
|||
kwargs.get(ATTR_TARGET_TEMP_LOW) is not None:
|
||||
self._target_temperature_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||
self._target_temperature_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
||||
self.schedule_update_ha_state()
|
||||
self.async_write_ha_state()
|
||||
|
||||
def set_humidity(self, humidity):
|
||||
async def async_set_humidity(self, humidity):
|
||||
"""Set new humidity level."""
|
||||
self._target_humidity = humidity
|
||||
self.schedule_update_ha_state()
|
||||
self.async_write_ha_state()
|
||||
|
||||
def set_swing_mode(self, swing_mode):
|
||||
async def async_set_swing_mode(self, swing_mode):
|
||||
"""Set new swing mode."""
|
||||
self._current_swing_mode = swing_mode
|
||||
self.schedule_update_ha_state()
|
||||
self.async_write_ha_state()
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
async def async_set_fan_mode(self, fan_mode):
|
||||
"""Set new fan mode."""
|
||||
self._current_fan_mode = fan_mode
|
||||
self.schedule_update_ha_state()
|
||||
self.async_write_ha_state()
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new operation mode."""
|
||||
self._current_operation = operation_mode
|
||||
self.schedule_update_ha_state()
|
||||
self._hvac_mode = hvac_mode
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def current_swing_mode(self):
|
||||
"""Return the swing setting."""
|
||||
return self._current_swing_mode
|
||||
|
||||
@property
|
||||
def swing_list(self):
|
||||
"""List of available swing modes."""
|
||||
return self._swing_list
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away mode on."""
|
||||
self._away = True
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away mode off."""
|
||||
self._away = False
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def set_hold_mode(self, hold_mode):
|
||||
"""Update hold_mode on."""
|
||||
self._hold = hold_mode
|
||||
self.schedule_update_ha_state()
|
||||
async def async_set_preset_mode(self, preset_mode):
|
||||
"""Update preset_mode on."""
|
||||
self._preset = preset_mode
|
||||
self.async_write_ha_state()
|
||||
|
||||
def turn_aux_heat_on(self):
|
||||
"""Turn auxiliary heater on."""
|
||||
self._aux = True
|
||||
self.schedule_update_ha_state()
|
||||
self.async_write_ha_state()
|
||||
|
||||
def turn_aux_heat_off(self):
|
||||
"""Turn auxiliary heater off."""
|
||||
self._aux = False
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def turn_on(self):
|
||||
"""Turn on."""
|
||||
self._on = True
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn off."""
|
||||
self._on = False
|
||||
self.schedule_update_ha_state()
|
||||
self.async_write_ha_state()
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
"""Support for Dyson Pure Hot+Cool link fan."""
|
||||
import logging
|
||||
|
||||
from libpurecool.const import HeatMode, HeatState, FocusMode, HeatTarget
|
||||
from libpurecool.dyson_pure_state import DysonPureHotCoolState
|
||||
from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, HVAC_MODE_COOL,
|
||||
HVAC_MODE_HEAT, SUPPORT_FAN_MODE, FAN_FOCUS,
|
||||
FAN_DIFFUSE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
|
||||
from . import DYSON_DEVICES
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STATE_DIFFUSE = "Diffuse Mode"
|
||||
STATE_FOCUS = "Focus Mode"
|
||||
FAN_LIST = [STATE_FOCUS, STATE_DIFFUSE]
|
||||
OPERATION_LIST = [STATE_HEAT, STATE_COOL]
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
| SUPPORT_OPERATION_MODE)
|
||||
SUPPORT_FAN = [FAN_FOCUS, FAN_DIFFUSE]
|
||||
SUPPORT_HVAG = [HVAC_MODE_COOL, HVAC_MODE_HEAT]
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
@ -24,7 +26,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
if discovery_info is None:
|
||||
return
|
||||
|
||||
from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink
|
||||
# Get Dyson Devices from parent component.
|
||||
add_devices(
|
||||
[DysonPureHotCoolLinkDevice(device)
|
||||
|
@ -43,17 +44,17 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
|
|||
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity is added to hass."""
|
||||
self.hass.async_add_job(self._device.add_message_listener,
|
||||
self.on_message)
|
||||
self.hass.async_add_job(
|
||||
self._device.add_message_listener, self.on_message)
|
||||
|
||||
def on_message(self, message):
|
||||
"""Call when new messages received from the climate."""
|
||||
from libpurecool.dyson_pure_state import DysonPureHotCoolState
|
||||
if not isinstance(message, DysonPureHotCoolState):
|
||||
return
|
||||
|
||||
if isinstance(message, DysonPureHotCoolState):
|
||||
_LOGGER.debug("Message received for climate device %s : %s",
|
||||
self.name, message)
|
||||
self.schedule_update_ha_state()
|
||||
_LOGGER.debug(
|
||||
"Message received for climate device %s : %s", self.name, message)
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
|
@ -101,32 +102,46 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
|
|||
return None
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
from libpurecool.const import HeatMode, HeatState
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
if self._device.state.heat_mode == HeatMode.HEAT_ON.value:
|
||||
return HVAC_MODE_HEAT
|
||||
return HVAC_MODE_COOL
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return SUPPORT_HVAG
|
||||
|
||||
@property
|
||||
def hvac_action(self):
|
||||
"""Return the current running hvac operation if supported.
|
||||
|
||||
Need to be one of CURRENT_HVAC_*.
|
||||
"""
|
||||
if self._device.state.heat_mode == HeatMode.HEAT_ON.value:
|
||||
if self._device.state.heat_state == HeatState.HEAT_STATE_ON.value:
|
||||
return STATE_HEAT
|
||||
return STATE_IDLE
|
||||
return STATE_COOL
|
||||
return CURRENT_HVAC_HEAT
|
||||
return CURRENT_HVAC_IDLE
|
||||
return CURRENT_HVAC_COOL
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return OPERATION_LIST
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
from libpurecool.const import FocusMode
|
||||
if self._device.state.focus_mode == FocusMode.FOCUS_ON.value:
|
||||
return STATE_FOCUS
|
||||
return STATE_DIFFUSE
|
||||
return FAN_FOCUS
|
||||
return FAN_DIFFUSE
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return FAN_LIST
|
||||
return SUPPORT_FAN
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
@ -138,7 +153,6 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
|
|||
# Limit the target temperature into acceptable range.
|
||||
target_temp = min(self.max_temp, target_temp)
|
||||
target_temp = max(self.min_temp, target_temp)
|
||||
from libpurecool.const import HeatTarget, HeatMode
|
||||
self._device.set_configuration(
|
||||
heat_target=HeatTarget.celsius(target_temp),
|
||||
heat_mode=HeatMode.HEAT_ON)
|
||||
|
@ -146,19 +160,17 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
|
|||
def set_fan_mode(self, fan_mode):
|
||||
"""Set new fan mode."""
|
||||
_LOGGER.debug("Set %s focus mode %s", self.name, fan_mode)
|
||||
from libpurecool.const import FocusMode
|
||||
if fan_mode == STATE_FOCUS:
|
||||
if fan_mode == FAN_FOCUS:
|
||||
self._device.set_configuration(focus_mode=FocusMode.FOCUS_ON)
|
||||
elif fan_mode == STATE_DIFFUSE:
|
||||
elif fan_mode == FAN_DIFFUSE:
|
||||
self._device.set_configuration(focus_mode=FocusMode.FOCUS_OFF)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set operation mode."""
|
||||
_LOGGER.debug("Set %s heat mode %s", self.name, operation_mode)
|
||||
from libpurecool.const import HeatMode
|
||||
if operation_mode == STATE_HEAT:
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
_LOGGER.debug("Set %s heat mode %s", self.name, hvac_mode)
|
||||
if hvac_mode == HVAC_MODE_HEAT:
|
||||
self._device.set_configuration(heat_mode=HeatMode.HEAT_ON)
|
||||
elif operation_mode == STATE_COOL:
|
||||
elif hvac_mode == HVAC_MODE_COOL:
|
||||
self._device.set_configuration(heat_mode=HeatMode.HEAT_OFF)
|
||||
|
||||
@property
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
"""Support for Ecobee Thermostats."""
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import ecobee
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
DOMAIN, STATE_COOL, STATE_HEAT, STATE_AUTO, STATE_IDLE,
|
||||
DOMAIN, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF,
|
||||
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
|
||||
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_RANGE, SUPPORT_FAN_MODE,
|
||||
PRESET_AWAY, FAN_AUTO, FAN_ON, CURRENT_HVAC_OFF, CURRENT_HVAC_HEAT,
|
||||
CURRENT_HVAC_COOL
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
|
||||
ATTR_ENTITY_ID, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT, TEMP_CELSIUS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_CONFIGURING = {}
|
||||
|
@ -23,10 +24,33 @@ ATTR_FAN_MIN_ON_TIME = 'fan_min_on_time'
|
|||
ATTR_RESUME_ALL = 'resume_all'
|
||||
|
||||
DEFAULT_RESUME_ALL = False
|
||||
TEMPERATURE_HOLD = 'temp'
|
||||
VACATION_HOLD = 'vacation'
|
||||
PRESET_TEMPERATURE = 'temp'
|
||||
PRESET_VACATION = 'vacation'
|
||||
PRESET_AUX_HEAT_ONLY = 'aux_heat_only'
|
||||
PRESET_HOLD_NEXT_TRANSITION = 'next_transition'
|
||||
PRESET_HOLD_INDEFINITE = 'indefinite'
|
||||
AWAY_MODE = 'awayMode'
|
||||
|
||||
ECOBEE_HVAC_TO_HASS = {
|
||||
'auxHeatOnly': HVAC_MODE_HEAT,
|
||||
'heat': HVAC_MODE_HEAT,
|
||||
'cool': HVAC_MODE_COOL,
|
||||
'off': HVAC_MODE_OFF,
|
||||
'auto': HVAC_MODE_AUTO,
|
||||
}
|
||||
|
||||
PRESET_TO_ECOBEE_HOLD = {
|
||||
PRESET_HOLD_NEXT_TRANSITION: 'nextTransition',
|
||||
PRESET_HOLD_INDEFINITE: 'indefinite',
|
||||
}
|
||||
|
||||
PRESET_MODES = [
|
||||
PRESET_AWAY,
|
||||
PRESET_TEMPERATURE,
|
||||
PRESET_HOLD_NEXT_TRANSITION,
|
||||
PRESET_HOLD_INDEFINITE
|
||||
]
|
||||
|
||||
SERVICE_SET_FAN_MIN_ON_TIME = 'ecobee_set_fan_min_on_time'
|
||||
SERVICE_RESUME_PROGRAM = 'ecobee_resume_program'
|
||||
|
||||
|
@ -40,11 +64,9 @@ RESUME_PROGRAM_SCHEMA = vol.Schema({
|
|||
vol.Optional(ATTR_RESUME_ALL, default=DEFAULT_RESUME_ALL): cv.boolean,
|
||||
})
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
|
||||
SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH |
|
||||
SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_HIGH |
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_FAN_MODE)
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
||||
SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_RANGE |
|
||||
SUPPORT_FAN_MODE)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
@ -114,9 +136,10 @@ class Thermostat(ClimateDevice):
|
|||
self.hold_temp = hold_temp
|
||||
self.vacation = None
|
||||
self._climate_list = self.climate_list
|
||||
self._operation_list = ['auto', 'auxHeatOnly', 'cool',
|
||||
'heat', 'off']
|
||||
self._fan_list = ['auto', 'on']
|
||||
self._operation_list = [
|
||||
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF
|
||||
]
|
||||
self._fan_modes = [FAN_AUTO, FAN_ON]
|
||||
self.update_without_throttle = False
|
||||
|
||||
def update(self):
|
||||
|
@ -143,6 +166,9 @@ class Thermostat(ClimateDevice):
|
|||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement."""
|
||||
if self.thermostat['settings']['useCelsius']:
|
||||
return TEMP_CELSIUS
|
||||
|
||||
return TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
|
@ -153,25 +179,25 @@ class Thermostat(ClimateDevice):
|
|||
@property
|
||||
def target_temperature_low(self):
|
||||
"""Return the lower bound temperature we try to reach."""
|
||||
if self.current_operation == STATE_AUTO:
|
||||
if self.hvac_mode == HVAC_MODE_AUTO:
|
||||
return self.thermostat['runtime']['desiredHeat'] / 10.0
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature_high(self):
|
||||
"""Return the upper bound temperature we try to reach."""
|
||||
if self.current_operation == STATE_AUTO:
|
||||
if self.hvac_mode == HVAC_MODE_AUTO:
|
||||
return self.thermostat['runtime']['desiredCool'] / 10.0
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
if self.current_operation == STATE_AUTO:
|
||||
if self.hvac_mode == HVAC_MODE_AUTO:
|
||||
return None
|
||||
if self.current_operation == STATE_HEAT:
|
||||
if self.hvac_mode == HVAC_MODE_HEAT:
|
||||
return self.thermostat['runtime']['desiredHeat'] / 10.0
|
||||
if self.current_operation == STATE_COOL:
|
||||
if self.hvac_mode == HVAC_MODE_COOL:
|
||||
return self.thermostat['runtime']['desiredCool'] / 10.0
|
||||
return None
|
||||
|
||||
|
@ -180,70 +206,63 @@ class Thermostat(ClimateDevice):
|
|||
"""Return the current fan status."""
|
||||
if 'fan' in self.thermostat['equipmentStatus']:
|
||||
return STATE_ON
|
||||
return STATE_OFF
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self.thermostat['runtime']['desiredFanMode']
|
||||
|
||||
@property
|
||||
def current_hold_mode(self):
|
||||
"""Return current hold mode."""
|
||||
mode = self._current_hold_mode
|
||||
return None if mode == AWAY_MODE else mode
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the available fan modes."""
|
||||
return self._fan_list
|
||||
return self._fan_modes
|
||||
|
||||
@property
|
||||
def _current_hold_mode(self):
|
||||
def preset_mode(self):
|
||||
"""Return current preset mode."""
|
||||
events = self.thermostat['events']
|
||||
for event in events:
|
||||
if event['running']:
|
||||
if event['type'] == 'hold':
|
||||
if event['holdClimateRef'] == 'away':
|
||||
if int(event['endDate'][0:4]) - \
|
||||
int(event['startDate'][0:4]) <= 1:
|
||||
# A temporary hold from away climate is a hold
|
||||
return 'away'
|
||||
# A permanent hold from away climate
|
||||
return AWAY_MODE
|
||||
if event['holdClimateRef'] != "":
|
||||
# Any other hold based on climate
|
||||
return event['holdClimateRef']
|
||||
# Any hold not based on a climate is a temp hold
|
||||
return TEMPERATURE_HOLD
|
||||
if event['type'].startswith('auto'):
|
||||
# All auto modes are treated as holds
|
||||
return event['type'][4:].lower()
|
||||
if event['type'] == 'vacation':
|
||||
self.vacation = event['name']
|
||||
return VACATION_HOLD
|
||||
if not event['running']:
|
||||
continue
|
||||
|
||||
if event['type'] == 'hold':
|
||||
if event['holdClimateRef'] == 'away':
|
||||
if int(event['endDate'][0:4]) - \
|
||||
int(event['startDate'][0:4]) <= 1:
|
||||
# A temporary hold from away climate is a hold
|
||||
return PRESET_AWAY
|
||||
# A permanent hold from away climate
|
||||
return PRESET_AWAY
|
||||
if event['holdClimateRef'] != "":
|
||||
# Any other hold based on climate
|
||||
return event['holdClimateRef']
|
||||
# Any hold not based on a climate is a temp hold
|
||||
return PRESET_TEMPERATURE
|
||||
if event['type'].startswith('auto'):
|
||||
# All auto modes are treated as holds
|
||||
return event['type'][4:].lower()
|
||||
if event['type'] == 'vacation':
|
||||
self.vacation = event['name']
|
||||
return PRESET_VACATION
|
||||
|
||||
if self.is_aux_heat:
|
||||
return PRESET_AUX_HEAT_ONLY
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation."""
|
||||
if self.operation_mode == 'auxHeatOnly' or \
|
||||
self.operation_mode == 'heatPump':
|
||||
return STATE_HEAT
|
||||
return self.operation_mode
|
||||
return ECOBEE_HVAC_TO_HASS[self.thermostat['settings']['hvacMode']]
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the operation modes list."""
|
||||
return self._operation_list
|
||||
|
||||
@property
|
||||
def operation_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self.thermostat['settings']['hvacMode']
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
def climate_mode(self):
|
||||
"""Return current mode, as the user-visible name."""
|
||||
cur = self.thermostat['program']['currentClimateRef']
|
||||
climates = self.thermostat['program']['climates']
|
||||
|
@ -251,80 +270,76 @@ class Thermostat(ClimateDevice):
|
|||
return current[0]['name']
|
||||
|
||||
@property
|
||||
def fan_min_on_time(self):
|
||||
"""Return current fan minimum on time."""
|
||||
return self.thermostat['settings']['fanMinOnTime']
|
||||
def current_humidity(self) -> Optional[int]:
|
||||
"""Return the current humidity."""
|
||||
return self.thermostat['runtime']['actualHumidity']
|
||||
|
||||
@property
|
||||
def hvac_action(self):
|
||||
"""Return current HVAC action."""
|
||||
status = self.thermostat['equipmentStatus']
|
||||
operation = None
|
||||
|
||||
if status == '':
|
||||
operation = CURRENT_HVAC_OFF
|
||||
elif 'Cool' in status:
|
||||
operation = CURRENT_HVAC_COOL
|
||||
elif 'auxHeat' in status or 'heatPump' in status:
|
||||
operation = CURRENT_HVAC_HEAT
|
||||
|
||||
return operation
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
# Move these to Thermostat Device and make them global
|
||||
status = self.thermostat['equipmentStatus']
|
||||
operation = None
|
||||
if status == '':
|
||||
operation = STATE_IDLE
|
||||
elif 'Cool' in status:
|
||||
operation = STATE_COOL
|
||||
elif 'auxHeat' in status:
|
||||
operation = STATE_HEAT
|
||||
elif 'heatPump' in status:
|
||||
operation = STATE_HEAT
|
||||
else:
|
||||
operation = status
|
||||
|
||||
return {
|
||||
"actual_humidity": self.thermostat['runtime']['actualHumidity'],
|
||||
"fan": self.fan,
|
||||
"climate_mode": self.mode,
|
||||
"operation": operation,
|
||||
"climate_mode": self.climate_mode,
|
||||
"equipment_running": status,
|
||||
"climate_list": self.climate_list,
|
||||
"fan_min_on_time": self.fan_min_on_time
|
||||
"fan_min_on_time": self.thermostat['settings']['fanMinOnTime']
|
||||
}
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._current_hold_mode == AWAY_MODE
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
def is_aux_heat(self):
|
||||
"""Return true if aux heater."""
|
||||
return 'auxHeat' in self.thermostat['equipmentStatus']
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away mode on by setting it on away hold indefinitely."""
|
||||
if self._current_hold_mode != AWAY_MODE:
|
||||
def set_preset(self, preset):
|
||||
"""Activate a preset."""
|
||||
if preset == self.preset_mode:
|
||||
return
|
||||
|
||||
self.update_without_throttle = True
|
||||
|
||||
# If we are currently in vacation mode, cancel it.
|
||||
if self.preset_mode == PRESET_VACATION:
|
||||
self.data.ecobee.delete_vacation(
|
||||
self.thermostat_index, self.vacation)
|
||||
|
||||
if preset == PRESET_AWAY:
|
||||
self.data.ecobee.set_climate_hold(self.thermostat_index, 'away',
|
||||
'indefinite')
|
||||
self.update_without_throttle = True
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
if self._current_hold_mode == AWAY_MODE:
|
||||
elif preset == PRESET_TEMPERATURE:
|
||||
self.set_temp_hold(self.current_temperature)
|
||||
|
||||
elif preset in (PRESET_HOLD_NEXT_TRANSITION, PRESET_HOLD_INDEFINITE):
|
||||
self.data.ecobee.set_climate_hold(
|
||||
self.thermostat_index, PRESET_TO_ECOBEE_HOLD[preset],
|
||||
self.hold_preference())
|
||||
|
||||
elif preset is None:
|
||||
self.data.ecobee.resume_program(self.thermostat_index)
|
||||
self.update_without_throttle = True
|
||||
|
||||
def set_hold_mode(self, hold_mode):
|
||||
"""Set hold mode (away, home, temp, sleep, etc.)."""
|
||||
hold = self.current_hold_mode
|
||||
|
||||
if hold == hold_mode:
|
||||
# no change, so no action required
|
||||
return
|
||||
if hold_mode == 'None' or hold_mode is None:
|
||||
if hold == VACATION_HOLD:
|
||||
self.data.ecobee.delete_vacation(
|
||||
self.thermostat_index, self.vacation)
|
||||
else:
|
||||
self.data.ecobee.resume_program(self.thermostat_index)
|
||||
else:
|
||||
if hold_mode == TEMPERATURE_HOLD:
|
||||
self.set_temp_hold(self.current_temperature)
|
||||
else:
|
||||
self.data.ecobee.set_climate_hold(
|
||||
self.thermostat_index, hold_mode, self.hold_preference())
|
||||
self.update_without_throttle = True
|
||||
_LOGGER.warning("Received invalid preset: %s", preset)
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Return available preset modes."""
|
||||
return PRESET_MODES
|
||||
|
||||
def set_auto_temp_hold(self, heat_temp, cool_temp):
|
||||
"""Set temperature hold in auto mode."""
|
||||
|
@ -352,7 +367,8 @@ class Thermostat(ClimateDevice):
|
|||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
"""Set the fan mode. Valid values are "on" or "auto"."""
|
||||
if (fan_mode.lower() != STATE_ON) and (fan_mode.lower() != STATE_AUTO):
|
||||
if fan_mode.lower() != STATE_ON and \
|
||||
fan_mode.lower() != HVAC_MODE_AUTO:
|
||||
error = "Invalid fan_mode value: Valid values are 'on' or 'auto'"
|
||||
_LOGGER.error(error)
|
||||
return
|
||||
|
@ -376,8 +392,8 @@ class Thermostat(ClimateDevice):
|
|||
heatCoolMinDelta property.
|
||||
https://www.ecobee.com/home/developer/api/examples/ex5.shtml
|
||||
"""
|
||||
if self.current_operation == STATE_HEAT or self.current_operation == \
|
||||
STATE_COOL:
|
||||
if self.hvac_mode == HVAC_MODE_HEAT or \
|
||||
self.hvac_mode == HVAC_MODE_COOL:
|
||||
heat_temp = temp
|
||||
cool_temp = temp
|
||||
else:
|
||||
|
@ -392,7 +408,7 @@ class Thermostat(ClimateDevice):
|
|||
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
||||
if self.current_operation == STATE_AUTO and \
|
||||
if self.hvac_mode == HVAC_MODE_AUTO and \
|
||||
(low_temp is not None or high_temp is not None):
|
||||
self.set_auto_temp_hold(low_temp, high_temp)
|
||||
elif temp is not None:
|
||||
|
@ -405,9 +421,14 @@ class Thermostat(ClimateDevice):
|
|||
"""Set the humidity level."""
|
||||
self.data.ecobee.set_humidity(self.thermostat_index, humidity)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
|
||||
self.data.ecobee.set_hvac_mode(self.thermostat_index, operation_mode)
|
||||
ecobee_value = next((k for k, v in ECOBEE_HVAC_TO_HASS.items()
|
||||
if v == hvac_mode), None)
|
||||
if ecobee_value is None:
|
||||
_LOGGER.error("Invalid mode for set_hvac_mode: %s", hvac_mode)
|
||||
return
|
||||
self.data.ecobee.set_hvac_mode(self.thermostat_index, ecobee_value)
|
||||
self.update_without_throttle = True
|
||||
|
||||
def set_fan_min_on_time(self, fan_min_on_time):
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
"""Support for control of Elk-M1 connected thermostats."""
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, STATE_AUTO, STATE_COOL,
|
||||
STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE_HIGH,
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, HVAC_MODE_AUTO,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_AUX_HEAT,
|
||||
SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||
from homeassistant.const import PRECISION_WHOLE, STATE_ON
|
||||
|
||||
from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities
|
||||
|
||||
|
||||
SUPPORT_HVAC = [HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_AUTO,
|
||||
HVAC_MODE_FAN_ONLY]
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Create the Elk-M1 thermostat platform."""
|
||||
|
@ -32,9 +37,8 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return (SUPPORT_OPERATION_MODE | SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT
|
||||
| SUPPORT_TARGET_TEMPERATURE_HIGH
|
||||
| SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
return (SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT
|
||||
| SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
|
@ -78,14 +82,14 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||
return self._element.humidity
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return [STATE_IDLE, STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_FAN_ONLY]
|
||||
return SUPPORT_HVAC
|
||||
|
||||
@property
|
||||
def precision(self):
|
||||
|
@ -93,7 +97,7 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||
return PRECISION_WHOLE
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
def is_aux_heat(self):
|
||||
"""Return if aux heater is on."""
|
||||
from elkm1_lib.const import ThermostatMode
|
||||
return self._element.mode == ThermostatMode.EMERGENCY_HEAT.value
|
||||
|
@ -109,11 +113,11 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||
return 99
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
from elkm1_lib.const import ThermostatFan
|
||||
if self._element.fan == ThermostatFan.AUTO.value:
|
||||
return STATE_AUTO
|
||||
return HVAC_MODE_AUTO
|
||||
if self._element.fan == ThermostatFan.ON.value:
|
||||
return STATE_ON
|
||||
return None
|
||||
|
@ -125,17 +129,19 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||
if fan is not None:
|
||||
self._element.set(ThermostatSetting.FAN.value, fan)
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set thermostat operation mode."""
|
||||
from elkm1_lib.const import ThermostatFan, ThermostatMode
|
||||
settings = {
|
||||
STATE_IDLE: (ThermostatMode.OFF.value, ThermostatFan.AUTO.value),
|
||||
STATE_HEAT: (ThermostatMode.HEAT.value, None),
|
||||
STATE_COOL: (ThermostatMode.COOL.value, None),
|
||||
STATE_AUTO: (ThermostatMode.AUTO.value, None),
|
||||
STATE_FAN_ONLY: (ThermostatMode.OFF.value, ThermostatFan.ON.value)
|
||||
HVAC_MODE_OFF:
|
||||
(ThermostatMode.OFF.value, ThermostatFan.AUTO.value),
|
||||
HVAC_MODE_HEAT: (ThermostatMode.HEAT.value, None),
|
||||
HVAC_MODE_COOL: (ThermostatMode.COOL.value, None),
|
||||
HVAC_MODE_AUTO: (ThermostatMode.AUTO.value, None),
|
||||
HVAC_MODE_FAN_ONLY:
|
||||
(ThermostatMode.OFF.value, ThermostatFan.ON.value)
|
||||
}
|
||||
self._elk_set(settings[operation_mode][0], settings[operation_mode][1])
|
||||
self._elk_set(settings[hvac_mode][0], settings[hvac_mode][1])
|
||||
|
||||
async def async_turn_aux_heat_on(self):
|
||||
"""Turn auxiliary heater on."""
|
||||
|
@ -148,14 +154,14 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||
self._elk_set(ThermostatMode.HEAT.value, None)
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return [STATE_AUTO, STATE_ON]
|
||||
return [HVAC_MODE_AUTO, STATE_ON]
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode):
|
||||
"""Set new target fan mode."""
|
||||
from elkm1_lib.const import ThermostatFan
|
||||
if fan_mode == STATE_AUTO:
|
||||
if fan_mode == HVAC_MODE_AUTO:
|
||||
self._elk_set(None, ThermostatFan.AUTO.value)
|
||||
elif fan_mode == STATE_ON:
|
||||
self._elk_set(None, ThermostatFan.ON.value)
|
||||
|
@ -175,13 +181,13 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||
def _element_changed(self, element, changeset):
|
||||
from elkm1_lib.const import ThermostatFan, ThermostatMode
|
||||
mode_to_state = {
|
||||
ThermostatMode.OFF.value: STATE_IDLE,
|
||||
ThermostatMode.COOL.value: STATE_COOL,
|
||||
ThermostatMode.HEAT.value: STATE_HEAT,
|
||||
ThermostatMode.EMERGENCY_HEAT.value: STATE_HEAT,
|
||||
ThermostatMode.AUTO.value: STATE_AUTO,
|
||||
ThermostatMode.OFF.value: HVAC_MODE_OFF,
|
||||
ThermostatMode.COOL.value: HVAC_MODE_COOL,
|
||||
ThermostatMode.HEAT.value: HVAC_MODE_HEAT,
|
||||
ThermostatMode.EMERGENCY_HEAT.value: HVAC_MODE_HEAT,
|
||||
ThermostatMode.AUTO.value: HVAC_MODE_AUTO,
|
||||
}
|
||||
self._state = mode_to_state.get(self._element.mode)
|
||||
if self._state == STATE_IDLE and \
|
||||
if self._state == HVAC_MODE_OFF and \
|
||||
self._element.fan == ThermostatFan.ON.value:
|
||||
self._state = STATE_FAN_ONLY
|
||||
self._state = HVAC_MODE_FAN_ONLY
|
||||
|
|
|
@ -5,10 +5,11 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_HEAT, STATE_AUTO, SUPPORT_AUX_HEAT, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, SUPPORT_AUX_HEAT,
|
||||
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, CURRENT_HVAC_HEAT,
|
||||
CURRENT_HVAC_IDLE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD, STATE_OFF)
|
||||
ATTR_TEMPERATURE, TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -16,7 +17,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
# Return cached results if last scan was less then this time ago
|
||||
SCAN_INTERVAL = timedelta(seconds=120)
|
||||
|
||||
OPERATION_LIST = [STATE_AUTO, STATE_HEAT, STATE_OFF]
|
||||
OPERATION_LIST = [HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
|
@ -24,9 +25,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
})
|
||||
|
||||
EPH_TO_HA_STATE = {
|
||||
'AUTO': STATE_AUTO,
|
||||
'ON': STATE_HEAT,
|
||||
'OFF': STATE_OFF
|
||||
'AUTO': HVAC_MODE_HEAT_COOL,
|
||||
'ON': HVAC_MODE_HEAT,
|
||||
'OFF': HVAC_MODE_OFF
|
||||
}
|
||||
|
||||
HA_STATE_TO_EPH = {value: key for key, value in EPH_TO_HA_STATE.items()}
|
||||
|
@ -65,11 +66,10 @@ class EphEmberThermostat(ClimateDevice):
|
|||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
if self._hot_water:
|
||||
return SUPPORT_AUX_HEAT | SUPPORT_OPERATION_MODE
|
||||
return SUPPORT_AUX_HEAT
|
||||
|
||||
return (SUPPORT_TARGET_TEMPERATURE |
|
||||
SUPPORT_AUX_HEAT |
|
||||
SUPPORT_OPERATION_MODE)
|
||||
SUPPORT_AUX_HEAT)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -100,43 +100,35 @@ class EphEmberThermostat(ClimateDevice):
|
|||
return 1
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Show Device Attributes."""
|
||||
attributes = {
|
||||
'currently_active': self._zone['isCurrentlyActive']
|
||||
}
|
||||
return attributes
|
||||
def hvac_action(self):
|
||||
"""Return current HVAC action."""
|
||||
if self._zone['isCurrentlyActive']:
|
||||
return CURRENT_HVAC_HEAT
|
||||
|
||||
return CURRENT_HVAC_IDLE
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
from pyephember.pyephember import ZoneMode
|
||||
mode = ZoneMode(self._zone['mode'])
|
||||
return self.map_mode_eph_hass(mode)
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the supported operations."""
|
||||
return OPERATION_LIST
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set the operation mode."""
|
||||
mode = self.map_mode_hass_eph(operation_mode)
|
||||
mode = self.map_mode_hass_eph(hvac_mode)
|
||||
if mode is not None:
|
||||
self._ember.set_mode_by_name(self._zone_name, mode)
|
||||
else:
|
||||
_LOGGER.error("Invalid operation mode provided %s", operation_mode)
|
||||
_LOGGER.error("Invalid operation mode provided %s", hvac_mode)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return current state."""
|
||||
if self._zone['isCurrentlyActive']:
|
||||
return True
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
def is_aux_heat(self):
|
||||
"""Return true if aux heater."""
|
||||
return self._zone['isBoostActive']
|
||||
|
||||
|
@ -197,4 +189,4 @@ class EphEmberThermostat(ClimateDevice):
|
|||
@staticmethod
|
||||
def map_mode_eph_hass(operation_mode):
|
||||
"""Map from eph mode to home assistant mode."""
|
||||
return EPH_TO_HA_STATE.get(operation_mode.name, STATE_AUTO)
|
||||
return EPH_TO_HA_STATE.get(operation_mode.name, HVAC_MODE_HEAT_COOL)
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
"""Support for eQ-3 Bluetooth Smart thermostats."""
|
||||
import logging
|
||||
|
||||
import eq3bt as eq3 # pylint: disable=import-error
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_HEAT, STATE_MANUAL, STATE_ECO,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE,
|
||||
SUPPORT_ON_OFF)
|
||||
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST,
|
||||
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_MAC, CONF_DEVICES, STATE_ON, STATE_OFF,
|
||||
TEMP_CELSIUS, PRECISION_HALVES)
|
||||
ATTR_TEMPERATURE, CONF_DEVICES, CONF_MAC, PRECISION_HALVES, TEMP_CELSIUS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -23,6 +22,32 @@ ATTR_STATE_LOCKED = 'is_locked'
|
|||
ATTR_STATE_LOW_BAT = 'low_battery'
|
||||
ATTR_STATE_AWAY_END = 'away_end'
|
||||
|
||||
EQ_TO_HA_HVAC = {
|
||||
eq3.Mode.Open: HVAC_MODE_HEAT,
|
||||
eq3.Mode.Closed: HVAC_MODE_OFF,
|
||||
eq3.Mode.Auto: HVAC_MODE_AUTO,
|
||||
eq3.Mode.Manual: HVAC_MODE_HEAT,
|
||||
eq3.Mode.Boost: HVAC_MODE_AUTO,
|
||||
eq3.Mode.Away: HVAC_MODE_HEAT,
|
||||
}
|
||||
|
||||
HA_TO_EQ_HVAC = {
|
||||
HVAC_MODE_HEAT: eq3.Mode.Manual,
|
||||
HVAC_MODE_OFF: eq3.Mode.Closed,
|
||||
HVAC_MODE_AUTO: eq3.Mode.Auto
|
||||
}
|
||||
|
||||
EQ_TO_HA_PRESET = {
|
||||
eq3.Mode.Boost: PRESET_BOOST,
|
||||
eq3.Mode.Away: PRESET_AWAY,
|
||||
}
|
||||
|
||||
HA_TO_EQ_PRESET = {
|
||||
PRESET_BOOST: eq3.Mode.Boost,
|
||||
PRESET_AWAY: eq3.Mode.Away,
|
||||
}
|
||||
|
||||
|
||||
DEVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_MAC): cv.string,
|
||||
})
|
||||
|
@ -32,8 +57,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
vol.Schema({cv.string: DEVICE_SCHEMA}),
|
||||
})
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_AWAY_MODE | SUPPORT_ON_OFF)
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
@ -42,7 +66,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
|
||||
for name, device_cfg in config[CONF_DEVICES].items():
|
||||
mac = device_cfg[CONF_MAC]
|
||||
devices.append(EQ3BTSmartThermostat(mac, name))
|
||||
devices.append(EQ3BTSmartThermostat(mac, name), True)
|
||||
|
||||
add_entities(devices)
|
||||
|
||||
|
@ -53,23 +77,8 @@ class EQ3BTSmartThermostat(ClimateDevice):
|
|||
def __init__(self, _mac, _name):
|
||||
"""Initialize the thermostat."""
|
||||
# We want to avoid name clash with this module.
|
||||
import eq3bt as eq3 # pylint: disable=import-error
|
||||
|
||||
self.modes = {
|
||||
eq3.Mode.Open: STATE_ON,
|
||||
eq3.Mode.Closed: STATE_OFF,
|
||||
eq3.Mode.Auto: STATE_HEAT,
|
||||
eq3.Mode.Manual: STATE_MANUAL,
|
||||
eq3.Mode.Boost: STATE_BOOST,
|
||||
eq3.Mode.Away: STATE_ECO,
|
||||
}
|
||||
|
||||
self.reverse_modes = {v: k for k, v in self.modes.items()}
|
||||
|
||||
self._name = _name
|
||||
self._thermostat = eq3.Thermostat(_mac)
|
||||
self._target_temperature = None
|
||||
self._target_mode = None
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
@ -79,7 +88,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
|
|||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if thermostat is available."""
|
||||
return self.current_operation is not None
|
||||
return self._thermostat.mode > 0
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -111,46 +120,25 @@ class EQ3BTSmartThermostat(ClimateDevice):
|
|||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if temperature is None:
|
||||
return
|
||||
self._target_temperature = temperature
|
||||
self._thermostat.target_temperature = temperature
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return the current operation mode."""
|
||||
if self._thermostat.mode < 0:
|
||||
return None
|
||||
return self.modes[self._thermostat.mode]
|
||||
return HVAC_MODE_OFF
|
||||
return EQ_TO_HA_HVAC[self._thermostat.mode]
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return [x for x in self.modes.values()]
|
||||
return list(HA_TO_EQ_HVAC.keys())
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set operation mode."""
|
||||
self._target_mode = operation_mode
|
||||
self._thermostat.mode = self.reverse_modes[operation_mode]
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Away mode off turns to AUTO mode."""
|
||||
self.set_operation_mode(STATE_HEAT)
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Set away mode on."""
|
||||
self.set_operation_mode(STATE_ECO)
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return if we are away."""
|
||||
return self.current_operation == STATE_ECO
|
||||
|
||||
def turn_on(self):
|
||||
"""Turn device on."""
|
||||
self.set_operation_mode(STATE_HEAT)
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn device off."""
|
||||
self.set_operation_mode(STATE_OFF)
|
||||
if self.preset_mode:
|
||||
return
|
||||
self._thermostat.mode = HA_TO_EQ_HVAC[hvac_mode]
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
|
@ -175,6 +163,28 @@ class EQ3BTSmartThermostat(ClimateDevice):
|
|||
|
||||
return dev_specific
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode, e.g., home, away, temp.
|
||||
|
||||
Requires SUPPORT_PRESET_MODE.
|
||||
"""
|
||||
return EQ_TO_HA_PRESET.get(self._thermostat.mode)
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Return a list of available preset modes.
|
||||
|
||||
Requires SUPPORT_PRESET_MODE.
|
||||
"""
|
||||
return list(HA_TO_EQ_PRESET.keys())
|
||||
|
||||
def set_preset_mode(self, preset_mode):
|
||||
"""Set new preset mode."""
|
||||
if not preset_mode:
|
||||
self.set_hvac_mode(HVAC_MODE_HEAT)
|
||||
self._thermostat.mode = HA_TO_EQ_PRESET[preset_mode]
|
||||
|
||||
def update(self):
|
||||
"""Update the data from the thermostat."""
|
||||
# pylint: disable=import-error,no-name-in-module
|
||||
|
@ -183,15 +193,3 @@ class EQ3BTSmartThermostat(ClimateDevice):
|
|||
self._thermostat.update()
|
||||
except BTLEException as ex:
|
||||
_LOGGER.warning("Updating the state failed: %s", ex)
|
||||
|
||||
if (self._target_temperature and
|
||||
self._thermostat.target_temperature
|
||||
!= self._target_temperature):
|
||||
self.set_temperature(temperature=self._target_temperature)
|
||||
else:
|
||||
self._target_temperature = None
|
||||
if (self._target_mode and
|
||||
self.modes[self._thermostat.mode] != self._target_mode):
|
||||
self.set_operation_mode(operation_mode=self._target_mode)
|
||||
else:
|
||||
self._target_mode = None
|
||||
|
|
|
@ -6,13 +6,14 @@ from aioesphomeapi import ClimateInfo, ClimateMode, ClimateState
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_AWAY_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE, PRESET_AWAY,
|
||||
HVAC_MODE_OFF)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE,
|
||||
STATE_OFF, TEMP_CELSIUS)
|
||||
TEMP_CELSIUS)
|
||||
|
||||
from . import (
|
||||
EsphomeEntity, esphome_map_enum, esphome_state_property,
|
||||
|
@ -34,10 +35,10 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
|||
@esphome_map_enum
|
||||
def _climate_modes():
|
||||
return {
|
||||
ClimateMode.OFF: STATE_OFF,
|
||||
ClimateMode.AUTO: STATE_AUTO,
|
||||
ClimateMode.COOL: STATE_COOL,
|
||||
ClimateMode.HEAT: STATE_HEAT,
|
||||
ClimateMode.OFF: HVAC_MODE_OFF,
|
||||
ClimateMode.AUTO: HVAC_MODE_HEAT_COOL,
|
||||
ClimateMode.COOL: HVAC_MODE_COOL,
|
||||
ClimateMode.HEAT: HVAC_MODE_HEAT,
|
||||
}
|
||||
|
||||
|
||||
|
@ -68,7 +69,7 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
|
|||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def operation_list(self) -> List[str]:
|
||||
def hvac_modes(self) -> List[str]:
|
||||
"""Return the list of available operation modes."""
|
||||
return [
|
||||
_climate_modes.from_esphome(mode)
|
||||
|
@ -94,18 +95,17 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
|
|||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
features = SUPPORT_OPERATION_MODE
|
||||
features = 0
|
||||
if self._static_info.supports_two_point_target_temperature:
|
||||
features |= (SUPPORT_TARGET_TEMPERATURE_LOW |
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH)
|
||||
features |= (SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||
else:
|
||||
features |= SUPPORT_TARGET_TEMPERATURE
|
||||
if self._static_info.supports_away:
|
||||
features |= SUPPORT_AWAY_MODE
|
||||
features |= SUPPORT_PRESET_MODE
|
||||
return features
|
||||
|
||||
@esphome_state_property
|
||||
def current_operation(self) -> Optional[str]:
|
||||
def hvac_mode(self) -> Optional[str]:
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return _climate_modes.from_esphome(self._state.mode)
|
||||
|
||||
|
@ -129,17 +129,12 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
|
|||
"""Return the highbound target temperature we try to reach."""
|
||||
return self._state.target_temperature_high
|
||||
|
||||
@esphome_state_property
|
||||
def is_away_mode_on(self) -> Optional[bool]:
|
||||
"""Return true if away mode is on."""
|
||||
return self._state.away
|
||||
|
||||
async def async_set_temperature(self, **kwargs) -> None:
|
||||
"""Set new target temperature (and operation mode if set)."""
|
||||
data = {'key': self._static_info.key}
|
||||
if ATTR_OPERATION_MODE in kwargs:
|
||||
if ATTR_HVAC_MODE in kwargs:
|
||||
data['mode'] = _climate_modes.from_hass(
|
||||
kwargs[ATTR_OPERATION_MODE])
|
||||
kwargs[ATTR_HVAC_MODE])
|
||||
if ATTR_TEMPERATURE in kwargs:
|
||||
data['target_temperature'] = kwargs[ATTR_TEMPERATURE]
|
||||
if ATTR_TARGET_TEMP_LOW in kwargs:
|
||||
|
@ -155,12 +150,24 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
|
|||
mode=_climate_modes.from_hass(operation_mode),
|
||||
)
|
||||
|
||||
async def async_turn_away_mode_on(self) -> None:
|
||||
"""Turn away mode on."""
|
||||
await self._client.climate_command(key=self._static_info.key,
|
||||
away=True)
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return current preset mode."""
|
||||
if self._state and self._state.away:
|
||||
return PRESET_AWAY
|
||||
|
||||
async def async_turn_away_mode_off(self) -> None:
|
||||
"""Turn away mode off."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Return preset modes."""
|
||||
if self._static_info.supports_away:
|
||||
return [PRESET_AWAY]
|
||||
|
||||
return []
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode):
|
||||
"""Set preset mode."""
|
||||
away = preset_mode == PRESET_AWAY
|
||||
await self._client.climate_command(key=self._static_info.key,
|
||||
away=False)
|
||||
away=away)
|
||||
|
|
|
@ -1,38 +1,39 @@
|
|||
"""Support for (EMEA/EU-based) Honeywell evohome systems."""
|
||||
# Glossary:
|
||||
# TCS - temperature control system (a.k.a. Controller, Parent), which can
|
||||
# have up to 13 Children:
|
||||
# 0-12 Heating zones (a.k.a. Zone), and
|
||||
# 0-1 DHW controller, (a.k.a. Boiler)
|
||||
# The TCS & Zones are implemented as Climate devices, Boiler as a WaterHeater
|
||||
"""Support for (EMEA/EU-based) Honeywell TCC climate systems.
|
||||
|
||||
Such systems include evohome (multi-zone), and Round Thermostat (single zone).
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
from dateutil.tz import tzlocal
|
||||
import requests.exceptions
|
||||
import voluptuous as vol
|
||||
|
||||
import evohomeclient2
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_SCAN_INTERVAL, CONF_USERNAME, CONF_PASSWORD,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
HTTP_SERVICE_UNAVAILABLE, HTTP_TOO_MANY_REQUESTS,
|
||||
PRECISION_HALVES, TEMP_CELSIUS)
|
||||
CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME,
|
||||
HTTP_SERVICE_UNAVAILABLE, HTTP_TOO_MANY_REQUESTS, TEMP_CELSIUS)
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.discovery import load_platform
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect, async_dispatcher_send)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.event import (
|
||||
async_track_point_in_utc_time, async_track_time_interval)
|
||||
from homeassistant.util.dt import as_utc, parse_datetime, utcnow
|
||||
|
||||
from .const import (
|
||||
DOMAIN, DATA_EVOHOME, DISPATCHER_EVOHOME, GWS, TCS)
|
||||
from .const import DOMAIN, EVO_STRFTIME, STORAGE_VERSION, STORAGE_KEY, GWS, TCS
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_ACCESS_TOKEN_EXPIRES = 'access_token_expires'
|
||||
CONF_REFRESH_TOKEN = 'refresh_token'
|
||||
|
||||
CONF_LOCATION_IDX = 'location_idx'
|
||||
SCAN_INTERVAL_DEFAULT = timedelta(seconds=300)
|
||||
SCAN_INTERVAL_MINIMUM = timedelta(seconds=180)
|
||||
SCAN_INTERVAL_MINIMUM = timedelta(seconds=60)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
|
@ -44,229 +45,314 @@ CONFIG_SCHEMA = vol.Schema({
|
|||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
CONF_SECRETS = [
|
||||
CONF_USERNAME, CONF_PASSWORD,
|
||||
]
|
||||
|
||||
# bit masks for dispatcher packets
|
||||
EVO_PARENT = 0x01
|
||||
EVO_CHILD = 0x02
|
||||
def _local_dt_to_utc(dt_naive: datetime) -> datetime:
|
||||
dt_aware = as_utc(dt_naive.replace(microsecond=0, tzinfo=tzlocal()))
|
||||
return dt_aware.replace(tzinfo=None)
|
||||
|
||||
|
||||
def setup(hass, hass_config):
|
||||
"""Create a (EMEA/EU-based) Honeywell evohome system.
|
||||
|
||||
Currently, only the Controller and the Zones are implemented here.
|
||||
"""
|
||||
evo_data = hass.data[DATA_EVOHOME] = {}
|
||||
evo_data['timers'] = {}
|
||||
|
||||
# use a copy, since scan_interval is rounded up to nearest 60s
|
||||
evo_data['params'] = dict(hass_config[DOMAIN])
|
||||
scan_interval = evo_data['params'][CONF_SCAN_INTERVAL]
|
||||
scan_interval = timedelta(
|
||||
minutes=(scan_interval.total_seconds() + 59) // 60)
|
||||
|
||||
def _handle_exception(err):
|
||||
try:
|
||||
client = evo_data['client'] = evohomeclient2.EvohomeClient(
|
||||
evo_data['params'][CONF_USERNAME],
|
||||
evo_data['params'][CONF_PASSWORD],
|
||||
debug=False
|
||||
)
|
||||
raise err
|
||||
|
||||
except evohomeclient2.AuthenticationError as err:
|
||||
except evohomeclient2.AuthenticationError:
|
||||
_LOGGER.error(
|
||||
"setup(): Failed to authenticate with the vendor's server. "
|
||||
"Check your username and password are correct. "
|
||||
"Resolve any errors and restart HA. Message is: %s",
|
||||
"Failed to (re)authenticate with the vendor's server. "
|
||||
"Check that your username and password are correct. "
|
||||
"Message is: %s",
|
||||
err
|
||||
)
|
||||
return False
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error(
|
||||
"setup(): Unable to connect with the vendor's server. "
|
||||
"Check your network and the vendor's status page. "
|
||||
"Resolve any errors and restart HA."
|
||||
# this appears to be common with Honeywell's servers
|
||||
_LOGGER.warning(
|
||||
"Unable to connect with the vendor's server. "
|
||||
"Check your network and the vendor's status page."
|
||||
"Message is: %s",
|
||||
err
|
||||
)
|
||||
return False
|
||||
|
||||
finally: # Redact any config data that's no longer needed
|
||||
for parameter in CONF_SECRETS:
|
||||
evo_data['params'][parameter] = 'REDACTED' \
|
||||
if evo_data['params'][parameter] else None
|
||||
except requests.exceptions.HTTPError:
|
||||
if err.response.status_code == HTTP_SERVICE_UNAVAILABLE:
|
||||
_LOGGER.warning(
|
||||
"Vendor says their server is currently unavailable. "
|
||||
"Check the vendor's status page."
|
||||
)
|
||||
return False
|
||||
|
||||
evo_data['status'] = {}
|
||||
if err.response.status_code == HTTP_TOO_MANY_REQUESTS:
|
||||
_LOGGER.warning(
|
||||
"The vendor's API rate limit has been exceeded. "
|
||||
"Consider increasing the %s.", CONF_SCAN_INTERVAL
|
||||
)
|
||||
return False
|
||||
|
||||
# Redact any installation data that's no longer needed
|
||||
for loc in client.installation_info:
|
||||
loc['locationInfo']['locationId'] = 'REDACTED'
|
||||
loc['locationInfo']['locationOwner'] = 'REDACTED'
|
||||
loc['locationInfo']['streetAddress'] = 'REDACTED'
|
||||
loc['locationInfo']['city'] = 'REDACTED'
|
||||
loc[GWS][0]['gatewayInfo'] = 'REDACTED'
|
||||
raise # we don't expect/handle any other HTTPErrors
|
||||
|
||||
# Pull down the installation configuration
|
||||
loc_idx = evo_data['params'][CONF_LOCATION_IDX]
|
||||
try:
|
||||
evo_data['config'] = client.installation_info[loc_idx]
|
||||
|
||||
except IndexError:
|
||||
_LOGGER.error(
|
||||
"setup(): config error, '%s' = %s, but its valid range is 0-%s. "
|
||||
"Unable to continue. Fix any configuration errors and restart HA.",
|
||||
CONF_LOCATION_IDX, loc_idx, len(client.installation_info) - 1
|
||||
)
|
||||
async def async_setup(hass, hass_config):
|
||||
"""Create a (EMEA/EU-based) Honeywell evohome system."""
|
||||
broker = EvoBroker(hass, hass_config[DOMAIN])
|
||||
if not await broker.init_client():
|
||||
return False
|
||||
|
||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||
tmp_loc = dict(evo_data['config'])
|
||||
tmp_loc['locationInfo']['postcode'] = 'REDACTED'
|
||||
|
||||
if 'dhw' in tmp_loc[GWS][0][TCS][0]: # if this location has DHW...
|
||||
tmp_loc[GWS][0][TCS][0]['dhw'] = '...'
|
||||
|
||||
_LOGGER.debug("setup(): evo_data['config']=%s", tmp_loc)
|
||||
|
||||
load_platform(hass, 'climate', DOMAIN, {}, hass_config)
|
||||
if broker.tcs.hotwater:
|
||||
_LOGGER.warning("DHW controller detected, however this integration "
|
||||
"does not currently support DHW controllers.")
|
||||
|
||||
if 'dhw' in evo_data['config'][GWS][0][TCS][0]:
|
||||
_LOGGER.warning(
|
||||
"setup(): DHW found, but this component doesn't support DHW."
|
||||
)
|
||||
|
||||
@callback
|
||||
def _first_update(event):
|
||||
"""When HA has started, the hub knows to retrieve it's first update."""
|
||||
pkt = {'sender': 'setup()', 'signal': 'refresh', 'to': EVO_PARENT}
|
||||
async_dispatcher_send(hass, DISPATCHER_EVOHOME, pkt)
|
||||
|
||||
hass.bus.listen(EVENT_HOMEASSISTANT_START, _first_update)
|
||||
async_track_time_interval(
|
||||
hass, broker.update, hass_config[DOMAIN][CONF_SCAN_INTERVAL]
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class EvoDevice(Entity):
|
||||
"""Base for any Honeywell evohome device.
|
||||
class EvoBroker:
|
||||
"""Container for evohome client and data."""
|
||||
|
||||
Such devices include the Controller, (up to 12) Heating Zones and
|
||||
def __init__(self, hass, params) -> None:
|
||||
"""Initialize the evohome client and data structure."""
|
||||
self.hass = hass
|
||||
self.params = params
|
||||
|
||||
self.config = self.status = self.timers = {}
|
||||
|
||||
self.client = self.tcs = None
|
||||
self._app_storage = None
|
||||
|
||||
hass.data[DOMAIN] = {}
|
||||
hass.data[DOMAIN]['broker'] = self
|
||||
|
||||
async def init_client(self) -> bool:
|
||||
"""Initialse the evohome data broker.
|
||||
|
||||
Return True if this is successful, otherwise return False.
|
||||
"""
|
||||
refresh_token, access_token, access_token_expires = \
|
||||
await self._load_auth_tokens()
|
||||
|
||||
try:
|
||||
client = self.client = await self.hass.async_add_executor_job(
|
||||
evohomeclient2.EvohomeClient,
|
||||
self.params[CONF_USERNAME],
|
||||
self.params[CONF_PASSWORD],
|
||||
False,
|
||||
refresh_token,
|
||||
access_token,
|
||||
access_token_expires
|
||||
)
|
||||
|
||||
except (requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError) as err:
|
||||
if not _handle_exception(err):
|
||||
return False
|
||||
|
||||
else:
|
||||
if access_token != self.client.access_token:
|
||||
await self._save_auth_tokens()
|
||||
|
||||
finally:
|
||||
self.params[CONF_PASSWORD] = 'REDACTED'
|
||||
|
||||
loc_idx = self.params[CONF_LOCATION_IDX]
|
||||
try:
|
||||
self.config = client.installation_info[loc_idx][GWS][0][TCS][0]
|
||||
|
||||
except IndexError:
|
||||
_LOGGER.error(
|
||||
"Config error: '%s' = %s, but its valid range is 0-%s. "
|
||||
"Unable to continue. "
|
||||
"Fix any configuration errors and restart HA.",
|
||||
CONF_LOCATION_IDX, loc_idx, len(client.installation_info) - 1
|
||||
)
|
||||
return False
|
||||
|
||||
else:
|
||||
self.tcs = \
|
||||
client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa: E501; pylint: disable=protected-access
|
||||
|
||||
_LOGGER.debug("Config = %s", self.config)
|
||||
|
||||
return True
|
||||
|
||||
async def _load_auth_tokens(self) -> Tuple[str, str, datetime]:
|
||||
store = self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
||||
app_storage = self._app_storage = await store.async_load()
|
||||
|
||||
if app_storage.get(CONF_USERNAME) == self.params[CONF_USERNAME]:
|
||||
refresh_token = app_storage.get(CONF_REFRESH_TOKEN)
|
||||
access_token = app_storage.get(CONF_ACCESS_TOKEN)
|
||||
at_expires_str = app_storage.get(CONF_ACCESS_TOKEN_EXPIRES)
|
||||
if at_expires_str:
|
||||
at_expires_dt = as_utc(parse_datetime(at_expires_str))
|
||||
at_expires_dt = at_expires_dt.astimezone(tzlocal())
|
||||
at_expires_dt = at_expires_dt.replace(tzinfo=None)
|
||||
else:
|
||||
at_expires_dt = None
|
||||
|
||||
return (refresh_token, access_token, at_expires_dt)
|
||||
|
||||
return (None, None, None) # account switched: so tokens wont be valid
|
||||
|
||||
async def _save_auth_tokens(self, *args) -> None:
|
||||
access_token_expires_utc = _local_dt_to_utc(
|
||||
self.client.access_token_expires)
|
||||
|
||||
self._app_storage[CONF_USERNAME] = self.params[CONF_USERNAME]
|
||||
self._app_storage[CONF_REFRESH_TOKEN] = self.client.refresh_token
|
||||
self._app_storage[CONF_ACCESS_TOKEN] = self.client.access_token
|
||||
self._app_storage[CONF_ACCESS_TOKEN_EXPIRES] = \
|
||||
access_token_expires_utc.isoformat()
|
||||
|
||||
store = self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
||||
await store.async_save(self._app_storage)
|
||||
|
||||
async_track_point_in_utc_time(
|
||||
self.hass,
|
||||
self._save_auth_tokens,
|
||||
access_token_expires_utc
|
||||
)
|
||||
|
||||
def update(self, *args, **kwargs) -> None:
|
||||
"""Get the latest state data of the entire evohome Location.
|
||||
|
||||
This includes state data for the Controller and all its child devices,
|
||||
such as the operating mode of the Controller and the current temp of
|
||||
its children (e.g. Zones, DHW controller).
|
||||
"""
|
||||
loc_idx = self.params[CONF_LOCATION_IDX]
|
||||
|
||||
try:
|
||||
status = self.client.locations[loc_idx].status()[GWS][0][TCS][0]
|
||||
except (requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError) as err:
|
||||
_handle_exception(err)
|
||||
else:
|
||||
self.timers['statusUpdated'] = utcnow()
|
||||
|
||||
_LOGGER.debug("Status = %s", status)
|
||||
|
||||
# inform the evohome devices that state data has been updated
|
||||
async_dispatcher_send(self.hass, DOMAIN, {'signal': 'refresh'})
|
||||
|
||||
|
||||
class EvoDevice(Entity):
|
||||
"""Base for any evohome device.
|
||||
|
||||
This includes the Controller, (up to 12) Heating Zones and
|
||||
(optionally) a DHW controller.
|
||||
"""
|
||||
|
||||
def __init__(self, evo_data, client, obj_ref):
|
||||
def __init__(self, evo_broker, evo_device) -> None:
|
||||
"""Initialize the evohome entity."""
|
||||
self._client = client
|
||||
self._obj = obj_ref
|
||||
self._evo_device = evo_device
|
||||
self._evo_tcs = evo_broker.tcs
|
||||
|
||||
self._name = None
|
||||
self._icon = None
|
||||
self._type = None
|
||||
self._name = self._icon = self._precision = None
|
||||
self._state_attributes = []
|
||||
|
||||
self._supported_features = None
|
||||
self._operation_list = None
|
||||
|
||||
self._params = evo_data['params']
|
||||
self._timers = evo_data['timers']
|
||||
self._status = {}
|
||||
|
||||
self._available = False # should become True after first update()
|
||||
self._setpoints = None
|
||||
|
||||
@callback
|
||||
def _connect(self, packet):
|
||||
if packet['to'] & self._type and packet['signal'] == 'refresh':
|
||||
def _refresh(self, packet):
|
||||
if packet['signal'] == 'refresh':
|
||||
self.async_schedule_update_ha_state(force_refresh=True)
|
||||
|
||||
def _handle_exception(self, err):
|
||||
try:
|
||||
raise err
|
||||
def get_setpoints(self) -> Dict[str, Any]:
|
||||
"""Return the current/next scheduled switchpoints.
|
||||
|
||||
except evohomeclient2.AuthenticationError:
|
||||
_LOGGER.error(
|
||||
"Failed to (re)authenticate with the vendor's server. "
|
||||
"This may be a temporary error. Message is: %s",
|
||||
err
|
||||
)
|
||||
Only Zones & DHW controllers (but not the TCS) have schedules.
|
||||
"""
|
||||
switchpoints = {}
|
||||
schedule = self._evo_device.schedule()
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
# this appears to be common with Honeywell's servers
|
||||
_LOGGER.warning(
|
||||
"Unable to connect with the vendor's server. "
|
||||
"Check your network and the vendor's status page."
|
||||
)
|
||||
|
||||
except requests.exceptions.HTTPError:
|
||||
if err.response.status_code == HTTP_SERVICE_UNAVAILABLE:
|
||||
_LOGGER.warning(
|
||||
"Vendor says their server is currently unavailable. "
|
||||
"This may be temporary; check the vendor's status page."
|
||||
)
|
||||
|
||||
elif err.response.status_code == HTTP_TOO_MANY_REQUESTS:
|
||||
_LOGGER.warning(
|
||||
"The vendor's API rate limit has been exceeded. "
|
||||
"So will cease polling, and will resume after %s seconds.",
|
||||
(self._params[CONF_SCAN_INTERVAL] * 3).total_seconds()
|
||||
)
|
||||
self._timers['statusUpdated'] = datetime.now() + \
|
||||
self._params[CONF_SCAN_INTERVAL] * 3
|
||||
day_time = datetime.now()
|
||||
day_of_week = int(day_time.strftime('%w')) # 0 is Sunday
|
||||
|
||||
# Iterate today's switchpoints until past the current time of day...
|
||||
day = schedule['DailySchedules'][day_of_week]
|
||||
sp_idx = -1 # last switchpoint of the day before
|
||||
for i, tmp in enumerate(day['Switchpoints']):
|
||||
if day_time.strftime('%H:%M:%S') > tmp['TimeOfDay']:
|
||||
sp_idx = i # current setpoint
|
||||
else:
|
||||
raise # we don't expect/handle any other HTTPErrors
|
||||
break
|
||||
|
||||
# These properties, methods are from the Entity class
|
||||
async def async_added_to_hass(self):
|
||||
"""Run when entity about to be added."""
|
||||
async_dispatcher_connect(self.hass, DISPATCHER_EVOHOME, self._connect)
|
||||
# Did the current SP start yesterday? Does the next start SP tomorrow?
|
||||
current_sp_day = -1 if sp_idx == -1 else 0
|
||||
next_sp_day = 1 if sp_idx + 1 == len(day['Switchpoints']) else 0
|
||||
|
||||
for key, offset, idx in [
|
||||
('current', current_sp_day, sp_idx),
|
||||
('next', next_sp_day, (sp_idx + 1) * (1 - next_sp_day))]:
|
||||
|
||||
spt = switchpoints[key] = {}
|
||||
|
||||
sp_date = (day_time + timedelta(days=offset)).strftime('%Y-%m-%d')
|
||||
day = schedule['DailySchedules'][(day_of_week + offset) % 7]
|
||||
switchpoint = day['Switchpoints'][idx]
|
||||
|
||||
dt_naive = datetime.strptime(
|
||||
'{}T{}'.format(sp_date, switchpoint['TimeOfDay']),
|
||||
'%Y-%m-%dT%H:%M:%S')
|
||||
|
||||
spt['target_temp'] = switchpoint['heatSetpoint']
|
||||
spt['from_datetime'] = \
|
||||
_local_dt_to_utc(dt_naive).strftime(EVO_STRFTIME)
|
||||
|
||||
return switchpoints
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Most evohome devices push their state to HA.
|
||||
|
||||
Only the Controller should be polled.
|
||||
"""
|
||||
"""Evohome entities should not be polled."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name to use in the frontend UI."""
|
||||
"""Return the name of the Evohome entity."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device state attributes of the evohome device.
|
||||
def device_state_attributes(self) -> Dict[str, Any]:
|
||||
"""Return the Evohome-specific state attributes."""
|
||||
status = {}
|
||||
for attr in self._state_attributes:
|
||||
if attr != 'setpoints':
|
||||
status[attr] = getattr(self._evo_device, attr)
|
||||
|
||||
This is state data that is not available otherwise, due to the
|
||||
restrictions placed upon ClimateDevice properties, etc. by HA.
|
||||
"""
|
||||
return {'status': self._status}
|
||||
if 'setpoints' in self._state_attributes:
|
||||
status['setpoints'] = self._setpoints
|
||||
|
||||
return {'status': status}
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
def icon(self) -> str:
|
||||
"""Return the icon to use in the frontend UI."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if the device is currently available."""
|
||||
return self._available
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Get the list of supported features of the device."""
|
||||
def supported_features(self) -> int:
|
||||
"""Get the flag of supported features of the device."""
|
||||
return self._supported_features
|
||||
|
||||
# These properties are common to ClimateDevice, WaterHeaterDevice classes
|
||||
@property
|
||||
def precision(self):
|
||||
"""Return the temperature precision to use in the frontend UI."""
|
||||
return PRECISION_HALVES
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Run when entity about to be added to hass."""
|
||||
async_dispatcher_connect(self.hass, DOMAIN, self._refresh)
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
def precision(self) -> float:
|
||||
"""Return the temperature precision to use in the frontend UI."""
|
||||
return self._precision
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the temperature unit to use in the frontend UI."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the list of available operations."""
|
||||
return self._operation_list
|
||||
def update(self) -> None:
|
||||
"""Get the latest state data."""
|
||||
self._setpoints = self.get_setpoints()
|
||||
|
|
|
@ -1,457 +1,331 @@
|
|||
"""Support for Climate devices of (EMEA/EU-based) Honeywell evohome systems."""
|
||||
from datetime import datetime, timedelta
|
||||
"""Support for Climate devices of (EMEA/EU-based) Honeywell TCC systems."""
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from typing import Optional, List
|
||||
|
||||
import requests.exceptions
|
||||
|
||||
import evohomeclient2
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_ECO, STATE_MANUAL, SUPPORT_AWAY_MODE, SUPPORT_ON_OFF,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
CONF_SCAN_INTERVAL, STATE_OFF,)
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF,
|
||||
PRESET_AWAY, PRESET_ECO, PRESET_HOME,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE)
|
||||
|
||||
from . import (
|
||||
EvoDevice,
|
||||
CONF_LOCATION_IDX, EVO_CHILD, EVO_PARENT)
|
||||
from . import CONF_LOCATION_IDX, _handle_exception, EvoDevice
|
||||
from .const import (
|
||||
DATA_EVOHOME, DISPATCHER_EVOHOME, GWS, TCS)
|
||||
DOMAIN, EVO_STRFTIME,
|
||||
EVO_RESET, EVO_AUTO, EVO_AUTOECO, EVO_AWAY, EVO_DAYOFF, EVO_CUSTOM,
|
||||
EVO_HEATOFF, EVO_FOLLOW, EVO_TEMPOVER, EVO_PERMOVER)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# The Controller's opmode/state and the zone's (inherited) state
|
||||
EVO_RESET = 'AutoWithReset'
|
||||
EVO_AUTO = 'Auto'
|
||||
EVO_AUTOECO = 'AutoWithEco'
|
||||
EVO_AWAY = 'Away'
|
||||
EVO_DAYOFF = 'DayOff'
|
||||
EVO_CUSTOM = 'Custom'
|
||||
EVO_HEATOFF = 'HeatingOff'
|
||||
PRESET_RESET = 'Reset' # reset all child zones to EVO_FOLLOW
|
||||
PRESET_CUSTOM = 'Custom'
|
||||
|
||||
# These are for Zones' opmode, and state
|
||||
EVO_FOLLOW = 'FollowSchedule'
|
||||
EVO_TEMPOVER = 'TemporaryOverride'
|
||||
EVO_PERMOVER = 'PermanentOverride'
|
||||
HA_HVAC_TO_TCS = {
|
||||
HVAC_MODE_OFF: EVO_HEATOFF,
|
||||
HVAC_MODE_HEAT: EVO_AUTO,
|
||||
}
|
||||
HA_PRESET_TO_TCS = {
|
||||
PRESET_AWAY: EVO_AWAY,
|
||||
PRESET_CUSTOM: EVO_CUSTOM,
|
||||
PRESET_ECO: EVO_AUTOECO,
|
||||
PRESET_HOME: EVO_DAYOFF,
|
||||
PRESET_RESET: EVO_RESET,
|
||||
}
|
||||
TCS_PRESET_TO_HA = {v: k for k, v in HA_PRESET_TO_TCS.items()}
|
||||
|
||||
# For the Controller. NB: evohome treats Away mode as a mode in/of itself,
|
||||
# where HA considers it to 'override' the exising operating mode
|
||||
TCS_STATE_TO_HA = {
|
||||
EVO_RESET: STATE_AUTO,
|
||||
EVO_AUTO: STATE_AUTO,
|
||||
EVO_AUTOECO: STATE_ECO,
|
||||
EVO_AWAY: STATE_AUTO,
|
||||
EVO_DAYOFF: STATE_AUTO,
|
||||
EVO_CUSTOM: STATE_AUTO,
|
||||
EVO_HEATOFF: STATE_OFF
|
||||
HA_PRESET_TO_EVO = {
|
||||
'temporary': EVO_TEMPOVER,
|
||||
'permanent': EVO_PERMOVER,
|
||||
}
|
||||
HA_STATE_TO_TCS = {
|
||||
STATE_AUTO: EVO_AUTO,
|
||||
STATE_ECO: EVO_AUTOECO,
|
||||
STATE_OFF: EVO_HEATOFF
|
||||
}
|
||||
TCS_OP_LIST = list(HA_STATE_TO_TCS)
|
||||
|
||||
# the Zones' opmode; their state is usually 'inherited' from the TCS
|
||||
EVO_FOLLOW = 'FollowSchedule'
|
||||
EVO_TEMPOVER = 'TemporaryOverride'
|
||||
EVO_PERMOVER = 'PermanentOverride'
|
||||
|
||||
# for the Zones...
|
||||
ZONE_STATE_TO_HA = {
|
||||
EVO_FOLLOW: STATE_AUTO,
|
||||
EVO_TEMPOVER: STATE_MANUAL,
|
||||
EVO_PERMOVER: STATE_MANUAL
|
||||
}
|
||||
HA_STATE_TO_ZONE = {
|
||||
STATE_AUTO: EVO_FOLLOW,
|
||||
STATE_MANUAL: EVO_PERMOVER
|
||||
}
|
||||
ZONE_OP_LIST = list(HA_STATE_TO_ZONE)
|
||||
EVO_PRESET_TO_HA = {v: k for k, v in HA_PRESET_TO_EVO.items()}
|
||||
|
||||
|
||||
async def async_setup_platform(hass, hass_config, async_add_entities,
|
||||
discovery_info=None):
|
||||
discovery_info=None) -> None:
|
||||
"""Create the evohome Controller, and its Zones, if any."""
|
||||
evo_data = hass.data[DATA_EVOHOME]
|
||||
|
||||
client = evo_data['client']
|
||||
loc_idx = evo_data['params'][CONF_LOCATION_IDX]
|
||||
|
||||
# evohomeclient has exposed no means of accessing non-default location
|
||||
# (i.e. loc_idx > 0) other than using a protected member, such as below
|
||||
tcs_obj_ref = client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa: E501; pylint: disable=protected-access
|
||||
broker = hass.data[DOMAIN]['broker']
|
||||
loc_idx = broker.params[CONF_LOCATION_IDX]
|
||||
|
||||
_LOGGER.debug(
|
||||
"Found Controller, id=%s [%s], name=%s (location_idx=%s)",
|
||||
tcs_obj_ref.systemId, tcs_obj_ref.modelType, tcs_obj_ref.location.name,
|
||||
broker.tcs.systemId, broker.tcs.modelType, broker.tcs.location.name,
|
||||
loc_idx)
|
||||
|
||||
controller = EvoController(evo_data, client, tcs_obj_ref)
|
||||
zones = []
|
||||
controller = EvoController(broker, broker.tcs)
|
||||
|
||||
for zone_idx in tcs_obj_ref.zones:
|
||||
zone_obj_ref = tcs_obj_ref.zones[zone_idx]
|
||||
zones = []
|
||||
for zone_idx in broker.tcs.zones:
|
||||
evo_zone = broker.tcs.zones[zone_idx]
|
||||
_LOGGER.debug(
|
||||
"Found Zone, id=%s [%s], name=%s",
|
||||
zone_obj_ref.zoneId, zone_obj_ref.zone_type, zone_obj_ref.name)
|
||||
zones.append(EvoZone(evo_data, client, zone_obj_ref))
|
||||
evo_zone.zoneId, evo_zone.zone_type, evo_zone.name)
|
||||
zones.append(EvoZone(broker, evo_zone))
|
||||
|
||||
entities = [controller] + zones
|
||||
|
||||
async_add_entities(entities, update_before_add=False)
|
||||
async_add_entities(entities, update_before_add=True)
|
||||
|
||||
|
||||
class EvoZone(EvoDevice, ClimateDevice):
|
||||
"""Base for a Honeywell evohome Zone device."""
|
||||
class EvoClimateDevice(EvoDevice, ClimateDevice):
|
||||
"""Base for a Honeywell evohome Climate device."""
|
||||
|
||||
def __init__(self, evo_data, client, obj_ref):
|
||||
def __init__(self, evo_broker, evo_device) -> None:
|
||||
"""Initialize the evohome Climate device."""
|
||||
super().__init__(evo_broker, evo_device)
|
||||
|
||||
self._hvac_modes = self._preset_modes = None
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> List[str]:
|
||||
"""Return the list of available hvac operation modes."""
|
||||
return self._hvac_modes
|
||||
|
||||
@property
|
||||
def preset_modes(self) -> Optional[List[str]]:
|
||||
"""Return a list of available preset modes."""
|
||||
return self._preset_modes
|
||||
|
||||
|
||||
class EvoZone(EvoClimateDevice):
|
||||
"""Base for a Honeywell evohome Zone."""
|
||||
|
||||
def __init__(self, evo_broker, evo_device) -> None:
|
||||
"""Initialize the evohome Zone."""
|
||||
super().__init__(evo_data, client, obj_ref)
|
||||
super().__init__(evo_broker, evo_device)
|
||||
|
||||
self._id = obj_ref.zoneId
|
||||
self._name = obj_ref.name
|
||||
self._icon = "mdi:radiator"
|
||||
self._type = EVO_CHILD
|
||||
self._id = evo_device.zoneId
|
||||
self._name = evo_device.name
|
||||
self._icon = 'mdi:radiator'
|
||||
|
||||
for _zone in evo_data['config'][GWS][0][TCS][0]['zones']:
|
||||
self._precision = \
|
||||
self._evo_device.setpointCapabilities['valueResolution']
|
||||
self._state_attributes = [
|
||||
'activeFaults', 'setpointStatus', 'temperatureStatus', 'setpoints']
|
||||
|
||||
self._supported_features = SUPPORT_PRESET_MODE | \
|
||||
SUPPORT_TARGET_TEMPERATURE
|
||||
self._hvac_modes = [HVAC_MODE_OFF, HVAC_MODE_HEAT]
|
||||
self._preset_modes = list(HA_PRESET_TO_EVO)
|
||||
|
||||
for _zone in evo_broker.config['zones']:
|
||||
if _zone['zoneId'] == self._id:
|
||||
self._config = _zone
|
||||
break
|
||||
self._status = {}
|
||||
|
||||
self._operation_list = ZONE_OP_LIST
|
||||
self._supported_features = \
|
||||
SUPPORT_OPERATION_MODE | \
|
||||
SUPPORT_TARGET_TEMPERATURE | \
|
||||
SUPPORT_ON_OFF
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return the current operating mode of the evohome Zone.
|
||||
|
||||
The evohome Zones that are in 'FollowSchedule' mode inherit their
|
||||
actual operating mode from the Controller.
|
||||
"""
|
||||
evo_data = self.hass.data[DATA_EVOHOME]
|
||||
NB: evohome Zones 'inherit' their operating mode from the controller.
|
||||
|
||||
system_mode = evo_data['status']['systemModeStatus']['mode']
|
||||
setpoint_mode = self._status['setpointStatus']['setpointMode']
|
||||
|
||||
if setpoint_mode == EVO_FOLLOW:
|
||||
# then inherit state from the controller
|
||||
if system_mode == EVO_RESET:
|
||||
current_operation = TCS_STATE_TO_HA.get(EVO_AUTO)
|
||||
else:
|
||||
current_operation = TCS_STATE_TO_HA.get(system_mode)
|
||||
else:
|
||||
current_operation = ZONE_STATE_TO_HA.get(setpoint_mode)
|
||||
|
||||
return current_operation
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature of the evohome Zone."""
|
||||
return (self._status['temperatureStatus']['temperature']
|
||||
if self._status['temperatureStatus']['isAvailable'] else None)
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the target temperature of the evohome Zone."""
|
||||
return self._status['setpointStatus']['targetHeatTemperature']
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if the evohome Zone is off.
|
||||
|
||||
A Zone is considered off if its target temp is set to its minimum, and
|
||||
it is not following its schedule (i.e. not in 'FollowSchedule' mode).
|
||||
"""
|
||||
is_off = \
|
||||
self.target_temperature == self.min_temp and \
|
||||
self._status['setpointStatus']['setpointMode'] == EVO_PERMOVER
|
||||
return not is_off
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum target temperature of a evohome Zone.
|
||||
|
||||
The default is 5 (in Celsius), but it is configurable within 5-35.
|
||||
"""
|
||||
return self._config['setpointCapabilities']['minHeatSetpoint']
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum target temperature of a evohome Zone.
|
||||
|
||||
The default is 35 (in Celsius), but it is configurable within 5-35.
|
||||
"""
|
||||
return self._config['setpointCapabilities']['maxHeatSetpoint']
|
||||
|
||||
def _set_temperature(self, temperature, until=None):
|
||||
"""Set the new target temperature of a Zone.
|
||||
|
||||
temperature is required, until can be:
|
||||
- strftime('%Y-%m-%dT%H:%M:%SZ') for TemporaryOverride, or
|
||||
- None for PermanentOverride (i.e. indefinitely)
|
||||
"""
|
||||
try:
|
||||
self._obj.set_temperature(temperature, until)
|
||||
except (requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError) as err:
|
||||
self._handle_exception(err)
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature, indefinitely."""
|
||||
self._set_temperature(kwargs['temperature'], until=None)
|
||||
|
||||
def turn_on(self):
|
||||
"""Turn the evohome Zone on.
|
||||
|
||||
This is achieved by setting the Zone to its 'FollowSchedule' mode.
|
||||
"""
|
||||
self._set_operation_mode(EVO_FOLLOW)
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn the evohome Zone off.
|
||||
|
||||
This is achieved by setting the Zone to its minimum temperature,
|
||||
indefinitely (i.e. 'PermanentOverride' mode).
|
||||
"""
|
||||
self._set_temperature(self.min_temp, until=None)
|
||||
|
||||
def _set_operation_mode(self, operation_mode):
|
||||
if operation_mode == EVO_FOLLOW:
|
||||
try:
|
||||
self._obj.cancel_temp_override()
|
||||
except (requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError) as err:
|
||||
self._handle_exception(err)
|
||||
|
||||
elif operation_mode == EVO_TEMPOVER:
|
||||
_LOGGER.error(
|
||||
"_set_operation_mode(op_mode=%s): mode not yet implemented",
|
||||
operation_mode
|
||||
)
|
||||
|
||||
elif operation_mode == EVO_PERMOVER:
|
||||
self._set_temperature(self.target_temperature, until=None)
|
||||
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"_set_operation_mode(op_mode=%s): mode not valid",
|
||||
operation_mode
|
||||
)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set an operating mode for a Zone.
|
||||
|
||||
Currently limited to 'Auto' & 'Manual'. If 'Off' is needed, it can be
|
||||
enabled via turn_off method.
|
||||
|
||||
NB: evohome Zones do not have an operating mode as understood by HA.
|
||||
Instead they usually 'inherit' an operating mode from their controller.
|
||||
|
||||
More correctly, these Zones are in a follow mode, 'FollowSchedule',
|
||||
where their setpoint temperatures are a function of their schedule, and
|
||||
the Controller's operating_mode, e.g. Economy mode is their scheduled
|
||||
setpoint less (usually) 3C.
|
||||
|
||||
Thus, you cannot set a Zone to Away mode, but the location (i.e. the
|
||||
Controller) is set to Away and each Zones's setpoints are adjusted
|
||||
accordingly to some lower temperature.
|
||||
Usually, Zones are in 'FollowSchedule' mode, where their setpoints are
|
||||
a function of their schedule, and the Controller's operating_mode, e.g.
|
||||
Economy mode is their scheduled setpoint less (usually) 3C.
|
||||
|
||||
However, Zones can override these setpoints, either for a specified
|
||||
period of time, 'TemporaryOverride', after which they will revert back
|
||||
to 'FollowSchedule' mode, or indefinitely, 'PermanentOverride'.
|
||||
"""
|
||||
self._set_operation_mode(HA_STATE_TO_ZONE.get(operation_mode))
|
||||
if self._evo_tcs.systemModeStatus['mode'] in [EVO_AWAY, EVO_HEATOFF]:
|
||||
return HVAC_MODE_AUTO
|
||||
is_off = self.target_temperature <= self.min_temp
|
||||
return HVAC_MODE_OFF if is_off else HVAC_MODE_HEAT
|
||||
|
||||
def update(self):
|
||||
"""Process the evohome Zone's state data."""
|
||||
evo_data = self.hass.data[DATA_EVOHOME]
|
||||
@property
|
||||
def current_temperature(self) -> Optional[float]:
|
||||
"""Return the current temperature of the evohome Zone."""
|
||||
return (self._evo_device.temperatureStatus['temperature']
|
||||
if self._evo_device.temperatureStatus['isAvailable'] else None)
|
||||
|
||||
for _zone in evo_data['status']['zones']:
|
||||
if _zone['zoneId'] == self._id:
|
||||
self._status = _zone
|
||||
break
|
||||
@property
|
||||
def target_temperature(self) -> Optional[float]:
|
||||
"""Return the target temperature of the evohome Zone."""
|
||||
if self._evo_tcs.systemModeStatus['mode'] == EVO_HEATOFF:
|
||||
return self._evo_device.setpointCapabilities['minHeatSetpoint']
|
||||
return self._evo_device.setpointStatus['targetHeatTemperature']
|
||||
|
||||
self._available = True
|
||||
@property
|
||||
def preset_mode(self) -> Optional[str]:
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
if self._evo_tcs.systemModeStatus['mode'] in [EVO_AWAY, EVO_HEATOFF]:
|
||||
return None
|
||||
return EVO_PRESET_TO_HA.get(
|
||||
self._evo_device.setpointStatus['setpointMode'], 'follow')
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
"""Return the minimum target temperature of a evohome Zone.
|
||||
|
||||
The default is 5, but is user-configurable within 5-35 (in Celsius).
|
||||
"""
|
||||
return self._evo_device.setpointCapabilities['minHeatSetpoint']
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
"""Return the maximum target temperature of a evohome Zone.
|
||||
|
||||
The default is 35, but is user-configurable within 5-35 (in Celsius).
|
||||
"""
|
||||
return self._evo_device.setpointCapabilities['maxHeatSetpoint']
|
||||
|
||||
def _set_temperature(self, temperature: float,
|
||||
until: Optional[datetime] = None):
|
||||
"""Set a new target temperature for the Zone.
|
||||
|
||||
until == None means indefinitely (i.e. PermanentOverride)
|
||||
"""
|
||||
try:
|
||||
self._evo_device.set_temperature(temperature, until)
|
||||
except (requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError) as err:
|
||||
_handle_exception(err)
|
||||
|
||||
def set_temperature(self, **kwargs) -> None:
|
||||
"""Set a new target temperature for an hour."""
|
||||
until = kwargs.get('until')
|
||||
if until:
|
||||
until = datetime.strptime(until, EVO_STRFTIME)
|
||||
|
||||
self._set_temperature(kwargs['temperature'], until)
|
||||
|
||||
def _set_operation_mode(self, op_mode) -> None:
|
||||
"""Set the Zone to one of its native EVO_* operating modes."""
|
||||
if op_mode == EVO_FOLLOW:
|
||||
try:
|
||||
self._evo_device.cancel_temp_override()
|
||||
except (requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError) as err:
|
||||
_handle_exception(err)
|
||||
return
|
||||
|
||||
self._setpoints = self.get_setpoints()
|
||||
temperature = self._evo_device.setpointStatus['targetHeatTemperature']
|
||||
|
||||
if op_mode == EVO_TEMPOVER:
|
||||
until = self._setpoints['next']['from_datetime']
|
||||
until = datetime.strptime(until, EVO_STRFTIME)
|
||||
else: # EVO_PERMOVER:
|
||||
until = None
|
||||
|
||||
self._set_temperature(temperature, until=until)
|
||||
|
||||
def set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set an operating mode for the Zone."""
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
self._set_temperature(self.min_temp, until=None)
|
||||
|
||||
else: # HVAC_MODE_HEAT
|
||||
self._set_operation_mode(EVO_FOLLOW)
|
||||
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set a new preset mode.
|
||||
|
||||
If preset_mode is None, then revert to following the schedule.
|
||||
"""
|
||||
self._set_operation_mode(HA_PRESET_TO_EVO.get(preset_mode, EVO_FOLLOW))
|
||||
|
||||
|
||||
class EvoController(EvoDevice, ClimateDevice):
|
||||
"""Base for a Honeywell evohome hub/Controller device.
|
||||
class EvoController(EvoClimateDevice):
|
||||
"""Base for a Honeywell evohome Controller (hub).
|
||||
|
||||
The Controller (aka TCS, temperature control system) is the parent of all
|
||||
the child (CH/DHW) devices. It is also a Climate device.
|
||||
"""
|
||||
|
||||
def __init__(self, evo_data, client, obj_ref):
|
||||
def __init__(self, evo_broker, evo_device) -> None:
|
||||
"""Initialize the evohome Controller (hub)."""
|
||||
super().__init__(evo_data, client, obj_ref)
|
||||
super().__init__(evo_broker, evo_device)
|
||||
|
||||
self._id = obj_ref.systemId
|
||||
self._name = '_{}'.format(obj_ref.location.name)
|
||||
self._icon = "mdi:thermostat"
|
||||
self._type = EVO_PARENT
|
||||
self._id = evo_device.systemId
|
||||
self._name = evo_device.location.name
|
||||
self._icon = 'mdi:thermostat'
|
||||
|
||||
self._config = evo_data['config'][GWS][0][TCS][0]
|
||||
self._status = evo_data['status']
|
||||
self._timers['statusUpdated'] = datetime.min
|
||||
self._precision = None
|
||||
self._state_attributes = [
|
||||
'activeFaults', 'systemModeStatus']
|
||||
|
||||
self._operation_list = TCS_OP_LIST
|
||||
self._supported_features = \
|
||||
SUPPORT_OPERATION_MODE | \
|
||||
SUPPORT_AWAY_MODE
|
||||
self._supported_features = SUPPORT_PRESET_MODE
|
||||
self._hvac_modes = list(HA_HVAC_TO_TCS)
|
||||
self._preset_modes = list(HA_PRESET_TO_TCS)
|
||||
|
||||
self._config = dict(evo_broker.config)
|
||||
self._config['zones'] = '...'
|
||||
if 'dhw' in self._config:
|
||||
self._config['dhw'] = '...'
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device state attributes of the evohome Controller.
|
||||
|
||||
This is state data that is not available otherwise, due to the
|
||||
restrictions placed upon ClimateDevice properties, etc. by HA.
|
||||
"""
|
||||
status = dict(self._status)
|
||||
|
||||
if 'zones' in status:
|
||||
del status['zones']
|
||||
if 'dhw' in status:
|
||||
del status['dhw']
|
||||
|
||||
return {'status': status}
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return the current operating mode of the evohome Controller."""
|
||||
return TCS_STATE_TO_HA.get(self._status['systemModeStatus']['mode'])
|
||||
tcs_mode = self._evo_device.systemModeStatus['mode']
|
||||
return HVAC_MODE_OFF if tcs_mode == EVO_HEATOFF else HVAC_MODE_HEAT
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the average current temperature of the Heating/DHW zones.
|
||||
def current_temperature(self) -> Optional[float]:
|
||||
"""Return the average current temperature of the heating Zones.
|
||||
|
||||
Although evohome Controllers do not have a target temp, one is
|
||||
expected by the HA schema.
|
||||
Controllers do not have a current temp, but one is expected by HA.
|
||||
"""
|
||||
tmp_list = [x for x in self._status['zones']
|
||||
if x['temperatureStatus']['isAvailable']]
|
||||
temps = [zone['temperatureStatus']['temperature'] for zone in tmp_list]
|
||||
|
||||
avg_temp = round(sum(temps) / len(temps), 1) if temps else None
|
||||
return avg_temp
|
||||
temps = [z.temperatureStatus['temperature'] for z in
|
||||
self._evo_device._zones if z.temperatureStatus['isAvailable']] # noqa: E501; pylint: disable=protected-access
|
||||
return round(sum(temps) / len(temps), 1) if temps else None
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the average target temperature of the Heating/DHW zones.
|
||||
def target_temperature(self) -> Optional[float]:
|
||||
"""Return the average target temperature of the heating Zones.
|
||||
|
||||
Although evohome Controllers do not have a target temp, one is
|
||||
expected by the HA schema.
|
||||
Controllers do not have a target temp, but one is expected by HA.
|
||||
"""
|
||||
temps = [zone['setpointStatus']['targetHeatTemperature']
|
||||
for zone in self._status['zones']]
|
||||
|
||||
avg_temp = round(sum(temps) / len(temps), 1) if temps else None
|
||||
return avg_temp
|
||||
temps = [z.setpointStatus['targetHeatTemperature']
|
||||
for z in self._evo_device._zones] # noqa: E501; pylint: disable=protected-access
|
||||
return round(sum(temps) / len(temps), 1) if temps else None
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self) -> bool:
|
||||
"""Return True if away mode is on."""
|
||||
return self._status['systemModeStatus']['mode'] == EVO_AWAY
|
||||
def preset_mode(self) -> Optional[str]:
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
return TCS_PRESET_TO_HA.get(self._evo_device.systemModeStatus['mode'])
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return True as evohome Controllers are always on.
|
||||
def min_temp(self) -> float:
|
||||
"""Return the minimum target temperature of the heating Zones.
|
||||
|
||||
For example, evohome Controllers have a 'HeatingOff' mode, but even
|
||||
then the DHW would remain on.
|
||||
Controllers do not have a min target temp, but one is required by HA.
|
||||
"""
|
||||
return True
|
||||
temps = [z.setpointCapabilities['minHeatSetpoint']
|
||||
for z in self._evo_device._zones] # noqa: E501; pylint: disable=protected-access
|
||||
return min(temps) if temps else 5
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum target temperature of a evohome Controller.
|
||||
def max_temp(self) -> float:
|
||||
"""Return the maximum target temperature of the heating Zones.
|
||||
|
||||
Although evohome Controllers do not have a minimum target temp, one is
|
||||
expected by the HA schema; the default for an evohome HR92 is used.
|
||||
Controllers do not have a max target temp, but one is required by HA.
|
||||
"""
|
||||
return 5
|
||||
temps = [z.setpointCapabilities['maxHeatSetpoint']
|
||||
for z in self._evo_device._zones] # noqa: E501; pylint: disable=protected-access
|
||||
return max(temps) if temps else 35
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum target temperature of a evohome Controller.
|
||||
|
||||
Although evohome Controllers do not have a maximum target temp, one is
|
||||
expected by the HA schema; the default for an evohome HR92 is used.
|
||||
"""
|
||||
return 35
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Return True as the evohome Controller should always be polled."""
|
||||
return True
|
||||
|
||||
def _set_operation_mode(self, operation_mode):
|
||||
def _set_operation_mode(self, op_mode) -> None:
|
||||
"""Set the Controller to any of its native EVO_* operating modes."""
|
||||
try:
|
||||
self._obj._set_status(operation_mode) # noqa: E501; pylint: disable=protected-access
|
||||
self._evo_device._set_status(op_mode) # noqa: E501; pylint: disable=protected-access
|
||||
except (requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError) as err:
|
||||
self._handle_exception(err)
|
||||
_handle_exception(err)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new target operation mode for the TCS.
|
||||
def set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set an operating mode for the Controller."""
|
||||
self._set_operation_mode(HA_HVAC_TO_TCS.get(hvac_mode))
|
||||
|
||||
Currently limited to 'Auto', 'AutoWithEco' & 'HeatingOff'. If 'Away'
|
||||
mode is needed, it can be enabled via turn_away_mode_on method.
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set a new preset mode.
|
||||
|
||||
If preset_mode is None, then revert to 'Auto' mode.
|
||||
"""
|
||||
self._set_operation_mode(HA_STATE_TO_TCS.get(operation_mode))
|
||||
self._set_operation_mode(HA_PRESET_TO_TCS.get(preset_mode, EVO_AUTO))
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away mode on.
|
||||
|
||||
The evohome Controller will not remember is previous operating mode.
|
||||
"""
|
||||
self._set_operation_mode(EVO_AWAY)
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away mode off.
|
||||
|
||||
The evohome Controller can not recall its previous operating mode (as
|
||||
intimated by the HA schema), so this method is achieved by setting the
|
||||
Controller's mode back to Auto.
|
||||
"""
|
||||
self._set_operation_mode(EVO_AUTO)
|
||||
|
||||
def update(self):
|
||||
"""Get the latest state data of the entire evohome Location.
|
||||
|
||||
This includes state data for the Controller and all its child devices,
|
||||
such as the operating mode of the Controller and the current temp of
|
||||
its children (e.g. Zones, DHW controller).
|
||||
"""
|
||||
# should the latest evohome state data be retreived this cycle?
|
||||
timeout = datetime.now() + timedelta(seconds=55)
|
||||
expired = timeout > self._timers['statusUpdated'] + \
|
||||
self._params[CONF_SCAN_INTERVAL]
|
||||
|
||||
if not expired:
|
||||
return
|
||||
|
||||
# Retrieve the latest state data via the client API
|
||||
loc_idx = self._params[CONF_LOCATION_IDX]
|
||||
|
||||
try:
|
||||
self._status.update(
|
||||
self._client.locations[loc_idx].status()[GWS][0][TCS][0])
|
||||
except (requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError) as err:
|
||||
self._handle_exception(err)
|
||||
else:
|
||||
self._timers['statusUpdated'] = datetime.now()
|
||||
self._available = True
|
||||
|
||||
_LOGGER.debug("Status = %s", self._status)
|
||||
|
||||
# inform the child devices that state data has been updated
|
||||
pkt = {'sender': 'controller', 'signal': 'refresh', 'to': EVO_CHILD}
|
||||
dispatcher_send(self.hass, DISPATCHER_EVOHOME, pkt)
|
||||
def update(self) -> None:
|
||||
"""Get the latest state data."""
|
||||
pass
|
||||
|
|
|
@ -1,9 +1,25 @@
|
|||
"""Provides the constants needed for evohome."""
|
||||
|
||||
"""Support for (EMEA/EU-based) Honeywell TCC climate systems."""
|
||||
DOMAIN = 'evohome'
|
||||
DATA_EVOHOME = 'data_' + DOMAIN
|
||||
DISPATCHER_EVOHOME = 'dispatcher_' + DOMAIN
|
||||
|
||||
# These are used only to help prevent E501 (line too long) violations.
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = DOMAIN
|
||||
|
||||
# The Parent's (i.e. TCS, Controller's) operating mode is one of:
|
||||
EVO_RESET = 'AutoWithReset'
|
||||
EVO_AUTO = 'Auto'
|
||||
EVO_AUTOECO = 'AutoWithEco'
|
||||
EVO_AWAY = 'Away'
|
||||
EVO_DAYOFF = 'DayOff'
|
||||
EVO_CUSTOM = 'Custom'
|
||||
EVO_HEATOFF = 'HeatingOff'
|
||||
|
||||
# The Childs' operating mode is one of:
|
||||
EVO_FOLLOW = 'FollowSchedule' # the operating mode is 'inherited' from the TCS
|
||||
EVO_TEMPOVER = 'TemporaryOverride'
|
||||
EVO_PERMOVER = 'PermanentOverride'
|
||||
|
||||
# These are used only to help prevent E501 (line too long) violations
|
||||
GWS = 'gateways'
|
||||
TCS = 'temperatureControlSystems'
|
||||
|
||||
EVO_STRFTIME = '%Y-%m-%dT%H:%M:%SZ'
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Evohome",
|
||||
"documentation": "https://www.home-assistant.io/components/evohome",
|
||||
"requirements": [
|
||||
"evohomeclient==0.3.2"
|
||||
"evohomeclient==0.3.3"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@zxdavb"]
|
||||
|
|
|
@ -1,90 +1,87 @@
|
|||
"""Support for Fibaro thermostats."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_COOL, STATE_DRY,
|
||||
STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
|
||||
STATE_MANUAL, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE)
|
||||
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, SUPPORT_FAN_MODE,
|
||||
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice)
|
||||
from . import FIBARO_DEVICES, FibaroDevice
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE,
|
||||
STATE_OFF,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT)
|
||||
|
||||
from . import (
|
||||
FIBARO_DEVICES, FibaroDevice)
|
||||
|
||||
SPEED_LOW = 'low'
|
||||
SPEED_MEDIUM = 'medium'
|
||||
SPEED_HIGH = 'high'
|
||||
|
||||
# State definitions missing from HA, but defined by Z-Wave standard.
|
||||
# We map them to states known supported by HA here:
|
||||
STATE_AUXILIARY = STATE_HEAT
|
||||
STATE_RESUME = STATE_HEAT
|
||||
STATE_MOIST = STATE_DRY
|
||||
STATE_AUTO_CHANGEOVER = STATE_AUTO
|
||||
STATE_ENERGY_HEAT = STATE_ECO
|
||||
STATE_ENERGY_COOL = STATE_COOL
|
||||
STATE_FULL_POWER = STATE_AUTO
|
||||
STATE_FORCE_OPEN = STATE_MANUAL
|
||||
STATE_AWAY = STATE_AUTO
|
||||
STATE_FURNACE = STATE_HEAT
|
||||
|
||||
FAN_AUTO_HIGH = 'auto_high'
|
||||
FAN_AUTO_MEDIUM = 'auto_medium'
|
||||
FAN_CIRCULATION = 'circulation'
|
||||
FAN_HUMIDITY_CIRCULATION = 'humidity_circulation'
|
||||
FAN_LEFT_RIGHT = 'left_right'
|
||||
FAN_UP_DOWN = 'up_down'
|
||||
FAN_QUIET = 'quiet'
|
||||
PRESET_RESUME = 'resume'
|
||||
PRESET_MOIST = 'moist'
|
||||
PRESET_FURNACE = 'furnace'
|
||||
PRESET_CHANGEOVER = 'changeover'
|
||||
PRESET_ECO_HEAT = 'eco_heat'
|
||||
PRESET_ECO_COOL = 'eco_cool'
|
||||
PRESET_FORCE_OPEN = 'force_open'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
|
||||
# Table 128, Thermostat Fan Mode Set version 4::Fan Mode encoding
|
||||
FANMODES = {
|
||||
0: STATE_OFF,
|
||||
1: SPEED_LOW,
|
||||
2: FAN_AUTO_HIGH,
|
||||
3: SPEED_HIGH,
|
||||
4: FAN_AUTO_MEDIUM,
|
||||
5: SPEED_MEDIUM,
|
||||
6: FAN_CIRCULATION,
|
||||
7: FAN_HUMIDITY_CIRCULATION,
|
||||
8: FAN_LEFT_RIGHT,
|
||||
9: FAN_UP_DOWN,
|
||||
10: FAN_QUIET,
|
||||
128: STATE_AUTO
|
||||
0: 'off',
|
||||
1: 'low',
|
||||
2: 'auto_high',
|
||||
3: 'medium',
|
||||
4: 'auto_medium',
|
||||
5: 'high',
|
||||
6: 'circulation',
|
||||
7: 'humidity_circulation',
|
||||
8: 'left_right',
|
||||
9: 'up_down',
|
||||
10: 'quiet',
|
||||
128: 'auto'
|
||||
}
|
||||
|
||||
HA_FANMODES = {v: k for k, v in FANMODES.items()}
|
||||
|
||||
# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
|
||||
# Table 130, Thermostat Mode Set version 3::Mode encoding.
|
||||
OPMODES = {
|
||||
0: STATE_OFF,
|
||||
1: STATE_HEAT,
|
||||
2: STATE_COOL,
|
||||
3: STATE_AUTO,
|
||||
4: STATE_AUXILIARY,
|
||||
5: STATE_RESUME,
|
||||
6: STATE_FAN_ONLY,
|
||||
7: STATE_FURNACE,
|
||||
8: STATE_DRY,
|
||||
9: STATE_MOIST,
|
||||
10: STATE_AUTO_CHANGEOVER,
|
||||
11: STATE_ENERGY_HEAT,
|
||||
12: STATE_ENERGY_COOL,
|
||||
13: STATE_AWAY,
|
||||
15: STATE_FULL_POWER,
|
||||
31: STATE_FORCE_OPEN
|
||||
# 4 AUXILARY
|
||||
OPMODES_PRESET = {
|
||||
5: PRESET_RESUME,
|
||||
7: PRESET_FURNACE,
|
||||
9: PRESET_MOIST,
|
||||
10: PRESET_CHANGEOVER,
|
||||
11: PRESET_ECO_HEAT,
|
||||
12: PRESET_ECO_COOL,
|
||||
13: PRESET_AWAY,
|
||||
15: PRESET_BOOST,
|
||||
31: PRESET_FORCE_OPEN,
|
||||
}
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
|
||||
HA_OPMODES_PRESET = {v: k for k, v in OPMODES_PRESET.items()}
|
||||
|
||||
OPMODES_HVAC = {
|
||||
0: HVAC_MODE_OFF,
|
||||
1: HVAC_MODE_HEAT,
|
||||
2: HVAC_MODE_COOL,
|
||||
3: HVAC_MODE_AUTO,
|
||||
4: HVAC_MODE_HEAT,
|
||||
5: HVAC_MODE_AUTO,
|
||||
6: HVAC_MODE_FAN_ONLY,
|
||||
7: HVAC_MODE_HEAT,
|
||||
8: HVAC_MODE_DRY,
|
||||
9: HVAC_MODE_DRY,
|
||||
10: HVAC_MODE_AUTO,
|
||||
11: HVAC_MODE_HEAT,
|
||||
12: HVAC_MODE_COOL,
|
||||
13: HVAC_MODE_AUTO,
|
||||
15: HVAC_MODE_AUTO,
|
||||
31: HVAC_MODE_HEAT,
|
||||
}
|
||||
|
||||
HA_OPMODES_HVAC = {
|
||||
HVAC_MODE_OFF: 0,
|
||||
HVAC_MODE_HEAT: 1,
|
||||
HVAC_MODE_COOL: 2,
|
||||
HVAC_MODE_AUTO: 3,
|
||||
HVAC_MODE_FAN_ONLY: 6,
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
@ -109,10 +106,9 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||
self._fan_mode_device = None
|
||||
self._support_flags = 0
|
||||
self.entity_id = 'climate.{}'.format(self.ha_id)
|
||||
self._fan_mode_to_state = {}
|
||||
self._fan_state_to_mode = {}
|
||||
self._op_mode_to_state = {}
|
||||
self._op_state_to_mode = {}
|
||||
self._hvac_support = []
|
||||
self._preset_support = []
|
||||
self._fan_support = []
|
||||
|
||||
siblings = fibaro_device.fibaro_controller.get_siblings(
|
||||
fibaro_device.id)
|
||||
|
@ -129,7 +125,7 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||
if 'setMode' in device.actions or \
|
||||
'setOperatingMode' in device.actions:
|
||||
self._op_mode_device = FibaroDevice(device)
|
||||
self._support_flags |= SUPPORT_OPERATION_MODE
|
||||
self._support_flags |= SUPPORT_PRESET_MODE
|
||||
if 'setFanMode' in device.actions:
|
||||
self._fan_mode_device = FibaroDevice(device)
|
||||
self._support_flags |= SUPPORT_FAN_MODE
|
||||
|
@ -143,11 +139,11 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||
fan_modes = self._fan_mode_device.fibaro_device.\
|
||||
properties.supportedModes.split(",")
|
||||
for mode in fan_modes:
|
||||
try:
|
||||
self._fan_mode_to_state[int(mode)] = FANMODES[int(mode)]
|
||||
self._fan_state_to_mode[FANMODES[int(mode)]] = int(mode)
|
||||
except KeyError:
|
||||
self._fan_mode_to_state[int(mode)] = 'unknown'
|
||||
mode = int(mode)
|
||||
if mode not in FANMODES:
|
||||
_LOGGER.warning("%d unknown fan mode", mode)
|
||||
continue
|
||||
self._fan_support.append(FANMODES[int(mode)])
|
||||
|
||||
if self._op_mode_device:
|
||||
prop = self._op_mode_device.fibaro_device.properties
|
||||
|
@ -156,11 +152,13 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||
elif "supportedModes" in prop:
|
||||
op_modes = prop.supportedModes.split(",")
|
||||
for mode in op_modes:
|
||||
try:
|
||||
self._op_mode_to_state[int(mode)] = OPMODES[int(mode)]
|
||||
self._op_state_to_mode[OPMODES[int(mode)]] = int(mode)
|
||||
except KeyError:
|
||||
self._op_mode_to_state[int(mode)] = 'unknown'
|
||||
mode = int(mode)
|
||||
if mode in OPMODES_HVAC:
|
||||
mode_ha = OPMODES_HVAC[mode]
|
||||
if mode_ha not in self._hvac_support:
|
||||
self._hvac_support.append(mode_ha)
|
||||
if mode in OPMODES_PRESET:
|
||||
self._preset_support.append(OPMODES_PRESET[mode])
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity is added to hass."""
|
||||
|
@ -194,32 +192,70 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||
return self._support_flags
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
if self._fan_mode_device is None:
|
||||
if not self._fan_mode_device:
|
||||
return None
|
||||
return list(self._fan_state_to_mode)
|
||||
return self._fan_support
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
if self._fan_mode_device is None:
|
||||
if not self._fan_mode_device:
|
||||
return None
|
||||
|
||||
mode = int(self._fan_mode_device.fibaro_device.properties.mode)
|
||||
return self._fan_mode_to_state[mode]
|
||||
return FANMODES[mode]
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
"""Set new target fan mode."""
|
||||
if self._fan_mode_device is None:
|
||||
if not self._fan_mode_device:
|
||||
return
|
||||
self._fan_mode_device.action(
|
||||
"setFanMode", self._fan_state_to_mode[fan_mode])
|
||||
self._fan_mode_device.action("setFanMode", HA_FANMODES[fan_mode])
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def fibaro_op_mode(self):
|
||||
"""Return the operating mode of the device."""
|
||||
if not self._op_mode_device:
|
||||
return 6 # Fan only
|
||||
|
||||
if "operatingMode" in self._op_mode_device.fibaro_device.properties:
|
||||
return int(self._op_mode_device.fibaro_device.
|
||||
properties.operatingMode)
|
||||
|
||||
return int(self._op_mode_device.fibaro_device.properties.mode)
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
if self._op_mode_device is None:
|
||||
return OPMODES_HVAC[self.fibaro_op_mode]
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
if not self._op_mode_device:
|
||||
return [HVAC_MODE_FAN_ONLY]
|
||||
return self._hvac_support
|
||||
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target operation mode."""
|
||||
if not self._op_mode_device:
|
||||
return
|
||||
if self.preset_mode:
|
||||
return
|
||||
|
||||
if "setOperatingMode" in self._op_mode_device.fibaro_device.actions:
|
||||
self._op_mode_device.action(
|
||||
"setOperatingMode", HA_OPMODES_HVAC[hvac_mode])
|
||||
elif "setMode" in self._op_mode_device.fibaro_device.actions:
|
||||
self._op_mode_device.action("setMode", HA_OPMODES_HVAC[hvac_mode])
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode, e.g., home, away, temp.
|
||||
|
||||
Requires SUPPORT_PRESET_MODE.
|
||||
"""
|
||||
if not self._op_mode_device:
|
||||
return None
|
||||
|
||||
if "operatingMode" in self._op_mode_device.fibaro_device.properties:
|
||||
|
@ -227,25 +263,31 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||
properties.operatingMode)
|
||||
else:
|
||||
mode = int(self._op_mode_device.fibaro_device.properties.mode)
|
||||
return self._op_mode_to_state.get(mode)
|
||||
|
||||
if mode not in OPMODES_PRESET:
|
||||
return None
|
||||
return OPMODES_PRESET[mode]
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the list of available operation modes."""
|
||||
if self._op_mode_device is None:
|
||||
return None
|
||||
return list(self._op_state_to_mode)
|
||||
def preset_modes(self):
|
||||
"""Return a list of available preset modes.
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new target operation mode."""
|
||||
Requires SUPPORT_PRESET_MODE.
|
||||
"""
|
||||
if not self._op_mode_device:
|
||||
return None
|
||||
return self._preset_support
|
||||
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
if self._op_mode_device is None:
|
||||
return
|
||||
if "setOperatingMode" in self._op_mode_device.fibaro_device.actions:
|
||||
self._op_mode_device.action(
|
||||
"setOperatingMode", self._op_state_to_mode[operation_mode])
|
||||
"setOperatingMode", HA_OPMODES_PRESET[preset_mode])
|
||||
elif "setMode" in self._op_mode_device.fibaro_device.actions:
|
||||
self._op_mode_device.action(
|
||||
"setMode", self._op_state_to_mode[operation_mode])
|
||||
"setMode", HA_OPMODES_PRESET[preset_mode])
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
|
@ -275,15 +317,6 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||
if temperature is not None:
|
||||
if "setThermostatSetpoint" in target.fibaro_device.actions:
|
||||
target.action("setThermostatSetpoint",
|
||||
self._op_state_to_mode[self.current_operation],
|
||||
temperature)
|
||||
self.fibaro_op_mode, temperature)
|
||||
else:
|
||||
target.action("setTargetLevel",
|
||||
temperature)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if on."""
|
||||
if self.current_operation == STATE_OFF:
|
||||
return False
|
||||
return True
|
||||
target.action("setTargetLevel", temperature)
|
||||
|
|
|
@ -12,6 +12,7 @@ For more details about this platform, please refer to the documentation
|
|||
https://home-assistant.io/components/climate.flexit/
|
||||
"""
|
||||
import logging
|
||||
from typing import List
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
|
@ -20,7 +21,7 @@ from homeassistant.const import (
|
|||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_FAN_MODE)
|
||||
SUPPORT_FAN_MODE, HVAC_MODE_COOL)
|
||||
from homeassistant.components.modbus import (
|
||||
CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -57,7 +58,7 @@ class Flexit(ClimateDevice):
|
|||
self._current_temperature = None
|
||||
self._current_fan_mode = None
|
||||
self._current_operation = None
|
||||
self._fan_list = ['Off', 'Low', 'Medium', 'High']
|
||||
self._fan_modes = ['Off', 'Low', 'Medium', 'High']
|
||||
self._current_operation = None
|
||||
self._filter_hours = None
|
||||
self._filter_alarm = None
|
||||
|
@ -81,7 +82,7 @@ class Flexit(ClimateDevice):
|
|||
self._target_temperature = self.unit.get_target_temp
|
||||
self._current_temperature = self.unit.get_temp
|
||||
self._current_fan_mode =\
|
||||
self._fan_list[self.unit.get_fan_speed]
|
||||
self._fan_modes[self.unit.get_fan_speed]
|
||||
self._filter_hours = self.unit.get_filter_hours
|
||||
# Mechanical heat recovery, 0-100%
|
||||
self._heat_recovery = self.unit.get_heat_recovery
|
||||
|
@ -134,19 +135,27 @@ class Flexit(ClimateDevice):
|
|||
return self._target_temperature
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._current_operation
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def hvac_modes(self) -> List[str]:
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return [HVAC_MODE_COOL]
|
||||
|
||||
@property
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._current_fan_mode
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return self._fan_list
|
||||
return self._fan_modes
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
@ -156,4 +165,4 @@ class Flexit(ClimateDevice):
|
|||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
"""Set new fan mode."""
|
||||
self.unit.set_fan_speed(self._fan_list.index(fan_mode))
|
||||
self.unit.set_fan_speed(self._fan_modes.index(fan_mode))
|
||||
|
|
|
@ -5,11 +5,11 @@ import requests
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_OPERATION_MODE, STATE_ECO, STATE_HEAT, STATE_MANUAL,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
ATTR_HVAC_MODE, HVAC_MODE_HEAT, PRESET_ECO, PRESET_COMFORT,
|
||||
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, SUPPORT_PRESET_MODE)
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES, STATE_OFF,
|
||||
STATE_ON, TEMP_CELSIUS)
|
||||
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES,
|
||||
TEMP_CELSIUS)
|
||||
|
||||
from . import (
|
||||
ATTR_STATE_BATTERY_LOW, ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_HOLIDAY_MODE,
|
||||
|
@ -18,13 +18,15 @@ from . import (
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE)
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
|
||||
OPERATION_LIST = [STATE_HEAT, STATE_ECO, STATE_OFF, STATE_ON]
|
||||
OPERATION_LIST = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
|
||||
MIN_TEMPERATURE = 8
|
||||
MAX_TEMPERATURE = 28
|
||||
|
||||
PRESET_MANUAL = 'manual'
|
||||
|
||||
# special temperatures for on/off in Fritz!Box API (modified by pyfritzhome)
|
||||
ON_API_TEMPERATURE = 127.0
|
||||
OFF_API_TEMPERATURE = 126.5
|
||||
|
@ -98,41 +100,51 @@ class FritzboxThermostat(ClimateDevice):
|
|||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
if ATTR_OPERATION_MODE in kwargs:
|
||||
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
|
||||
self.set_operation_mode(operation_mode)
|
||||
if ATTR_HVAC_MODE in kwargs:
|
||||
hvac_mode = kwargs.get(ATTR_HVAC_MODE)
|
||||
self.set_hvac_mode(hvac_mode)
|
||||
elif ATTR_TEMPERATURE in kwargs:
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
self._device.set_target_temperature(temperature)
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return the current operation mode."""
|
||||
if self._target_temperature == ON_API_TEMPERATURE:
|
||||
return STATE_ON
|
||||
if self._target_temperature == OFF_API_TEMPERATURE:
|
||||
return STATE_OFF
|
||||
if self._target_temperature == self._comfort_temperature:
|
||||
return STATE_HEAT
|
||||
if self._target_temperature == self._eco_temperature:
|
||||
return STATE_ECO
|
||||
return STATE_MANUAL
|
||||
if self._target_temperature == OFF_REPORT_SET_TEMPERATURE:
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return OPERATION_LIST
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new operation mode."""
|
||||
if operation_mode == STATE_HEAT:
|
||||
self.set_temperature(temperature=self._comfort_temperature)
|
||||
elif operation_mode == STATE_ECO:
|
||||
self.set_temperature(temperature=self._eco_temperature)
|
||||
elif operation_mode == STATE_OFF:
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
self.set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE)
|
||||
elif operation_mode == STATE_ON:
|
||||
self.set_temperature(temperature=ON_REPORT_SET_TEMPERATURE)
|
||||
else:
|
||||
self.set_temperature(temperature=self._comfort_temperature)
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return current preset mode."""
|
||||
if self._target_temperature == self._comfort_temperature:
|
||||
return PRESET_COMFORT
|
||||
if self._target_temperature == self._eco_temperature:
|
||||
return PRESET_ECO
|
||||
|
||||
def preset_modes(self):
|
||||
"""Return supported preset modes."""
|
||||
return [PRESET_ECO, PRESET_COMFORT]
|
||||
|
||||
def set_preset_mode(self, preset_mode):
|
||||
"""Set preset mode."""
|
||||
if preset_mode == PRESET_COMFORT:
|
||||
self.set_temperature(temperature=self._comfort_temperature)
|
||||
elif preset_mode == PRESET_ECO:
|
||||
self.set_temperature(temperature=self._eco_temperature)
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Home Assistant Frontend",
|
||||
"documentation": "https://www.home-assistant.io/components/frontend",
|
||||
"requirements": [
|
||||
"home-assistant-frontend==20190702.0"
|
||||
"home-assistant-frontend==20190705.0"
|
||||
],
|
||||
"dependencies": [
|
||||
"api",
|
||||
|
|
|
@ -4,10 +4,15 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_PRESET_MODE, CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE,
|
||||
CURRENT_HVAC_OFF, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||
PRESET_AWAY, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_NAME, PRECISION_HALVES,
|
||||
PRECISION_TENTHS, PRECISION_WHOLE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
||||
STATE_OFF, STATE_ON, STATE_UNKNOWN)
|
||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_NAME, EVENT_HOMEASSISTANT_START,
|
||||
PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE, SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON, STATE_ON, STATE_UNKNOWN)
|
||||
from homeassistant.core import DOMAIN as HA_DOMAIN, callback
|
||||
from homeassistant.helpers import condition
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -15,12 +20,6 @@ from homeassistant.helpers.event import (
|
|||
async_track_state_change, async_track_time_interval)
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_AWAY_MODE, ATTR_OPERATION_MODE, STATE_AUTO, STATE_COOL, STATE_HEAT,
|
||||
STATE_IDLE, SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_TOLERANCE = 0.3
|
||||
|
@ -36,11 +35,10 @@ CONF_MIN_DUR = 'min_cycle_duration'
|
|||
CONF_COLD_TOLERANCE = 'cold_tolerance'
|
||||
CONF_HOT_TOLERANCE = 'hot_tolerance'
|
||||
CONF_KEEP_ALIVE = 'keep_alive'
|
||||
CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode'
|
||||
CONF_INITIAL_HVAC_MODE = 'initial_hvac_mode'
|
||||
CONF_AWAY_TEMP = 'away_temp'
|
||||
CONF_PRECISION = 'precision'
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
||||
SUPPORT_OPERATION_MODE)
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HEATER): cv.entity_id,
|
||||
|
@ -57,8 +55,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
|
||||
vol.Optional(CONF_KEEP_ALIVE): vol.All(
|
||||
cv.time_period, cv.positive_timedelta),
|
||||
vol.Optional(CONF_INITIAL_OPERATION_MODE):
|
||||
vol.In([STATE_AUTO, STATE_OFF]),
|
||||
vol.Optional(CONF_INITIAL_HVAC_MODE):
|
||||
vol.In([HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF]),
|
||||
vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float),
|
||||
vol.Optional(CONF_PRECISION): vol.In(
|
||||
[PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]),
|
||||
|
@ -79,77 +77,78 @@ async def async_setup_platform(hass, config, async_add_entities,
|
|||
cold_tolerance = config.get(CONF_COLD_TOLERANCE)
|
||||
hot_tolerance = config.get(CONF_HOT_TOLERANCE)
|
||||
keep_alive = config.get(CONF_KEEP_ALIVE)
|
||||
initial_operation_mode = config.get(CONF_INITIAL_OPERATION_MODE)
|
||||
initial_hvac_mode = config.get(CONF_INITIAL_HVAC_MODE)
|
||||
away_temp = config.get(CONF_AWAY_TEMP)
|
||||
precision = config.get(CONF_PRECISION)
|
||||
unit = hass.config.units.temperature_unit
|
||||
|
||||
async_add_entities([GenericThermostat(
|
||||
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
|
||||
name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
|
||||
target_temp, ac_mode, min_cycle_duration, cold_tolerance,
|
||||
hot_tolerance, keep_alive, initial_operation_mode, away_temp,
|
||||
precision)])
|
||||
hot_tolerance, keep_alive, initial_hvac_mode, away_temp,
|
||||
precision, unit)])
|
||||
|
||||
|
||||
class GenericThermostat(ClimateDevice, RestoreEntity):
|
||||
"""Representation of a Generic Thermostat device."""
|
||||
|
||||
def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
|
||||
def __init__(self, name, heater_entity_id, sensor_entity_id,
|
||||
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
|
||||
cold_tolerance, hot_tolerance, keep_alive,
|
||||
initial_operation_mode, away_temp, precision):
|
||||
initial_hvac_mode, away_temp, precision, unit):
|
||||
"""Initialize the thermostat."""
|
||||
self.hass = hass
|
||||
self._name = name
|
||||
self.heater_entity_id = heater_entity_id
|
||||
self.sensor_entity_id = sensor_entity_id
|
||||
self.ac_mode = ac_mode
|
||||
self.min_cycle_duration = min_cycle_duration
|
||||
self._cold_tolerance = cold_tolerance
|
||||
self._hot_tolerance = hot_tolerance
|
||||
self._keep_alive = keep_alive
|
||||
self._initial_operation_mode = initial_operation_mode
|
||||
self._saved_target_temp = target_temp if target_temp is not None \
|
||||
else away_temp
|
||||
self._hvac_mode = initial_hvac_mode
|
||||
self._saved_target_temp = target_temp or away_temp
|
||||
self._temp_precision = precision
|
||||
if self.ac_mode:
|
||||
self._current_operation = STATE_COOL
|
||||
self._operation_list = [STATE_COOL, STATE_OFF]
|
||||
self._hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
|
||||
else:
|
||||
self._current_operation = STATE_HEAT
|
||||
self._operation_list = [STATE_HEAT, STATE_OFF]
|
||||
if initial_operation_mode == STATE_OFF:
|
||||
self._enabled = False
|
||||
self._current_operation = STATE_OFF
|
||||
else:
|
||||
self._enabled = True
|
||||
self._hvac_list = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
self._active = False
|
||||
self._cur_temp = None
|
||||
self._temp_lock = asyncio.Lock()
|
||||
self._min_temp = min_temp
|
||||
self._max_temp = max_temp
|
||||
self._target_temp = target_temp
|
||||
self._unit = hass.config.units.temperature_unit
|
||||
self._unit = unit
|
||||
self._support_flags = SUPPORT_FLAGS
|
||||
if away_temp is not None:
|
||||
self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE
|
||||
if away_temp:
|
||||
self._support_flags = SUPPORT_FLAGS | SUPPORT_PRESET_MODE
|
||||
self._away_temp = away_temp
|
||||
self._is_away = False
|
||||
|
||||
async_track_state_change(
|
||||
hass, sensor_entity_id, self._async_sensor_changed)
|
||||
async_track_state_change(
|
||||
hass, heater_entity_id, self._async_switch_changed)
|
||||
|
||||
if self._keep_alive:
|
||||
async_track_time_interval(
|
||||
hass, self._async_control_heating, self._keep_alive)
|
||||
|
||||
sensor_state = hass.states.get(sensor_entity_id)
|
||||
if sensor_state and sensor_state.state != STATE_UNKNOWN:
|
||||
self._async_update_temp(sensor_state)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Run when entity about to be added."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
# Add listener
|
||||
async_track_state_change(
|
||||
self.hass, self.sensor_entity_id, self._async_sensor_changed)
|
||||
async_track_state_change(
|
||||
self.hass, self.heater_entity_id, self._async_switch_changed)
|
||||
|
||||
if self._keep_alive:
|
||||
async_track_time_interval(
|
||||
self.hass, self._async_control_heating, self._keep_alive)
|
||||
|
||||
@callback
|
||||
def _async_startup(event):
|
||||
"""Init on startup."""
|
||||
sensor_state = self.hass.states.get(self.sensor_entity_id)
|
||||
if sensor_state and sensor_state.state != STATE_UNKNOWN:
|
||||
self._async_update_temp(sensor_state)
|
||||
|
||||
self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, _async_startup)
|
||||
|
||||
# Check If we have an old state
|
||||
old_state = await self.async_get_last_state()
|
||||
if old_state is not None:
|
||||
|
@ -166,14 +165,10 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||
else:
|
||||
self._target_temp = float(
|
||||
old_state.attributes[ATTR_TEMPERATURE])
|
||||
if old_state.attributes.get(ATTR_AWAY_MODE) is not None:
|
||||
self._is_away = str(
|
||||
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON
|
||||
if (self._initial_operation_mode is None and
|
||||
old_state.attributes[ATTR_OPERATION_MODE] is not None):
|
||||
self._current_operation = \
|
||||
old_state.attributes[ATTR_OPERATION_MODE]
|
||||
self._enabled = self._current_operation != STATE_OFF
|
||||
if old_state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY:
|
||||
self._is_away = True
|
||||
if not self._hvac_mode and old_state.state:
|
||||
self._hvac_mode = old_state.state
|
||||
|
||||
else:
|
||||
# No previous state, try and restore defaults
|
||||
|
@ -185,14 +180,9 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||
_LOGGER.warning("No previously saved temperature, setting to %s",
|
||||
self._target_temp)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the current state."""
|
||||
if self._is_device_active:
|
||||
return self.current_operation
|
||||
if self._enabled:
|
||||
return STATE_IDLE
|
||||
return STATE_OFF
|
||||
# Set default state to off
|
||||
if not self._hvac_mode:
|
||||
self._hvac_mode = HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
|
@ -222,9 +212,23 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||
return self._cur_temp
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation."""
|
||||
return self._current_operation
|
||||
return self._hvac_mode
|
||||
|
||||
@property
|
||||
def hvac_action(self):
|
||||
"""Return the current running hvac operation if supported.
|
||||
|
||||
Need to be one of CURRENT_HVAC_*.
|
||||
"""
|
||||
if self._hvac_mode == HVAC_MODE_OFF:
|
||||
return CURRENT_HVAC_OFF
|
||||
if not self._is_device_active:
|
||||
return CURRENT_HVAC_IDLE
|
||||
if self.ac_mode:
|
||||
return CURRENT_HVAC_COOL
|
||||
return CURRENT_HVAC_HEAT
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
|
@ -232,39 +236,42 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||
return self._target_temp
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""List of available operation modes."""
|
||||
return self._operation_list
|
||||
return self._hvac_list
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
"""Set operation mode."""
|
||||
if operation_mode == STATE_HEAT:
|
||||
self._current_operation = STATE_HEAT
|
||||
self._enabled = True
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
if self._is_away:
|
||||
return PRESET_AWAY
|
||||
return None
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Return a list of available preset modes."""
|
||||
if self._away_temp:
|
||||
return [PRESET_AWAY]
|
||||
return None
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set hvac mode."""
|
||||
if hvac_mode == HVAC_MODE_HEAT:
|
||||
self._hvac_mode = HVAC_MODE_HEAT
|
||||
await self._async_control_heating(force=True)
|
||||
elif operation_mode == STATE_COOL:
|
||||
self._current_operation = STATE_COOL
|
||||
self._enabled = True
|
||||
elif hvac_mode == HVAC_MODE_COOL:
|
||||
self._hvac_mode = HVAC_MODE_COOL
|
||||
await self._async_control_heating(force=True)
|
||||
elif operation_mode == STATE_OFF:
|
||||
self._current_operation = STATE_OFF
|
||||
self._enabled = False
|
||||
elif hvac_mode == HVAC_MODE_OFF:
|
||||
self._hvac_mode = HVAC_MODE_OFF
|
||||
if self._is_device_active:
|
||||
await self._async_heater_turn_off()
|
||||
else:
|
||||
_LOGGER.error("Unrecognized operation mode: %s", operation_mode)
|
||||
_LOGGER.error("Unrecognized hvac mode: %s", hvac_mode)
|
||||
return
|
||||
# Ensure we update the current operation after changing the mode
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn thermostat on."""
|
||||
await self.async_set_operation_mode(self.operation_list[0])
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn thermostat off."""
|
||||
await self.async_set_operation_mode(STATE_OFF)
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
@ -326,7 +333,7 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||
"Generic thermostat active. %s, %s",
|
||||
self._cur_temp, self._target_temp)
|
||||
|
||||
if not self._active or not self._enabled:
|
||||
if not self._active or self._hvac_mode == HVAC_MODE_OFF:
|
||||
return
|
||||
|
||||
if not force and time is None:
|
||||
|
@ -338,7 +345,7 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||
if self._is_device_active:
|
||||
current_state = STATE_ON
|
||||
else:
|
||||
current_state = STATE_OFF
|
||||
current_state = HVAC_MODE_OFF
|
||||
long_enough = condition.state(
|
||||
self.hass, self.heater_entity_id, current_state,
|
||||
self.min_cycle_duration)
|
||||
|
@ -387,26 +394,19 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||
data = {ATTR_ENTITY_ID: self.heater_entity_id}
|
||||
await self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_OFF, data)
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._is_away
|
||||
async def async_set_preset_mode(self, preset_mode: str):
|
||||
"""Set new preset mode.
|
||||
|
||||
async def async_turn_away_mode_on(self):
|
||||
"""Turn away mode on by setting it on away hold indefinitely."""
|
||||
if self._is_away:
|
||||
return
|
||||
self._is_away = True
|
||||
self._saved_target_temp = self._target_temp
|
||||
self._target_temp = self._away_temp
|
||||
await self._async_control_heating(force=True)
|
||||
await self.async_update_ha_state()
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
if preset_mode == PRESET_AWAY and not self._is_away:
|
||||
self._is_away = True
|
||||
self._saved_target_temp = self._target_temp
|
||||
self._target_temp = self._away_temp
|
||||
await self._async_control_heating(force=True)
|
||||
elif not preset_mode and self._is_away:
|
||||
self._is_away = False
|
||||
self._target_temp = self._saved_target_temp
|
||||
await self._async_control_heating(force=True)
|
||||
|
||||
async def async_turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
if not self._is_away:
|
||||
return
|
||||
self._is_away = False
|
||||
self._target_temp = self._saved_target_temp
|
||||
await self._async_control_heating(force=True)
|
||||
await self.async_update_ha_state()
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
"""Support for Genius Hub climate devices."""
|
||||
import logging
|
||||
from typing import Any, Awaitable, Dict, Optional, List
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_ECO, STATE_HEAT, STATE_MANUAL,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_ON_OFF)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS)
|
||||
HVAC_MODE_OFF, HVAC_MODE_HEAT, PRESET_BOOST, PRESET_ACTIVITY,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
|
@ -14,36 +14,25 @@ from . import DOMAIN
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_DURATION = 'duration'
|
||||
|
||||
GH_ZONES = ['radiator']
|
||||
|
||||
GH_SUPPORT_FLAGS = \
|
||||
SUPPORT_TARGET_TEMPERATURE | \
|
||||
SUPPORT_ON_OFF | \
|
||||
SUPPORT_OPERATION_MODE
|
||||
|
||||
GH_MAX_TEMP = 28.0
|
||||
GH_MIN_TEMP = 4.0
|
||||
|
||||
# Genius Hub Zones support only Off, Override/Boost, Footprint & Timer modes
|
||||
HA_OPMODE_TO_GH = {
|
||||
STATE_OFF: 'off',
|
||||
STATE_AUTO: 'timer',
|
||||
STATE_ECO: 'footprint',
|
||||
STATE_MANUAL: 'override',
|
||||
}
|
||||
GH_STATE_TO_HA = {
|
||||
'off': STATE_OFF,
|
||||
'timer': STATE_AUTO,
|
||||
'footprint': STATE_ECO,
|
||||
'away': None,
|
||||
'override': STATE_MANUAL,
|
||||
'early': STATE_HEAT,
|
||||
'test': None,
|
||||
'linked': None,
|
||||
'other': None,
|
||||
}
|
||||
# temperature is repeated here, as it gives access to high-precision temps
|
||||
GH_STATE_ATTRS = ['temperature', 'type', 'occupied', 'override']
|
||||
GH_STATE_ATTRS = ['mode', 'temperature', 'type', 'occupied', 'override']
|
||||
|
||||
# GeniusHub Zones support: Off, Timer, Override/Boost, Footprint & Linked modes
|
||||
HA_HVAC_TO_GH = {
|
||||
HVAC_MODE_OFF: 'off',
|
||||
HVAC_MODE_HEAT: 'timer'
|
||||
}
|
||||
GH_HVAC_TO_HA = {v: k for k, v in HA_HVAC_TO_GH.items()}
|
||||
|
||||
HA_PRESET_TO_GH = {
|
||||
PRESET_ACTIVITY: 'footprint',
|
||||
PRESET_BOOST: 'override'
|
||||
}
|
||||
GH_PRESET_TO_HA = {v: k for k, v in HA_PRESET_TO_GH.items()}
|
||||
|
||||
|
||||
async def async_setup_platform(hass, hass_config, async_add_entities,
|
||||
|
@ -63,28 +52,26 @@ class GeniusClimateZone(ClimateDevice):
|
|||
self._client = client
|
||||
self._zone = zone
|
||||
|
||||
# Only some zones have movement detectors, which allows footprint mode
|
||||
op_list = list(HA_OPMODE_TO_GH)
|
||||
if not hasattr(self._zone, 'occupied'):
|
||||
op_list.remove(STATE_ECO)
|
||||
self._operation_list = op_list
|
||||
self._supported_features = GH_SUPPORT_FLAGS
|
||||
if hasattr(self._zone, 'occupied'): # has a movement sensor
|
||||
self._preset_modes = list(HA_PRESET_TO_GH)
|
||||
else:
|
||||
self._preset_modes = [PRESET_BOOST]
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self) -> Awaitable[None]:
|
||||
"""Run when entity about to be added."""
|
||||
async_dispatcher_connect(self.hass, DOMAIN, self._refresh)
|
||||
|
||||
@callback
|
||||
def _refresh(self):
|
||||
def _refresh(self) -> None:
|
||||
self.async_schedule_update_ha_state(force_refresh=True)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
"""Return the name of the climate device."""
|
||||
return self._zone.name
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
def device_state_attributes(self) -> Dict[str, Any]:
|
||||
"""Return the device state attributes."""
|
||||
tmp = self._zone.__dict__.items()
|
||||
return {'status': {k: v for k, v in tmp if k in GH_STATE_ATTRS}}
|
||||
|
@ -95,72 +82,69 @@ class GeniusClimateZone(ClimateDevice):
|
|||
return False
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
def icon(self) -> str:
|
||||
"""Return the icon to use in the frontend UI."""
|
||||
return "mdi:radiator"
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
def current_temperature(self) -> Optional[float]:
|
||||
"""Return the current temperature."""
|
||||
return self._zone.temperature
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
def target_temperature(self) -> Optional[float]:
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._zone.setpoint
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
def min_temp(self) -> float:
|
||||
"""Return max valid temperature that can be set."""
|
||||
return GH_MIN_TEMP
|
||||
return 4.0
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
def max_temp(self) -> float:
|
||||
"""Return max valid temperature that can be set."""
|
||||
return GH_MAX_TEMP
|
||||
return 28.0
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
return self._supported_features
|
||||
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return self._operation_list
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation ie. heat, cool mode."""
|
||||
return GH_HVAC_TO_HA.get(self._zone.mode, HVAC_MODE_HEAT)
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return the current operation mode."""
|
||||
return GH_STATE_TO_HA[self._zone.mode]
|
||||
def hvac_modes(self) -> List[str]:
|
||||
"""Return the list of available hvac operation modes."""
|
||||
return list(HA_HVAC_TO_GH)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if the device is on."""
|
||||
return self._zone.mode != HA_OPMODE_TO_GH[STATE_OFF]
|
||||
def preset_mode(self) -> Optional[str]:
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
return GH_PRESET_TO_HA.get(self._zone.mode)
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
"""Set a new operation mode for this zone."""
|
||||
await self._zone.set_mode(HA_OPMODE_TO_GH[operation_mode])
|
||||
@property
|
||||
def preset_modes(self) -> Optional[List[str]]:
|
||||
"""Return a list of available preset modes."""
|
||||
return self._preset_modes
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
async def async_set_temperature(self, **kwargs) -> Awaitable[None]:
|
||||
"""Set a new target temperature for this zone."""
|
||||
await self._zone.set_override(kwargs.get(ATTR_TEMPERATURE), 3600)
|
||||
await self._zone.set_override(kwargs[ATTR_TEMPERATURE],
|
||||
kwargs.get(ATTR_DURATION, 3600))
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn on this heating zone.
|
||||
async def async_set_hvac_mode(self, hvac_mode: str) -> Awaitable[None]:
|
||||
"""Set a new hvac mode."""
|
||||
await self._zone.set_mode(HA_HVAC_TO_GH.get(hvac_mode))
|
||||
|
||||
Set a Zone to Footprint mode if they have a Room sensor, and to Timer
|
||||
mode otherwise.
|
||||
"""
|
||||
mode = STATE_ECO if hasattr(self._zone, 'occupied') else STATE_AUTO
|
||||
await self._zone.set_mode(HA_OPMODE_TO_GH[mode])
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn off this heating zone (i.e. to frost protect)."""
|
||||
await self._zone.set_mode(HA_OPMODE_TO_GH[STATE_OFF])
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> Awaitable[None]:
|
||||
"""Set a new preset mode."""
|
||||
await self._zone.set_mode(HA_PRESET_TO_GH.get(preset_mode, 'timer'))
|
||||
|
|
|
@ -537,26 +537,59 @@ class TemperatureSettingTrait(_Trait):
|
|||
]
|
||||
# We do not support "on" as we are unable to know how to restore
|
||||
# the last mode.
|
||||
hass_to_google = {
|
||||
climate.STATE_HEAT: 'heat',
|
||||
climate.STATE_COOL: 'cool',
|
||||
STATE_OFF: 'off',
|
||||
climate.STATE_AUTO: 'heatcool',
|
||||
climate.STATE_FAN_ONLY: 'fan-only',
|
||||
climate.STATE_DRY: 'dry',
|
||||
climate.STATE_ECO: 'eco'
|
||||
hvac_to_google = {
|
||||
climate.HVAC_MODE_HEAT: 'heat',
|
||||
climate.HVAC_MODE_COOL: 'cool',
|
||||
climate.HVAC_MODE_OFF: 'off',
|
||||
climate.HVAC_MODE_AUTO: 'auto',
|
||||
climate.HVAC_MODE_HEAT_COOL: 'heatcool',
|
||||
climate.HVAC_MODE_FAN_ONLY: 'fan-only',
|
||||
climate.HVAC_MODE_DRY: 'dry',
|
||||
}
|
||||
google_to_hass = {value: key for key, value in hass_to_google.items()}
|
||||
google_to_hvac = {value: key for key, value in hvac_to_google.items()}
|
||||
|
||||
preset_to_google = {
|
||||
climate.PRESET_ECO: 'eco'
|
||||
}
|
||||
google_to_preset = {value: key for key, value in preset_to_google.items()}
|
||||
|
||||
@staticmethod
|
||||
def supported(domain, features, device_class):
|
||||
"""Test if state is supported."""
|
||||
if domain == climate.DOMAIN:
|
||||
return features & climate.SUPPORT_OPERATION_MODE
|
||||
return True
|
||||
|
||||
return (domain == sensor.DOMAIN
|
||||
and device_class == sensor.DEVICE_CLASS_TEMPERATURE)
|
||||
|
||||
@property
|
||||
def climate_google_modes(self):
|
||||
"""Return supported Google modes."""
|
||||
modes = []
|
||||
attrs = self.state.attributes
|
||||
|
||||
for mode in attrs.get(climate.ATTR_HVAC_MODES, []):
|
||||
google_mode = self.hvac_to_google.get(mode)
|
||||
if google_mode and google_mode not in modes:
|
||||
modes.append(google_mode)
|
||||
|
||||
for preset in attrs.get(climate.ATTR_PRESET_MODES, []):
|
||||
google_mode = self.preset_to_google.get(preset)
|
||||
if google_mode and google_mode not in modes:
|
||||
modes.append(google_mode)
|
||||
|
||||
return modes
|
||||
|
||||
@property
|
||||
def climate_on_mode(self):
|
||||
"""Return the mode that should be considered on."""
|
||||
modes = [m for m in self.climate_google_modes if m != 'off']
|
||||
|
||||
if len(modes) == 1:
|
||||
return modes[0]
|
||||
|
||||
return None
|
||||
|
||||
def sync_attributes(self):
|
||||
"""Return temperature point and modes attributes for a sync request."""
|
||||
response = {}
|
||||
|
@ -571,18 +604,10 @@ class TemperatureSettingTrait(_Trait):
|
|||
response["queryOnlyTemperatureSetting"] = True
|
||||
|
||||
elif domain == climate.DOMAIN:
|
||||
modes = []
|
||||
supported = attrs.get(ATTR_SUPPORTED_FEATURES)
|
||||
|
||||
if supported & climate.SUPPORT_ON_OFF != 0:
|
||||
modes.append(STATE_OFF)
|
||||
modes.append(STATE_ON)
|
||||
|
||||
if supported & climate.SUPPORT_OPERATION_MODE != 0:
|
||||
for mode in attrs.get(climate.ATTR_OPERATION_LIST, []):
|
||||
google_mode = self.hass_to_google.get(mode)
|
||||
if google_mode and google_mode not in modes:
|
||||
modes.append(google_mode)
|
||||
modes = self.climate_google_modes
|
||||
on_mode = self.climate_on_mode
|
||||
if on_mode is not None:
|
||||
modes.append('on')
|
||||
response['availableThermostatModes'] = ','.join(modes)
|
||||
|
||||
return response
|
||||
|
@ -606,17 +631,14 @@ class TemperatureSettingTrait(_Trait):
|
|||
), 1)
|
||||
|
||||
elif domain == climate.DOMAIN:
|
||||
operation = attrs.get(climate.ATTR_OPERATION_MODE)
|
||||
supported = attrs.get(ATTR_SUPPORTED_FEATURES)
|
||||
operation = self.state.state
|
||||
preset = attrs.get(climate.ATTR_PRESET_MODE)
|
||||
supported = attrs.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
if (supported & climate.SUPPORT_ON_OFF
|
||||
and self.state.state == STATE_OFF):
|
||||
response['thermostatMode'] = 'off'
|
||||
elif (supported & climate.SUPPORT_OPERATION_MODE
|
||||
and operation in self.hass_to_google):
|
||||
response['thermostatMode'] = self.hass_to_google[operation]
|
||||
elif supported & climate.SUPPORT_ON_OFF:
|
||||
response['thermostatMode'] = 'on'
|
||||
if preset in self.preset_to_google:
|
||||
response['thermostatMode'] = self.preset_to_google[preset]
|
||||
else:
|
||||
response['thermostatMode'] = self.hvac_to_google.get(operation)
|
||||
|
||||
current_temp = attrs.get(climate.ATTR_CURRENT_TEMPERATURE)
|
||||
if current_temp is not None:
|
||||
|
@ -631,9 +653,9 @@ class TemperatureSettingTrait(_Trait):
|
|||
if current_humidity is not None:
|
||||
response['thermostatHumidityAmbient'] = current_humidity
|
||||
|
||||
if operation == climate.STATE_AUTO:
|
||||
if (supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH and
|
||||
supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW):
|
||||
if operation in (climate.HVAC_MODE_AUTO,
|
||||
climate.HVAC_MODE_HEAT_COOL):
|
||||
if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE:
|
||||
response['thermostatTemperatureSetpointHigh'] = \
|
||||
round(temp_util.convert(
|
||||
attrs[climate.ATTR_TARGET_TEMP_HIGH],
|
||||
|
@ -725,8 +747,7 @@ class TemperatureSettingTrait(_Trait):
|
|||
ATTR_ENTITY_ID: self.state.entity_id,
|
||||
}
|
||||
|
||||
if(supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH
|
||||
and supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW):
|
||||
if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE:
|
||||
svc_data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high
|
||||
svc_data[climate.ATTR_TARGET_TEMP_LOW] = temp_low
|
||||
else:
|
||||
|
@ -740,22 +761,40 @@ class TemperatureSettingTrait(_Trait):
|
|||
target_mode = params['thermostatMode']
|
||||
supported = self.state.attributes.get(ATTR_SUPPORTED_FEATURES)
|
||||
|
||||
if (target_mode in [STATE_ON, STATE_OFF] and
|
||||
supported & climate.SUPPORT_ON_OFF):
|
||||
if target_mode in self.google_to_preset:
|
||||
await self.hass.services.async_call(
|
||||
climate.DOMAIN,
|
||||
(SERVICE_TURN_ON
|
||||
if target_mode == STATE_ON
|
||||
else SERVICE_TURN_OFF),
|
||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
||||
blocking=True, context=data.context)
|
||||
elif supported & climate.SUPPORT_OPERATION_MODE:
|
||||
await self.hass.services.async_call(
|
||||
climate.DOMAIN, climate.SERVICE_SET_OPERATION_MODE, {
|
||||
ATTR_ENTITY_ID: self.state.entity_id,
|
||||
climate.ATTR_OPERATION_MODE:
|
||||
self.google_to_hass[target_mode],
|
||||
}, blocking=True, context=data.context)
|
||||
climate.DOMAIN, climate.SERVICE_SET_PRESET_MODE,
|
||||
{
|
||||
climate.ATTR_PRESET_MODE:
|
||||
self.google_to_preset[target_mode],
|
||||
ATTR_ENTITY_ID: self.state.entity_id
|
||||
},
|
||||
blocking=True, context=data.context
|
||||
)
|
||||
return
|
||||
|
||||
if target_mode == 'on':
|
||||
# When targetting 'on', we're going to try best effort.
|
||||
modes = [m for m in self.climate_google_modes
|
||||
if m != climate.HVAC_MODE_OFF]
|
||||
|
||||
if len(modes) == 1:
|
||||
target_mode = modes[0]
|
||||
elif 'auto' in modes:
|
||||
target_mode = 'auto'
|
||||
elif 'heatcool' in modes:
|
||||
target_mode = 'heatcool'
|
||||
else:
|
||||
raise SmartHomeError(
|
||||
ERR_FUNCTION_NOT_SUPPORTED,
|
||||
"Unable to translate 'on' to a HVAC mode.")
|
||||
|
||||
await self.hass.services.async_call(
|
||||
climate.DOMAIN, climate.SERVICE_SET_HVAC_MODE, {
|
||||
ATTR_ENTITY_ID: self.state.entity_id,
|
||||
climate.ATTR_HVAC_MODE:
|
||||
self.google_to_hvac[target_mode],
|
||||
}, blocking=True, context=data.context)
|
||||
|
||||
|
||||
@register_trait
|
||||
|
|
|
@ -39,11 +39,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
serport = connection.connection(ipaddress, port)
|
||||
serport.open()
|
||||
|
||||
for tstat in tstats.values():
|
||||
add_entities([
|
||||
HeatmiserV3Thermostat(
|
||||
heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport)
|
||||
])
|
||||
add_entities([
|
||||
HeatmiserV3Thermostat(
|
||||
heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport)
|
||||
for tstat in tstats.values()], True)
|
||||
|
||||
|
||||
class HeatmiserV3Thermostat(ClimateDevice):
|
||||
|
@ -54,11 +53,10 @@ class HeatmiserV3Thermostat(ClimateDevice):
|
|||
self.heatmiser = heatmiser
|
||||
self.serport = serport
|
||||
self._current_temperature = None
|
||||
self._target_temperature = None
|
||||
self._name = name
|
||||
self._id = device
|
||||
self.dcb = None
|
||||
self.update()
|
||||
self._target_temperature = int(self.dcb.get('roomset'))
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
@ -78,13 +76,6 @@ class HeatmiserV3Thermostat(ClimateDevice):
|
|||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
if self.dcb is not None:
|
||||
low = self.dcb.get('floortemplow ')
|
||||
high = self.dcb.get('floortemphigh')
|
||||
temp = (high * 256 + low) / 10.0
|
||||
self._current_temperature = temp
|
||||
else:
|
||||
self._current_temperature = None
|
||||
return self._current_temperature
|
||||
|
||||
@property
|
||||
|
@ -95,16 +86,17 @@ class HeatmiserV3Thermostat(ClimateDevice):
|
|||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if temperature is None:
|
||||
return
|
||||
self.heatmiser.hmSendAddress(
|
||||
self._id,
|
||||
18,
|
||||
temperature,
|
||||
1,
|
||||
self.serport)
|
||||
self._target_temperature = temperature
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data."""
|
||||
self.dcb = self.heatmiser.hmReadAddress(self._id, 'prt', self.serport)
|
||||
low = self.dcb.get('floortemplow ')
|
||||
high = self.dcb.get('floortemphigh')
|
||||
self._current_temperature = (high * 256 + low) / 10.0
|
||||
self._target_temperature = int(self.dcb.get('roomset'))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Support for the Hive devices."""
|
||||
import logging
|
||||
|
||||
from pyhiveapi import Pyhiveapi
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
|
@ -45,8 +46,6 @@ class HiveSession:
|
|||
|
||||
def setup(hass, config):
|
||||
"""Set up the Hive Component."""
|
||||
from pyhiveapi import Pyhiveapi
|
||||
|
||||
session = HiveSession()
|
||||
session.core = Pyhiveapi()
|
||||
|
||||
|
|
|
@ -1,39 +1,41 @@
|
|||
"""Support for the Hive climate devices."""
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_HEAT, SUPPORT_AUX_HEAT, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS)
|
||||
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_BOOST,
|
||||
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
|
||||
from . import DATA_HIVE, DOMAIN
|
||||
|
||||
HIVE_TO_HASS_STATE = {
|
||||
'SCHEDULE': STATE_AUTO,
|
||||
'MANUAL': STATE_HEAT,
|
||||
'ON': STATE_ON,
|
||||
'OFF': STATE_OFF,
|
||||
'SCHEDULE': HVAC_MODE_AUTO,
|
||||
'MANUAL': HVAC_MODE_HEAT,
|
||||
'OFF': HVAC_MODE_OFF,
|
||||
}
|
||||
|
||||
HASS_TO_HIVE_STATE = {
|
||||
STATE_AUTO: 'SCHEDULE',
|
||||
STATE_HEAT: 'MANUAL',
|
||||
STATE_ON: 'ON',
|
||||
STATE_OFF: 'OFF',
|
||||
HVAC_MODE_AUTO: 'SCHEDULE',
|
||||
HVAC_MODE_HEAT: 'MANUAL',
|
||||
HVAC_MODE_OFF: 'OFF',
|
||||
}
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
||||
SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_AUX_HEAT)
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
SUPPORT_HVAC = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
SUPPORT_PRESET = [PRESET_BOOST]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up Hive climate devices."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
session = hass.data.get(DATA_HIVE)
|
||||
if discovery_info["HA_DeviceType"] != "Heating":
|
||||
return
|
||||
|
||||
add_entities([HiveClimateEntity(session, discovery_info)])
|
||||
session = hass.data.get(DATA_HIVE)
|
||||
climate = HiveClimateEntity(session, discovery_info)
|
||||
|
||||
add_entities([climate])
|
||||
session.entities.append(climate)
|
||||
|
||||
|
||||
class HiveClimateEntity(ClimateDevice):
|
||||
|
@ -43,21 +45,11 @@ class HiveClimateEntity(ClimateDevice):
|
|||
"""Initialize the Climate device."""
|
||||
self.node_id = hivedevice["Hive_NodeID"]
|
||||
self.node_name = hivedevice["Hive_NodeName"]
|
||||
self.device_type = hivedevice["HA_DeviceType"]
|
||||
if self.device_type == "Heating":
|
||||
self.thermostat_node_id = hivedevice["Thermostat_NodeID"]
|
||||
self.thermostat_node_id = hivedevice["Thermostat_NodeID"]
|
||||
self.session = hivesession
|
||||
self.attributes = {}
|
||||
self.data_updatesource = '{}.{}'.format(
|
||||
self.device_type, self.node_id)
|
||||
self._unique_id = '{}-{}'.format(self.node_id, self.device_type)
|
||||
|
||||
if self.device_type == "Heating":
|
||||
self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF]
|
||||
elif self.device_type == "HotWater":
|
||||
self.modes = [STATE_AUTO, STATE_ON, STATE_OFF]
|
||||
|
||||
self.session.entities.append(self)
|
||||
self.data_updatesource = 'Heating.{}'.format(self.node_id)
|
||||
self._unique_id = '{}-Heating'.format(self.node_id)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
|
@ -81,19 +73,15 @@ class HiveClimateEntity(ClimateDevice):
|
|||
|
||||
def handle_update(self, updatesource):
|
||||
"""Handle the new update request."""
|
||||
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
|
||||
if 'Heating.{}'.format(self.node_id) not in updatesource:
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the Climate device."""
|
||||
friendly_name = "Climate Device"
|
||||
if self.device_type == "Heating":
|
||||
friendly_name = "Heating"
|
||||
if self.node_name is not None:
|
||||
friendly_name = '{} {}'.format(self.node_name, friendly_name)
|
||||
elif self.device_type == "HotWater":
|
||||
friendly_name = "Hot Water"
|
||||
friendly_name = "Heating"
|
||||
if self.node_name is not None:
|
||||
friendly_name = '{} {}'.format(self.node_name, friendly_name)
|
||||
return friendly_name
|
||||
|
||||
@property
|
||||
|
@ -101,6 +89,22 @@ class HiveClimateEntity(ClimateDevice):
|
|||
"""Show Device Attributes."""
|
||||
return self.attributes
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return SUPPORT_HVAC
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
return HIVE_TO_HASS_STATE[self.session.heating.get_mode(self.node_id)]
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement."""
|
||||
|
@ -109,48 +113,39 @@ class HiveClimateEntity(ClimateDevice):
|
|||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
if self.device_type == "Heating":
|
||||
return self.session.heating.current_temperature(self.node_id)
|
||||
return self.session.heating.current_temperature(self.node_id)
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the target temperature."""
|
||||
if self.device_type == "Heating":
|
||||
return self.session.heating.get_target_temperature(self.node_id)
|
||||
return self.session.heating.get_target_temperature(self.node_id)
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return minimum temperature."""
|
||||
if self.device_type == "Heating":
|
||||
return self.session.heating.min_temperature(self.node_id)
|
||||
return self.session.heating.min_temperature(self.node_id)
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return the maximum temperature."""
|
||||
if self.device_type == "Heating":
|
||||
return self.session.heating.max_temperature(self.node_id)
|
||||
return self.session.heating.max_temperature(self.node_id)
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of the operation modes."""
|
||||
return self.modes
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
if self.session.heating.get_boost(self.node_id) == "ON":
|
||||
return PRESET_BOOST
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current mode."""
|
||||
if self.device_type == "Heating":
|
||||
currentmode = self.session.heating.get_mode(self.node_id)
|
||||
elif self.device_type == "HotWater":
|
||||
currentmode = self.session.hotwater.get_mode(self.node_id)
|
||||
return HIVE_TO_HASS_STATE.get(currentmode)
|
||||
def preset_modes(self):
|
||||
"""Return a list of available preset modes."""
|
||||
return SUPPORT_PRESET
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new Heating mode."""
|
||||
new_mode = HASS_TO_HIVE_STATE.get(operation_mode)
|
||||
if self.device_type == "Heating":
|
||||
self.session.heating.set_mode(self.node_id, new_mode)
|
||||
elif self.device_type == "HotWater":
|
||||
self.session.hotwater.set_mode(self.node_id, new_mode)
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
new_mode = HASS_TO_HIVE_STATE[hvac_mode]
|
||||
self.session.heating.set_mode(self.node_id, new_mode)
|
||||
|
||||
for entity in self.session.entities:
|
||||
entity.handle_update(self.data_updatesource)
|
||||
|
@ -159,55 +154,29 @@ class HiveClimateEntity(ClimateDevice):
|
|||
"""Set new target temperature."""
|
||||
new_temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if new_temperature is not None:
|
||||
if self.device_type == "Heating":
|
||||
self.session.heating.set_target_temperature(self.node_id,
|
||||
new_temperature)
|
||||
self.session.heating.set_target_temperature(
|
||||
self.node_id, new_temperature)
|
||||
|
||||
for entity in self.session.entities:
|
||||
entity.handle_update(self.data_updatesource)
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
"""Return true if auxiliary heater is on."""
|
||||
boost_status = None
|
||||
if self.device_type == "Heating":
|
||||
boost_status = self.session.heating.get_boost(self.node_id)
|
||||
elif self.device_type == "HotWater":
|
||||
boost_status = self.session.hotwater.get_boost(self.node_id)
|
||||
return boost_status == "ON"
|
||||
def set_preset_mode(self, preset_mode) -> None:
|
||||
"""Set new preset mode."""
|
||||
if preset_mode is None and self.preset_mode == PRESET_BOOST:
|
||||
self.session.heating.turn_boost_off(self.node_id)
|
||||
|
||||
def turn_aux_heat_on(self):
|
||||
"""Turn auxiliary heater on."""
|
||||
target_boost_time = 30
|
||||
if self.device_type == "Heating":
|
||||
elif preset_mode == PRESET_BOOST:
|
||||
curtemp = self.session.heating.current_temperature(self.node_id)
|
||||
curtemp = round(curtemp * 2) / 2
|
||||
target_boost_temperature = curtemp + 0.5
|
||||
self.session.heating.turn_boost_on(self.node_id,
|
||||
target_boost_time,
|
||||
target_boost_temperature)
|
||||
elif self.device_type == "HotWater":
|
||||
self.session.hotwater.turn_boost_on(self.node_id,
|
||||
target_boost_time)
|
||||
temperature = curtemp + 0.5
|
||||
|
||||
for entity in self.session.entities:
|
||||
entity.handle_update(self.data_updatesource)
|
||||
|
||||
def turn_aux_heat_off(self):
|
||||
"""Turn auxiliary heater off."""
|
||||
if self.device_type == "Heating":
|
||||
self.session.heating.turn_boost_off(self.node_id)
|
||||
elif self.device_type == "HotWater":
|
||||
self.session.hotwater.turn_boost_off(self.node_id)
|
||||
self.session.heating.turn_boost_on(self.node_id, 30, temperature)
|
||||
|
||||
for entity in self.session.entities:
|
||||
entity.handle_update(self.data_updatesource)
|
||||
|
||||
def update(self):
|
||||
"""Update all Node data from Hive."""
|
||||
node = self.node_id
|
||||
if self.device_type == "Heating":
|
||||
node = self.thermostat_node_id
|
||||
|
||||
self.session.core.update_data(self.node_id)
|
||||
self.attributes = self.session.attributes.state_attributes(node)
|
||||
self.attributes = self.session.attributes.state_attributes(
|
||||
self.thermostat_node_id)
|
||||
|
|
|
@ -4,21 +4,20 @@ import logging
|
|||
from pyhap.const import CATEGORY_THERMOSTAT
|
||||
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_CURRENT_TEMPERATURE, ATTR_MAX_TEMP, ATTR_MIN_TEMP,
|
||||
ATTR_OPERATION_LIST, ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_STEP,
|
||||
DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP,
|
||||
DOMAIN as DOMAIN_CLIMATE,
|
||||
SERVICE_SET_OPERATION_MODE as SERVICE_SET_OPERATION_MODE_THERMOSTAT,
|
||||
SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT, STATE_AUTO,
|
||||
STATE_COOL, STATE_HEAT, SUPPORT_ON_OFF, SUPPORT_TARGET_TEMPERATURE_HIGH,
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
ATTR_CURRENT_TEMPERATURE, ATTR_HVAC_ACTIONS, ATTR_HVAC_MODE, ATTR_MAX_TEMP,
|
||||
ATTR_MIN_TEMP, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
ATTR_TARGET_TEMP_STEP, CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT,
|
||||
CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP,
|
||||
DOMAIN as DOMAIN_CLIMATE, HVAC_MODE_COOL, HVAC_MODE_HEAT,
|
||||
HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF,
|
||||
SERVICE_SET_HVAC_MODE as SERVICE_SET_HVAC_MODE_THERMOSTAT,
|
||||
SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||
from homeassistant.components.water_heater import (
|
||||
DOMAIN as DOMAIN_WATER_HEATER,
|
||||
SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_WATER_HEATER)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE,
|
||||
SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, TEMP_CELSIUS,
|
||||
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT)
|
||||
|
||||
from . import TYPES
|
||||
|
@ -36,12 +35,16 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
UNIT_HASS_TO_HOMEKIT = {TEMP_CELSIUS: 0, TEMP_FAHRENHEIT: 1}
|
||||
UNIT_HOMEKIT_TO_HASS = {c: s for s, c in UNIT_HASS_TO_HOMEKIT.items()}
|
||||
HC_HASS_TO_HOMEKIT = {STATE_OFF: 0, STATE_HEAT: 1,
|
||||
STATE_COOL: 2, STATE_AUTO: 3}
|
||||
HC_HASS_TO_HOMEKIT = {HVAC_MODE_OFF: 0, HVAC_MODE_HEAT: 1,
|
||||
HVAC_MODE_COOL: 2, HVAC_MODE_HEAT_COOL: 3}
|
||||
HC_HOMEKIT_TO_HASS = {c: s for s, c in HC_HASS_TO_HOMEKIT.items()}
|
||||
|
||||
SUPPORT_TEMP_RANGE = SUPPORT_TARGET_TEMPERATURE_LOW | \
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH
|
||||
HC_HASS_TO_HOMEKIT_ACTION = {
|
||||
CURRENT_HVAC_OFF: 0,
|
||||
CURRENT_HVAC_IDLE: 0,
|
||||
CURRENT_HVAC_HEAT: 1,
|
||||
CURRENT_HVAC_COOL: 2,
|
||||
}
|
||||
|
||||
|
||||
@TYPES.register('Thermostat')
|
||||
|
@ -56,7 +59,6 @@ class Thermostat(HomeAccessory):
|
|||
self._flag_temperature = False
|
||||
self._flag_coolingthresh = False
|
||||
self._flag_heatingthresh = False
|
||||
self.support_power_state = False
|
||||
min_temp, max_temp = self.get_temperature_range()
|
||||
temp_step = self.hass.states.get(self.entity_id) \
|
||||
.attributes.get(ATTR_TARGET_TEMP_STEP, 0.5)
|
||||
|
@ -65,9 +67,7 @@ class Thermostat(HomeAccessory):
|
|||
self.chars = []
|
||||
features = self.hass.states.get(self.entity_id) \
|
||||
.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if features & SUPPORT_ON_OFF:
|
||||
self.support_power_state = True
|
||||
if features & SUPPORT_TEMP_RANGE:
|
||||
if features & SUPPORT_TARGET_TEMPERATURE_RANGE:
|
||||
self.chars.extend((CHAR_COOLING_THRESHOLD_TEMPERATURE,
|
||||
CHAR_HEATING_THRESHOLD_TEMPERATURE))
|
||||
|
||||
|
@ -133,17 +133,13 @@ class Thermostat(HomeAccessory):
|
|||
_LOGGER.debug('%s: Set heat-cool to %d', self.entity_id, value)
|
||||
self._flag_heat_cool = True
|
||||
hass_value = HC_HOMEKIT_TO_HASS[value]
|
||||
if self.support_power_state is True:
|
||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||
if hass_value == STATE_OFF:
|
||||
self.call_service(DOMAIN_CLIMATE, SERVICE_TURN_OFF, params)
|
||||
return
|
||||
self.call_service(DOMAIN_CLIMATE, SERVICE_TURN_ON, params)
|
||||
params = {ATTR_ENTITY_ID: self.entity_id,
|
||||
ATTR_OPERATION_MODE: hass_value}
|
||||
params = {
|
||||
ATTR_ENTITY_ID: self.entity_id,
|
||||
ATTR_HVAC_MODE: hass_value
|
||||
}
|
||||
self.call_service(
|
||||
DOMAIN_CLIMATE, SERVICE_SET_OPERATION_MODE_THERMOSTAT,
|
||||
params, hass_value)
|
||||
DOMAIN_CLIMATE, SERVICE_SET_HVAC_MODE_THERMOSTAT, params,
|
||||
hass_value)
|
||||
|
||||
@debounce
|
||||
def set_cooling_threshold(self, value):
|
||||
|
@ -232,56 +228,18 @@ class Thermostat(HomeAccessory):
|
|||
self.char_display_units.set_value(UNIT_HASS_TO_HOMEKIT[self._unit])
|
||||
|
||||
# Update target operation mode
|
||||
operation_mode = new_state.attributes.get(ATTR_OPERATION_MODE)
|
||||
if self.support_power_state is True and new_state.state == STATE_OFF:
|
||||
self.char_target_heat_cool.set_value(0) # Off
|
||||
elif operation_mode and operation_mode in HC_HASS_TO_HOMEKIT:
|
||||
hvac_mode = new_state.state
|
||||
if hvac_mode and hvac_mode in HC_HASS_TO_HOMEKIT:
|
||||
if not self._flag_heat_cool:
|
||||
self.char_target_heat_cool.set_value(
|
||||
HC_HASS_TO_HOMEKIT[operation_mode])
|
||||
HC_HASS_TO_HOMEKIT[hvac_mode])
|
||||
self._flag_heat_cool = False
|
||||
|
||||
# Set current operation mode based on temperatures and target mode
|
||||
if self.support_power_state is True and new_state.state == STATE_OFF:
|
||||
current_operation_mode = STATE_OFF
|
||||
elif operation_mode == STATE_HEAT:
|
||||
if isinstance(target_temp, float) and current_temp < target_temp:
|
||||
current_operation_mode = STATE_HEAT
|
||||
else:
|
||||
current_operation_mode = STATE_OFF
|
||||
elif operation_mode == STATE_COOL:
|
||||
if isinstance(target_temp, float) and current_temp > target_temp:
|
||||
current_operation_mode = STATE_COOL
|
||||
else:
|
||||
current_operation_mode = STATE_OFF
|
||||
elif operation_mode == STATE_AUTO:
|
||||
# Check if auto is supported
|
||||
if self.char_cooling_thresh_temp:
|
||||
lower_temp = self.char_heating_thresh_temp.value
|
||||
upper_temp = self.char_cooling_thresh_temp.value
|
||||
if current_temp < lower_temp:
|
||||
current_operation_mode = STATE_HEAT
|
||||
elif current_temp > upper_temp:
|
||||
current_operation_mode = STATE_COOL
|
||||
else:
|
||||
current_operation_mode = STATE_OFF
|
||||
else:
|
||||
# Check if heating or cooling are supported
|
||||
heat = STATE_HEAT in new_state.attributes[ATTR_OPERATION_LIST]
|
||||
cool = STATE_COOL in new_state.attributes[ATTR_OPERATION_LIST]
|
||||
if isinstance(target_temp, float) and \
|
||||
current_temp < target_temp and heat:
|
||||
current_operation_mode = STATE_HEAT
|
||||
elif isinstance(target_temp, float) and \
|
||||
current_temp > target_temp and cool:
|
||||
current_operation_mode = STATE_COOL
|
||||
else:
|
||||
current_operation_mode = STATE_OFF
|
||||
else:
|
||||
current_operation_mode = STATE_OFF
|
||||
|
||||
self.char_current_heat_cool.set_value(
|
||||
HC_HASS_TO_HOMEKIT[current_operation_mode])
|
||||
# Set current operation mode for supported thermostats
|
||||
hvac_action = new_state.attributes.get(ATTR_HVAC_ACTIONS)
|
||||
if hvac_action:
|
||||
self.char_current_heat_cool.set_value(
|
||||
HC_HASS_TO_HOMEKIT_ACTION[hvac_action])
|
||||
|
||||
|
||||
@TYPES.register('WaterHeater')
|
||||
|
@ -337,7 +295,7 @@ class WaterHeater(HomeAccessory):
|
|||
_LOGGER.debug('%s: Set heat-cool to %d', self.entity_id, value)
|
||||
self._flag_heat_cool = True
|
||||
hass_value = HC_HOMEKIT_TO_HASS[value]
|
||||
if hass_value != STATE_HEAT:
|
||||
if hass_value != HVAC_MODE_HEAT:
|
||||
self.char_target_heat_cool.set_value(1) # Heat
|
||||
|
||||
@debounce
|
||||
|
@ -370,7 +328,7 @@ class WaterHeater(HomeAccessory):
|
|||
self.char_display_units.set_value(UNIT_HASS_TO_HOMEKIT[self._unit])
|
||||
|
||||
# Update target operation mode
|
||||
operation_mode = new_state.attributes.get(ATTR_OPERATION_MODE)
|
||||
operation_mode = new_state.state
|
||||
if operation_mode and not self._flag_heat_cool:
|
||||
self.char_target_heat_cool.set_value(1) # Heat
|
||||
self._flag_heat_cool = False
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
"""Support for Homekit climate devices."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate import (
|
||||
ClimateDevice, DEFAULT_MIN_HUMIDITY, DEFAULT_MAX_HUMIDITY,
|
||||
)
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY,
|
||||
SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS
|
||||
HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||
CURRENT_HVAC_OFF, CURRENT_HVAC_HEAT, CURRENT_HVAC_COOL,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
|
||||
from . import KNOWN_DEVICES, HomeKitEntity
|
||||
|
||||
|
@ -14,10 +16,10 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
# Map of Homekit operation modes to hass modes
|
||||
MODE_HOMEKIT_TO_HASS = {
|
||||
0: STATE_OFF,
|
||||
1: STATE_HEAT,
|
||||
2: STATE_COOL,
|
||||
3: STATE_AUTO,
|
||||
0: HVAC_MODE_OFF,
|
||||
1: HVAC_MODE_HEAT,
|
||||
2: HVAC_MODE_COOL,
|
||||
3: HVAC_MODE_HEAT_COOL,
|
||||
}
|
||||
|
||||
# Map of hass operation modes to homekit modes
|
||||
|
@ -25,6 +27,12 @@ MODE_HASS_TO_HOMEKIT = {v: k for k, v in MODE_HOMEKIT_TO_HASS.items()}
|
|||
|
||||
DEFAULT_VALID_MODES = list(MODE_HOMEKIT_TO_HASS)
|
||||
|
||||
CURRENT_MODE_HOMEKIT_TO_HASS = {
|
||||
0: CURRENT_HVAC_OFF,
|
||||
1: CURRENT_HVAC_HEAT,
|
||||
2: CURRENT_HVAC_COOL,
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
|
@ -53,6 +61,7 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||
def __init__(self, *args):
|
||||
"""Initialise the device."""
|
||||
self._state = None
|
||||
self._target_mode = None
|
||||
self._current_mode = None
|
||||
self._valid_modes = []
|
||||
self._current_temp = None
|
||||
|
@ -61,8 +70,8 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||
self._target_humidity = None
|
||||
self._min_target_temp = None
|
||||
self._max_target_temp = None
|
||||
self._min_target_humidity = None
|
||||
self._max_target_humidity = None
|
||||
self._min_target_humidity = DEFAULT_MIN_HUMIDITY
|
||||
self._max_target_humidity = DEFAULT_MAX_HUMIDITY
|
||||
super().__init__(*args)
|
||||
|
||||
def get_characteristic_types(self):
|
||||
|
@ -79,8 +88,6 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||
]
|
||||
|
||||
def _setup_heating_cooling_target(self, characteristic):
|
||||
self._features |= SUPPORT_OPERATION_MODE
|
||||
|
||||
if 'valid-values' in characteristic:
|
||||
valid_values = [
|
||||
val for val in DEFAULT_VALID_MODES
|
||||
|
@ -117,17 +124,22 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||
|
||||
if 'minValue' in characteristic:
|
||||
self._min_target_humidity = characteristic['minValue']
|
||||
self._features |= SUPPORT_TARGET_HUMIDITY_LOW
|
||||
|
||||
if 'maxValue' in characteristic:
|
||||
self._max_target_humidity = characteristic['maxValue']
|
||||
self._features |= SUPPORT_TARGET_HUMIDITY_HIGH
|
||||
|
||||
def _update_heating_cooling_current(self, value):
|
||||
self._state = MODE_HOMEKIT_TO_HASS.get(value)
|
||||
# This characteristic describes the current mode of a device,
|
||||
# e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit.
|
||||
# Can be 0 - 2 (Off, Heat, Cool)
|
||||
self._current_mode = CURRENT_MODE_HOMEKIT_TO_HASS.get(value)
|
||||
|
||||
def _update_heating_cooling_target(self, value):
|
||||
self._current_mode = MODE_HOMEKIT_TO_HASS.get(value)
|
||||
# This characteristic describes the target mode
|
||||
# E.g. should the device start heating a room if the temperature
|
||||
# falls below the target temperature.
|
||||
# Can be 0 - 3 (Off, Heat, Cool, Auto)
|
||||
self._target_mode = MODE_HOMEKIT_TO_HASS.get(value)
|
||||
|
||||
def _update_temperature_current(self, value):
|
||||
self._current_temp = value
|
||||
|
@ -157,25 +169,13 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||
'value': humidity}]
|
||||
await self._accessory.put_characteristics(characteristics)
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target operation mode."""
|
||||
characteristics = [{'aid': self._aid,
|
||||
'iid': self._chars['heating-cooling.target'],
|
||||
'value': MODE_HASS_TO_HOMEKIT[operation_mode]}]
|
||||
'value': MODE_HASS_TO_HOMEKIT[hvac_mode]}]
|
||||
await self._accessory.put_characteristics(characteristics)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the current state."""
|
||||
# If the device reports its operating mode as off, it sometimes doesn't
|
||||
# report a new state.
|
||||
if self._current_mode == STATE_OFF:
|
||||
return STATE_OFF
|
||||
|
||||
if self._state == STATE_OFF and self._current_mode != STATE_OFF:
|
||||
return STATE_IDLE
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
|
@ -221,13 +221,18 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||
return self._max_target_humidity
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
def hvac_action(self):
|
||||
"""Return the current running hvac operation."""
|
||||
return self._current_mode
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the list of available operation modes."""
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode."""
|
||||
return self._target_mode
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes."""
|
||||
return self._valid_modes
|
||||
|
||||
@property
|
||||
|
|
|
@ -3,26 +3,14 @@ import logging
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_MANUAL, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_AUTO,
|
||||
HVAC_MODE_HEAT, PRESET_BOOST, PRESET_COMFORT, PRESET_ECO)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
|
||||
from . import ATTR_DISCOVER_DEVICES, HM_ATTRIBUTE_SUPPORT, HMDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STATE_BOOST = 'boost'
|
||||
STATE_COMFORT = 'comfort'
|
||||
STATE_LOWERING = 'lowering'
|
||||
|
||||
HM_STATE_MAP = {
|
||||
'AUTO_MODE': STATE_AUTO,
|
||||
'MANU_MODE': STATE_MANUAL,
|
||||
'BOOST_MODE': STATE_BOOST,
|
||||
'COMFORT_MODE': STATE_COMFORT,
|
||||
'LOWERING_MODE': STATE_LOWERING
|
||||
}
|
||||
|
||||
HM_TEMP_MAP = [
|
||||
'ACTUAL_TEMPERATURE',
|
||||
'TEMPERATURE',
|
||||
|
@ -33,10 +21,16 @@ HM_HUMI_MAP = [
|
|||
'HUMIDITY',
|
||||
]
|
||||
|
||||
HM_PRESET_MAP = {
|
||||
"BOOST_MODE": PRESET_BOOST,
|
||||
"COMFORT_MODE": PRESET_COMFORT,
|
||||
"LOWERING_MODE": PRESET_ECO,
|
||||
}
|
||||
|
||||
HM_CONTROL_MODE = 'CONTROL_MODE'
|
||||
HMIP_CONTROL_MODE = 'SET_POINT_MODE'
|
||||
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
@ -66,40 +60,54 @@ class HMThermostat(HMDevice, ClimateDevice):
|
|||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
if HM_CONTROL_MODE not in self._data:
|
||||
return None
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
# boost mode is active
|
||||
if self._data.get('BOOST_MODE', False):
|
||||
return STATE_BOOST
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
if "MANU_MODE" in self._hmdevice.ACTIONNODE:
|
||||
if self._hm_controll_mode == self._hmdevice.MANU_MODE:
|
||||
return HVAC_MODE_HEAT
|
||||
return HVAC_MODE_AUTO
|
||||
|
||||
# HmIP uses the set_point_mode to say if its
|
||||
# auto or manual
|
||||
if HMIP_CONTROL_MODE in self._data:
|
||||
code = self._data[HMIP_CONTROL_MODE]
|
||||
# Other devices use the control_mode
|
||||
else:
|
||||
code = self._data['CONTROL_MODE']
|
||||
|
||||
# get the name of the mode
|
||||
name = HM_ATTRIBUTE_SUPPORT[HM_CONTROL_MODE][1][code]
|
||||
return name.lower()
|
||||
# Simple devices
|
||||
if self._data.get("BOOST_MODE"):
|
||||
return HVAC_MODE_AUTO
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the list of available operation modes."""
|
||||
# HMIP use set_point_mode for operation
|
||||
if HMIP_CONTROL_MODE in self._data:
|
||||
return [STATE_MANUAL, STATE_AUTO, STATE_BOOST]
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
# HM
|
||||
op_list = []
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
if "AUTO_MODE" in self._hmdevice.ACTIONNODE:
|
||||
return [HVAC_MODE_AUTO, HVAC_MODE_HEAT]
|
||||
return [HVAC_MODE_HEAT]
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
if self._data.get('BOOST_MODE', False):
|
||||
return 'boost'
|
||||
|
||||
# Get the name of the mode
|
||||
mode = HM_ATTRIBUTE_SUPPORT[HM_CONTROL_MODE][1][self._hm_controll_mode]
|
||||
mode = mode.lower()
|
||||
|
||||
# Filter HVAC states
|
||||
if mode not in (HVAC_MODE_AUTO, HVAC_MODE_HEAT):
|
||||
return None
|
||||
return mode
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Return a list of available preset modes."""
|
||||
preset_modes = []
|
||||
for mode in self._hmdevice.ACTIONNODE:
|
||||
if mode in HM_STATE_MAP:
|
||||
op_list.append(HM_STATE_MAP.get(mode))
|
||||
return op_list
|
||||
if mode in HM_PRESET_MAP:
|
||||
preset_modes.append(HM_PRESET_MAP[mode])
|
||||
return preset_modes
|
||||
|
||||
@property
|
||||
def current_humidity(self):
|
||||
|
@ -128,13 +136,21 @@ class HMThermostat(HMDevice, ClimateDevice):
|
|||
|
||||
self._hmdevice.writeNodeData(self._state, float(temperature))
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new target operation mode."""
|
||||
for mode, state in HM_STATE_MAP.items():
|
||||
if state == operation_mode:
|
||||
code = getattr(self._hmdevice, mode, 0)
|
||||
self._hmdevice.MODE = code
|
||||
return
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
if hvac_mode == HVAC_MODE_AUTO:
|
||||
self._hmdevice.MODE = self._hmdevice.AUTO_MODE
|
||||
else:
|
||||
self._hmdevice.MODE = self._hmdevice.MANU_MODE
|
||||
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
if preset_mode == PRESET_BOOST:
|
||||
self._hmdevice.MODE = self._hmdevice.BOOST_MODE
|
||||
elif preset_mode == PRESET_COMFORT:
|
||||
self._hmdevice.MODE = self._hmdevice.COMFORT_MODE
|
||||
elif preset_mode == PRESET_ECO:
|
||||
self._hmdevice.MODE = self._hmdevice.LOWERING_MODE
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
|
@ -146,6 +162,19 @@ class HMThermostat(HMDevice, ClimateDevice):
|
|||
"""Return the maximum temperature - 30.5 means on."""
|
||||
return 30.5
|
||||
|
||||
@property
|
||||
def target_temperature_step(self):
|
||||
"""Return the supported step of target temperature."""
|
||||
return 0.5
|
||||
|
||||
@property
|
||||
def _hm_controll_mode(self):
|
||||
"""Return Control mode."""
|
||||
if HMIP_CONTROL_MODE in self._data:
|
||||
return self._data[HMIP_CONTROL_MODE]
|
||||
# Homematic
|
||||
return self._data['CONTROL_MODE']
|
||||
|
||||
def _init_data_struct(self):
|
||||
"""Generate a data dict (self._data) from the Homematic metadata."""
|
||||
self._state = next(iter(self._hmdevice.WRITENODE.keys()))
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Support for HomematicIP Cloud climate devices."""
|
||||
import logging
|
||||
from typing import Awaitable
|
||||
|
||||
from homematicip.aio.device import (
|
||||
AsyncHeatingThermostat, AsyncHeatingThermostatCompact)
|
||||
|
@ -8,7 +9,8 @@ from homematicip.aio.home import AsyncHome
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_MANUAL, SUPPORT_TARGET_TEMPERATURE)
|
||||
HVAC_MODE_AUTO, HVAC_MODE_HEAT, PRESET_BOOST, PRESET_COMFORT, PRESET_ECO,
|
||||
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
@ -17,12 +19,9 @@ from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
HA_STATE_TO_HMIP = {
|
||||
STATE_AUTO: 'AUTOMATIC',
|
||||
STATE_MANUAL: 'MANUAL',
|
||||
}
|
||||
|
||||
HMIP_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_HMIP.items()}
|
||||
HMIP_AUTOMATIC_CM = 'AUTOMATIC'
|
||||
HMIP_MANUAL_CM = 'MANUAL'
|
||||
HMIP_ECO_CM = 'ECO'
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
|
@ -63,7 +62,7 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
|
|||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_TARGET_TEMPERATURE
|
||||
return SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> float:
|
||||
|
@ -83,9 +82,48 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
|
|||
return self._device.humidity
|
||||
|
||||
@property
|
||||
def current_operation(self) -> str:
|
||||
"""Return current operation ie. automatic or manual."""
|
||||
return HMIP_STATE_TO_HA.get(self._device.controlMode)
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
if self._device.boostMode:
|
||||
return HVAC_MODE_AUTO
|
||||
if self._device.controlMode == HMIP_MANUAL_CM:
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
return HVAC_MODE_AUTO
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return [HVAC_MODE_AUTO, HVAC_MODE_HEAT]
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode, e.g., home, away, temp.
|
||||
|
||||
Requires SUPPORT_PRESET_MODE.
|
||||
"""
|
||||
if self._device.boostMode:
|
||||
return PRESET_BOOST
|
||||
if self._device.controlMode == HMIP_AUTOMATIC_CM:
|
||||
return PRESET_COMFORT
|
||||
if self._device.controlMode == HMIP_ECO_CM:
|
||||
return PRESET_ECO
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Return a list of available preset modes.
|
||||
|
||||
Requires SUPPORT_PRESET_MODE.
|
||||
"""
|
||||
return [PRESET_BOOST, PRESET_COMFORT]
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
|
@ -104,6 +142,22 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
|
|||
return
|
||||
await self._device.set_point_temperature(temperature)
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: str) -> Awaitable[None]:
|
||||
"""Set new target hvac mode."""
|
||||
if hvac_mode == HVAC_MODE_AUTO:
|
||||
await self._device.set_control_mode(HMIP_AUTOMATIC_CM)
|
||||
else:
|
||||
await self._device.set_control_mode(HMIP_MANUAL_CM)
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> Awaitable[None]:
|
||||
"""Set new preset mode."""
|
||||
if self._device.boostMode and preset_mode != PRESET_BOOST:
|
||||
await self._device.set_boost(False)
|
||||
if preset_mode == PRESET_BOOST:
|
||||
await self._device.set_boost()
|
||||
elif preset_mode == PRESET_COMFORT:
|
||||
await self._device.set_control_mode(HMIP_AUTOMATIC_CM)
|
||||
|
||||
|
||||
def _get_first_heating_thermostat(heating_group: AsyncHeatingGroup):
|
||||
"""Return the first HeatingThermostat from a HeatingGroup."""
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/components/homematicip_cloud",
|
||||
"requirements": [
|
||||
"homematicip==0.10.7"
|
||||
"homematicip==0.10.9"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
|
|
|
@ -1 +1 @@
|
|||
"""Support for Honeywell Total Connect Comfort climate systems."""
|
||||
"""Support for Honeywell (US) Total Connect Comfort climate systems."""
|
||||
|
|
|
@ -1,31 +1,35 @@
|
|||
"""Support for Honeywell Total Connect Comfort climate systems."""
|
||||
import logging
|
||||
"""Support for Honeywell (US) Total Connect Comfort climate systems."""
|
||||
import datetime
|
||||
import logging
|
||||
from typing import Any, Dict, Optional, List
|
||||
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
import somecomfort
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_FAN_MODE, ATTR_FAN_LIST,
|
||||
ATTR_OPERATION_MODE, ATTR_OPERATION_LIST, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE)
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
FAN_AUTO, FAN_DIFFUSE, FAN_ON,
|
||||
SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE,
|
||||
SUPPORT_PRESET_MODE, SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF,
|
||||
HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT_COOL,
|
||||
PRESET_AWAY,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
||||
ATTR_TEMPERATURE, CONF_REGION)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_FAN = 'fan'
|
||||
ATTR_SYSTEM_MODE = 'system_mode'
|
||||
ATTR_CURRENT_OPERATION = 'equipment_output_status'
|
||||
ATTR_FAN_ACTION = 'fan_action'
|
||||
|
||||
CONF_AWAY_TEMPERATURE = 'away_temperature'
|
||||
CONF_COOL_AWAY_TEMPERATURE = 'away_cool_temperature'
|
||||
CONF_HEAT_AWAY_TEMPERATURE = 'away_heat_temperature'
|
||||
|
||||
DEFAULT_AWAY_TEMPERATURE = 16 # in C, for eu regions, the others are F/us
|
||||
DEFAULT_COOL_AWAY_TEMPERATURE = 88
|
||||
DEFAULT_HEAT_AWAY_TEMPERATURE = 61
|
||||
DEFAULT_REGION = 'eu'
|
||||
|
@ -34,8 +38,6 @@ REGIONS = ['eu', 'us']
|
|||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_AWAY_TEMPERATURE,
|
||||
default=DEFAULT_AWAY_TEMPERATURE): vol.Coerce(float),
|
||||
vol.Optional(CONF_COOL_AWAY_TEMPERATURE,
|
||||
default=DEFAULT_COOL_AWAY_TEMPERATURE): vol.Coerce(int),
|
||||
vol.Optional(CONF_HEAT_AWAY_TEMPERATURE,
|
||||
|
@ -43,191 +45,71 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(REGIONS),
|
||||
})
|
||||
|
||||
HVAC_MODE_TO_HW_MODE = {
|
||||
'SwitchOffAllowed': {HVAC_MODE_OFF: 'off'},
|
||||
'SwitchAutoAllowed': {HVAC_MODE_HEAT_COOL: 'auto'},
|
||||
'SwitchCoolAllowed': {HVAC_MODE_COOL: 'cool'},
|
||||
'SwitchHeatAllowed': {HVAC_MODE_HEAT: 'heat'},
|
||||
}
|
||||
HW_MODE_TO_HVAC_MODE = {
|
||||
'off': HVAC_MODE_OFF,
|
||||
'emheat': HVAC_MODE_HEAT,
|
||||
'heat': HVAC_MODE_HEAT,
|
||||
'cool': HVAC_MODE_COOL,
|
||||
'auto': HVAC_MODE_HEAT_COOL,
|
||||
}
|
||||
HW_MODE_TO_HA_HVAC_ACTION = {
|
||||
'off': CURRENT_HVAC_OFF,
|
||||
'fan': CURRENT_HVAC_IDLE,
|
||||
'heat': CURRENT_HVAC_HEAT,
|
||||
'cool': CURRENT_HVAC_COOL,
|
||||
}
|
||||
FAN_MODE_TO_HW = {
|
||||
'fanModeOnAllowed': {FAN_ON: 'on'},
|
||||
'fanModeAutoAllowed': {FAN_AUTO: 'auto'},
|
||||
'fanModeCirculateAllowed': {FAN_DIFFUSE: 'circulate'},
|
||||
}
|
||||
HW_FAN_MODE_TO_HA = {
|
||||
'on': FAN_ON,
|
||||
'auto': FAN_AUTO,
|
||||
'circulate': FAN_DIFFUSE,
|
||||
'follow schedule': FAN_AUTO,
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Honeywell thermostat."""
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
region = config.get(CONF_REGION)
|
||||
|
||||
if region == 'us':
|
||||
return _setup_us(username, password, config, add_entities)
|
||||
if config.get(CONF_REGION) == 'us':
|
||||
try:
|
||||
client = somecomfort.SomeComfort(username, password)
|
||||
except somecomfort.AuthError:
|
||||
_LOGGER.error("Failed to login to honeywell account %s", username)
|
||||
return
|
||||
except somecomfort.SomeComfortError as ex:
|
||||
_LOGGER.error("Failed to initialize honeywell client: %s", str(ex))
|
||||
return
|
||||
|
||||
dev_id = config.get('thermostat')
|
||||
loc_id = config.get('location')
|
||||
cool_away_temp = config.get(CONF_COOL_AWAY_TEMPERATURE)
|
||||
heat_away_temp = config.get(CONF_HEAT_AWAY_TEMPERATURE)
|
||||
|
||||
add_entities([HoneywellUSThermostat(client, device, cool_away_temp,
|
||||
heat_away_temp, username, password)
|
||||
for location in client.locations_by_id.values()
|
||||
for device in location.devices_by_id.values()
|
||||
if ((not loc_id or location.locationid == loc_id) and
|
||||
(not dev_id or device.deviceid == dev_id))])
|
||||
return
|
||||
|
||||
_LOGGER.warning(
|
||||
"The honeywell component is deprecated for EU (i.e. non-US) systems, "
|
||||
"this functionality will be removed in version 0.96. "
|
||||
"Please switch to the evohome component, "
|
||||
"The honeywell component has been deprecated for EU (i.e. non-US) "
|
||||
"systems. For EU-based systems, use the evohome component, "
|
||||
"see: https://home-assistant.io/components/evohome")
|
||||
|
||||
return _setup_round(username, password, config, add_entities)
|
||||
|
||||
|
||||
def _setup_round(username, password, config, add_entities):
|
||||
"""Set up the rounding function."""
|
||||
from evohomeclient import EvohomeClient
|
||||
|
||||
away_temp = config.get(CONF_AWAY_TEMPERATURE)
|
||||
evo_api = EvohomeClient(username, password)
|
||||
|
||||
try:
|
||||
zones = evo_api.temperatures(force_refresh=True)
|
||||
for i, zone in enumerate(zones):
|
||||
add_entities(
|
||||
[RoundThermostat(evo_api, zone['id'], i == 0, away_temp)],
|
||||
True
|
||||
)
|
||||
except requests.exceptions.RequestException as err:
|
||||
_LOGGER.error(
|
||||
"Connection error logging into the honeywell evohome web service, "
|
||||
"hint: %s", err)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
# config will be used later
|
||||
def _setup_us(username, password, config, add_entities):
|
||||
"""Set up the user."""
|
||||
import somecomfort
|
||||
|
||||
try:
|
||||
client = somecomfort.SomeComfort(username, password)
|
||||
except somecomfort.AuthError:
|
||||
_LOGGER.error("Failed to login to honeywell account %s", username)
|
||||
return False
|
||||
except somecomfort.SomeComfortError as ex:
|
||||
_LOGGER.error("Failed to initialize honeywell client: %s", str(ex))
|
||||
return False
|
||||
|
||||
dev_id = config.get('thermostat')
|
||||
loc_id = config.get('location')
|
||||
cool_away_temp = config.get(CONF_COOL_AWAY_TEMPERATURE)
|
||||
heat_away_temp = config.get(CONF_HEAT_AWAY_TEMPERATURE)
|
||||
|
||||
add_entities([HoneywellUSThermostat(client, device, cool_away_temp,
|
||||
heat_away_temp, username, password)
|
||||
for location in client.locations_by_id.values()
|
||||
for device in location.devices_by_id.values()
|
||||
if ((not loc_id or location.locationid == loc_id) and
|
||||
(not dev_id or device.deviceid == dev_id))])
|
||||
return True
|
||||
|
||||
|
||||
class RoundThermostat(ClimateDevice):
|
||||
"""Representation of a Honeywell Round Connected thermostat."""
|
||||
|
||||
def __init__(self, client, zone_id, master, away_temp):
|
||||
"""Initialize the thermostat."""
|
||||
self.client = client
|
||||
self._current_temperature = None
|
||||
self._target_temperature = None
|
||||
self._name = 'round connected'
|
||||
self._id = zone_id
|
||||
self._master = master
|
||||
self._is_dhw = False
|
||||
self._away_temp = away_temp
|
||||
self._away = False
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
supported = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE)
|
||||
if hasattr(self.client, ATTR_SYSTEM_MODE):
|
||||
supported |= SUPPORT_OPERATION_MODE
|
||||
return supported
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the honeywell, if any."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._current_temperature
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
if self._is_dhw:
|
||||
return None
|
||||
return self._target_temperature
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if temperature is None:
|
||||
return
|
||||
self.client.set_temperature(self._name, temperature)
|
||||
|
||||
@property
|
||||
def current_operation(self) -> str:
|
||||
"""Get the current operation of the system."""
|
||||
return getattr(self.client, ATTR_SYSTEM_MODE, None)
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._away
|
||||
|
||||
def set_operation_mode(self, operation_mode: str) -> None:
|
||||
"""Set the HVAC mode for the thermostat."""
|
||||
if hasattr(self.client, ATTR_SYSTEM_MODE):
|
||||
self.client.system_mode = operation_mode
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away on.
|
||||
|
||||
Honeywell does have a proprietary away mode, but it doesn't really work
|
||||
the way it should. For example: If you set a temperature manually
|
||||
it doesn't get overwritten when away mode is switched on.
|
||||
"""
|
||||
self._away = True
|
||||
self.client.set_temperature(self._name, self._away_temp)
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
self._away = False
|
||||
self.client.cancel_temp_override(self._name)
|
||||
|
||||
def update(self):
|
||||
"""Get the latest date."""
|
||||
try:
|
||||
# Only refresh if this is the "master" device,
|
||||
# others will pick up the cache
|
||||
for val in self.client.temperatures(force_refresh=self._master):
|
||||
if val['id'] == self._id:
|
||||
data = val
|
||||
|
||||
except KeyError:
|
||||
_LOGGER.error("Update failed from Honeywell server")
|
||||
self.client.user_data = None
|
||||
return
|
||||
|
||||
except StopIteration:
|
||||
_LOGGER.error("Did not receive any temperature data from the "
|
||||
"evohomeclient API")
|
||||
return
|
||||
|
||||
self._current_temperature = data['temp']
|
||||
self._target_temperature = data['setpoint']
|
||||
if data['thermostat'] == 'DOMESTIC_HOT_WATER':
|
||||
self._name = 'Hot Water'
|
||||
self._is_dhw = True
|
||||
else:
|
||||
self._name = data['name']
|
||||
self._is_dhw = False
|
||||
|
||||
# The underlying library doesn't expose the thermostat's mode
|
||||
# but we can pull it out of the big dictionary of information.
|
||||
device = self.client.devices[self._id]
|
||||
self.client.system_mode = device[
|
||||
'thermostat']['changeableValues']['mode']
|
||||
|
||||
|
||||
class HoneywellUSThermostat(ClimateDevice):
|
||||
"""Representation of a Honeywell US Thermostat."""
|
||||
|
@ -243,61 +125,132 @@ class HoneywellUSThermostat(ClimateDevice):
|
|||
self._username = username
|
||||
self._password = password
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
supported = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE)
|
||||
if hasattr(self._device, ATTR_SYSTEM_MODE):
|
||||
supported |= SUPPORT_OPERATION_MODE
|
||||
return supported
|
||||
self._supported_features = (SUPPORT_PRESET_MODE |
|
||||
SUPPORT_TARGET_TEMPERATURE |
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
_LOGGER.debug("uiData = %s ", device._data['uiData'])
|
||||
|
||||
# not all honeywell HVACs upport all modes
|
||||
mappings = [v for k, v in HVAC_MODE_TO_HW_MODE.items()
|
||||
if k in device._data['uiData']]
|
||||
self._hvac_mode_map = {k: v for d in mappings for k, v in d.items()}
|
||||
|
||||
if device._data['canControlHumidification']:
|
||||
self._supported_features |= SUPPORT_TARGET_HUMIDITY
|
||||
if device._data['uiData']['SwitchEmergencyHeatAllowed']:
|
||||
self._supported_features |= SUPPORT_AUX_HEAT
|
||||
|
||||
if not device._data['hasFan']:
|
||||
return
|
||||
|
||||
self._supported_features |= SUPPORT_FAN_MODE
|
||||
# not all honeywell fans support all modes
|
||||
mappings = [v for k, v in FAN_MODE_TO_HW.items()
|
||||
if k in device._data['fanData']]
|
||||
self._fan_mode_map = {k: v for d in mappings for k, v in d.items()}
|
||||
|
||||
@property
|
||||
def is_fan_on(self):
|
||||
"""Return true if fan is on."""
|
||||
return self._device.fan_running
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> Optional[str]:
|
||||
"""Return the name of the honeywell, if any."""
|
||||
return self._device.name
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
def device_state_attributes(self) -> Dict[str, Any]:
|
||||
"""Return the device specific state attributes."""
|
||||
# pylint: disable=protected-access
|
||||
data = {}
|
||||
if self._device._data['hasFan']:
|
||||
data[ATTR_FAN_ACTION] = \
|
||||
'running' if self._device.fan_running else 'idle'
|
||||
return data
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
return self._supported_features
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the unit of measurement."""
|
||||
return (TEMP_CELSIUS if self._device.temperature_unit == 'C'
|
||||
else TEMP_FAHRENHEIT)
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._device.current_temperature
|
||||
|
||||
@property
|
||||
def current_humidity(self):
|
||||
def current_humidity(self) -> Optional[int]:
|
||||
"""Return the current humidity."""
|
||||
return self._device.current_humidity
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation ie. heat, cool mode."""
|
||||
return HW_MODE_TO_HVAC_MODE[self._device.system_mode]
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> List[str]:
|
||||
"""Return the list of available hvac operation modes."""
|
||||
return list(self._hvac_mode_map)
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> Optional[str]:
|
||||
"""Return the current running hvac operation if supported."""
|
||||
return HW_MODE_TO_HA_HVAC_ACTION[self._device.equipment_output_status]
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> Optional[float]:
|
||||
"""Return the current temperature."""
|
||||
return self._device.current_temperature
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> Optional[float]:
|
||||
"""Return the temperature we try to reach."""
|
||||
if self._device.system_mode == 'cool':
|
||||
if self.hvac_mode == HVAC_MODE_COOL:
|
||||
return self._device.setpoint_cool
|
||||
if self.hvac_mode != HVAC_MODE_HEAT:
|
||||
return self._device.setpoint_heat
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature_high(self) -> Optional[float]:
|
||||
"""Return the highbound target temperature we try to reach."""
|
||||
return self._device.setpoint_cool
|
||||
|
||||
@property
|
||||
def target_temperature_low(self) -> Optional[float]:
|
||||
"""Return the lowbound target temperature we try to reach."""
|
||||
return self._device.setpoint_heat
|
||||
|
||||
@property
|
||||
def current_operation(self) -> str:
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
oper = getattr(self._device, ATTR_CURRENT_OPERATION, None)
|
||||
if oper == "off":
|
||||
oper = "idle"
|
||||
return oper
|
||||
def preset_mode(self) -> Optional[str]:
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
return PRESET_AWAY if self._away else None
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set target temperature."""
|
||||
@property
|
||||
def preset_modes(self) -> Optional[List[str]]:
|
||||
"""Return a list of available preset modes."""
|
||||
return [PRESET_AWAY]
|
||||
|
||||
@property
|
||||
def is_aux_heat(self) -> Optional[str]:
|
||||
"""Return true if aux heater."""
|
||||
return self._device.system_mode == 'emheat'
|
||||
|
||||
@property
|
||||
def fan_mode(self) -> Optional[str]:
|
||||
"""Return the fan setting."""
|
||||
return HW_FAN_MODE_TO_HA[self._device.fan_mode]
|
||||
|
||||
@property
|
||||
def fan_modes(self) -> Optional[List[str]]:
|
||||
"""Return the list of available fan modes."""
|
||||
return list(self._fan_mode_map)
|
||||
|
||||
def _set_temperature(self, **kwargs) -> None:
|
||||
"""Set new target temperature."""
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if temperature is None:
|
||||
return
|
||||
import somecomfort
|
||||
try:
|
||||
# Get current mode
|
||||
mode = self._device.system_mode
|
||||
|
@ -320,25 +273,31 @@ class HoneywellUSThermostat(ClimateDevice):
|
|||
except somecomfort.SomeComfortError:
|
||||
_LOGGER.error("Temperature %.1f out of range", temperature)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
import somecomfort
|
||||
data = {
|
||||
ATTR_FAN: (self.is_fan_on and 'running' or 'idle'),
|
||||
ATTR_FAN_MODE: self._device.fan_mode,
|
||||
ATTR_OPERATION_MODE: self._device.system_mode,
|
||||
}
|
||||
data[ATTR_FAN_LIST] = somecomfort.FAN_MODES
|
||||
data[ATTR_OPERATION_LIST] = somecomfort.SYSTEM_MODES
|
||||
return data
|
||||
def set_temperature(self, **kwargs) -> None:
|
||||
"""Set new target temperature."""
|
||||
if {HVAC_MODE_COOL, HVAC_MODE_HEAT} & set(self._hvac_mode_map):
|
||||
self._set_temperature(**kwargs)
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._away
|
||||
try:
|
||||
if HVAC_MODE_HEAT_COOL in self._hvac_mode_map:
|
||||
temperature = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||
if temperature:
|
||||
self._device.setpoint_cool = temperature
|
||||
temperature = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
||||
if temperature:
|
||||
self._device.setpoint_heat = temperature
|
||||
except somecomfort.SomeComfortError as err:
|
||||
_LOGGER.error("Invalid temperature %s: %s", temperature, err)
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
def set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set new target fan mode."""
|
||||
self._device.fan_mode = self._fan_mode_map[fan_mode]
|
||||
|
||||
def set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set new target hvac mode."""
|
||||
self._device.system_mode = self._hvac_mode_map[hvac_mode]
|
||||
|
||||
def _turn_away_mode_on(self) -> None:
|
||||
"""Turn away on.
|
||||
|
||||
Somecomfort does have a proprietary away mode, but it doesn't really
|
||||
|
@ -346,7 +305,6 @@ class HoneywellUSThermostat(ClimateDevice):
|
|||
it doesn't get overwritten when away mode is switched on.
|
||||
"""
|
||||
self._away = True
|
||||
import somecomfort
|
||||
try:
|
||||
# Get current mode
|
||||
mode = self._device.system_mode
|
||||
|
@ -367,10 +325,9 @@ class HoneywellUSThermostat(ClimateDevice):
|
|||
_LOGGER.error('Temperature %.1f out of range',
|
||||
getattr(self, "_{}_away_temp".format(mode)))
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
def _turn_away_mode_off(self) -> None:
|
||||
"""Turn away off."""
|
||||
self._away = False
|
||||
import somecomfort
|
||||
try:
|
||||
# Disabling all hold modes
|
||||
self._device.hold_cool = False
|
||||
|
@ -378,36 +335,27 @@ class HoneywellUSThermostat(ClimateDevice):
|
|||
except somecomfort.SomeComfortError:
|
||||
_LOGGER.error('Can not stop hold mode')
|
||||
|
||||
def set_operation_mode(self, operation_mode: str) -> None:
|
||||
"""Set the system mode (Cool, Heat, etc)."""
|
||||
if hasattr(self._device, ATTR_SYSTEM_MODE):
|
||||
self._device.system_mode = operation_mode
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
if preset_mode == PRESET_AWAY:
|
||||
self._turn_away_mode_on()
|
||||
else:
|
||||
self._turn_away_mode_off()
|
||||
|
||||
def update(self):
|
||||
"""Update the state."""
|
||||
import somecomfort
|
||||
retries = 3
|
||||
while retries > 0:
|
||||
try:
|
||||
self._device.refresh()
|
||||
break
|
||||
except (somecomfort.client.APIRateLimited, OSError,
|
||||
requests.exceptions.ReadTimeout) as exp:
|
||||
retries -= 1
|
||||
if retries == 0:
|
||||
raise exp
|
||||
if not self._retry():
|
||||
raise exp
|
||||
_LOGGER.error(
|
||||
"SomeComfort update failed, Retrying - Error: %s", exp)
|
||||
def turn_aux_heat_on(self) -> None:
|
||||
"""Turn auxiliary heater on."""
|
||||
self._device.system_mode = 'emheat'
|
||||
|
||||
def _retry(self):
|
||||
def turn_aux_heat_off(self) -> None:
|
||||
"""Turn auxiliary heater off."""
|
||||
self._device.system_mode = 'auto'
|
||||
|
||||
def _retry(self) -> bool:
|
||||
"""Recreate a new somecomfort client.
|
||||
|
||||
When we got an error, the best way to be sure that the next query
|
||||
will succeed, is to recreate a new somecomfort client.
|
||||
"""
|
||||
import somecomfort
|
||||
try:
|
||||
self._client = somecomfort.SomeComfort(
|
||||
self._username, self._password)
|
||||
|
@ -431,3 +379,20 @@ class HoneywellUSThermostat(ClimateDevice):
|
|||
|
||||
self._device = devices[0]
|
||||
return True
|
||||
|
||||
def update(self) -> None:
|
||||
"""Update the state."""
|
||||
retries = 3
|
||||
while retries > 0:
|
||||
try:
|
||||
self._device.refresh()
|
||||
break
|
||||
except (somecomfort.client.APIRateLimited, OSError,
|
||||
requests.exceptions.ReadTimeout) as exp:
|
||||
retries -= 1
|
||||
if retries == 0:
|
||||
raise exp
|
||||
if not self._retry():
|
||||
raise exp
|
||||
_LOGGER.error(
|
||||
"SomeComfort update failed, Retrying - Error: %s", exp)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
"name": "Honeywell",
|
||||
"documentation": "https://www.home-assistant.io/components/honeywell",
|
||||
"requirements": [
|
||||
"evohomeclient==0.3.2",
|
||||
"somecomfort==0.5.2"
|
||||
],
|
||||
"dependencies": [],
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
"""Support for an Intergas boiler via an InComfort/InTouch Lan2RF gateway."""
|
||||
from typing import Any, Dict, Optional, List
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE
|
||||
from homeassistant.const import (ATTR_TEMPERATURE, TEMP_CELSIUS)
|
||||
from homeassistant.components.climate.const import (
|
||||
HVAC_MODE_HEAT, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from . import DOMAIN
|
||||
|
||||
INTOUCH_SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
INTOUCH_MAX_TEMP = 30.0
|
||||
INTOUCH_MIN_TEMP = 5.0
|
||||
|
||||
|
||||
async def async_setup_platform(hass, hass_config, async_add_entities,
|
||||
discovery_info=None):
|
||||
|
@ -31,7 +29,7 @@ class InComfortClimate(ClimateDevice):
|
|||
self._room = room
|
||||
self._name = 'Room {}'.format(room.room_no)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Set up a listener when this entity is added to HA."""
|
||||
async_dispatcher_connect(self.hass, DOMAIN, self._refresh)
|
||||
|
||||
|
@ -40,51 +38,65 @@ class InComfortClimate(ClimateDevice):
|
|||
self.async_schedule_update_ha_state(force_refresh=True)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def should_poll(self) -> bool:
|
||||
"""Return False as this device should never be polled."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the climate device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
def device_state_attributes(self) -> Dict[str, Any]:
|
||||
"""Return the device state attributes."""
|
||||
return {'status': self._room.status}
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._room.room_temp
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._room.override
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return max valid temperature that can be set."""
|
||||
return INTOUCH_MIN_TEMP
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
"""Return max valid temperature that can be set."""
|
||||
return INTOUCH_MAX_TEMP
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return INTOUCH_SUPPORT_FLAGS
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation ie. heat, cool mode."""
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
@property
|
||||
def hvac_modes(self) -> List[str]:
|
||||
"""Return the list of available hvac operation modes."""
|
||||
return [HVAC_MODE_HEAT]
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> Optional[float]:
|
||||
"""Return the current temperature."""
|
||||
return self._room.room_temp
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> Optional[float]:
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._room.override
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
"""Return max valid temperature that can be set."""
|
||||
return 5.0
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
"""Return max valid temperature that can be set."""
|
||||
return 30.0
|
||||
|
||||
async def async_set_temperature(self, **kwargs) -> None:
|
||||
"""Set a new target temperature for this zone."""
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
await self._room.set_override(temperature)
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Return False as this device should never be polled."""
|
||||
return False
|
||||
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set new target hvac mode."""
|
||||
pass
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
"""Support for KNX/IP climate devices."""
|
||||
from typing import Optional, List
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_DRY, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, STATE_MANUAL,
|
||||
SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||
PRESET_ECO, PRESET_SLEEP, PRESET_AWAY, PRESET_COMFORT,
|
||||
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -41,19 +44,25 @@ DEFAULT_SETPOINT_SHIFT_MAX = 6
|
|||
DEFAULT_SETPOINT_SHIFT_MIN = -6
|
||||
# Map KNX operation modes to HA modes. This list might not be full.
|
||||
OPERATION_MODES = {
|
||||
# Map DPT 201.100 HVAC operating modes
|
||||
"Frost Protection": STATE_MANUAL,
|
||||
"Night": STATE_IDLE,
|
||||
"Standby": STATE_ECO,
|
||||
"Comfort": STATE_HEAT,
|
||||
# Map DPT 201.104 HVAC control modes
|
||||
"Fan only": STATE_FAN_ONLY,
|
||||
"Dehumidification": STATE_DRY
|
||||
"Fan only": HVAC_MODE_FAN_ONLY,
|
||||
"Dehumidification": HVAC_MODE_DRY
|
||||
}
|
||||
|
||||
OPERATION_MODES_INV = dict((
|
||||
reversed(item) for item in OPERATION_MODES.items()))
|
||||
|
||||
PRESET_MODES = {
|
||||
# Map DPT 201.100 HVAC operating modes to HA presets
|
||||
"Frost Protection": PRESET_ECO,
|
||||
"Night": PRESET_SLEEP,
|
||||
"Standby": PRESET_AWAY,
|
||||
"Comfort": PRESET_COMFORT,
|
||||
}
|
||||
|
||||
PRESET_MODES_INV = dict((
|
||||
reversed(item) for item in PRESET_MODES.items()))
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
|
||||
|
@ -167,16 +176,11 @@ class KNXClimate(ClimateDevice):
|
|||
self._unit_of_measurement = TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
support = SUPPORT_TARGET_TEMPERATURE
|
||||
if self.device.mode.supports_operation_mode:
|
||||
support |= SUPPORT_OPERATION_MODE
|
||||
if self.device.supports_on_off:
|
||||
support |= SUPPORT_ON_OFF
|
||||
return support
|
||||
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks to update hass after device was changed."""
|
||||
async def after_update_callback(device):
|
||||
"""Call after device was updated."""
|
||||
|
@ -184,17 +188,17 @@ class KNXClimate(ClimateDevice):
|
|||
self.device.register_device_updated_cb(after_update_callback)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
"""Return the name of the KNX device."""
|
||||
return self.device.name
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return self.hass.data[DATA_KNX].connected
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
def should_poll(self) -> bool:
|
||||
"""No polling needed within KNX."""
|
||||
return False
|
||||
|
||||
|
@ -211,7 +215,7 @@ class KNXClimate(ClimateDevice):
|
|||
@property
|
||||
def target_temperature_step(self):
|
||||
"""Return the supported step of target temperature."""
|
||||
return self.device.setpoint_shift_step
|
||||
return self.device.temperature_step
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
|
@ -228,7 +232,7 @@ class KNXClimate(ClimateDevice):
|
|||
"""Return the maximum temperature."""
|
||||
return self.device.target_temperature_max
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
async def async_set_temperature(self, **kwargs) -> None:
|
||||
"""Set new target temperature."""
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if temperature is None:
|
||||
|
@ -237,39 +241,74 @@ class KNXClimate(ClimateDevice):
|
|||
await self.async_update_ha_state()
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self) -> Optional[str]:
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
if self.device.supports_on_off and not self.device.is_on:
|
||||
return HVAC_MODE_OFF
|
||||
if self.device.supports_on_off and self.device.is_on:
|
||||
return HVAC_MODE_HEAT
|
||||
if self.device.mode.supports_operation_mode:
|
||||
return OPERATION_MODES.get(self.device.mode.operation_mode.value)
|
||||
return OPERATION_MODES.get(
|
||||
self.device.mode.operation_mode.value, HVAC_MODE_HEAT)
|
||||
return None
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self) -> Optional[List[str]]:
|
||||
"""Return the list of available operation modes."""
|
||||
return [OPERATION_MODES.get(operation_mode.value) for
|
||||
operation_mode in
|
||||
self.device.mode.operation_modes]
|
||||
_operations = [OPERATION_MODES.get(operation_mode.value) for
|
||||
operation_mode in
|
||||
self.device.mode.operation_modes]
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
if self.device.supports_on_off:
|
||||
_operations.append(HVAC_MODE_HEAT)
|
||||
_operations.append(HVAC_MODE_OFF)
|
||||
|
||||
return [op for op in _operations if op is not None]
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set operation mode."""
|
||||
if self.device.mode.supports_operation_mode:
|
||||
if self.device.supports_on_off and hvac_mode == HVAC_MODE_OFF:
|
||||
await self.device.turn_off()
|
||||
elif self.device.supports_on_off and hvac_mode == HVAC_MODE_HEAT:
|
||||
await self.device.turn_on()
|
||||
elif self.device.mode.supports_operation_mode:
|
||||
from xknx.knx import HVACOperationMode
|
||||
knx_operation_mode = HVACOperationMode(
|
||||
OPERATION_MODES_INV.get(operation_mode))
|
||||
OPERATION_MODES_INV.get(hvac_mode))
|
||||
await self.device.mode.set_operation_mode(knx_operation_mode)
|
||||
await self.async_update_ha_state()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the device is on."""
|
||||
if self.device.supports_on_off:
|
||||
return self.device.is_on
|
||||
def preset_mode(self) -> Optional[str]:
|
||||
"""Return the current preset mode, e.g., home, away, temp.
|
||||
|
||||
Requires SUPPORT_PRESET_MODE.
|
||||
"""
|
||||
if self.device.mode.supports_operation_mode:
|
||||
return PRESET_MODES.get(
|
||||
self.device.mode.operation_mode.value, PRESET_AWAY)
|
||||
return None
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn on."""
|
||||
await self.device.turn_on()
|
||||
@property
|
||||
def preset_modes(self) -> Optional[List[str]]:
|
||||
"""Return a list of available preset modes.
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn off."""
|
||||
await self.device.turn_off()
|
||||
Requires SUPPORT_PRESET_MODE.
|
||||
"""
|
||||
_presets = [PRESET_MODES.get(operation_mode.value) for
|
||||
operation_mode in
|
||||
self.device.mode.operation_modes]
|
||||
|
||||
return list(filter(None, _presets))
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
if self.device.mode.supports_operation_mode:
|
||||
from xknx.knx import HVACOperationMode
|
||||
knx_operation_mode = HVACOperationMode(
|
||||
PRESET_MODES_INV.get(preset_mode))
|
||||
await self.device.mode.set_operation_mode(knx_operation_mode)
|
||||
await self.async_update_ha_state()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Support for LCN climate control."""
|
||||
|
||||
import pypck
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice, const
|
||||
|
@ -53,10 +54,6 @@ class LcnClimate(LcnDevice, ClimateDevice):
|
|||
self._target_temperature = None
|
||||
self._is_on = None
|
||||
|
||||
self.support = const.SUPPORT_TARGET_TEMPERATURE
|
||||
if self.is_lockable:
|
||||
self.support |= const.SUPPORT_ON_OFF
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Run when entity about to be added to hass."""
|
||||
await super().async_added_to_hass()
|
||||
|
@ -68,7 +65,7 @@ class LcnClimate(LcnDevice, ClimateDevice):
|
|||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return self.support
|
||||
return const.SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
|
@ -86,9 +83,25 @@ class LcnClimate(LcnDevice, ClimateDevice):
|
|||
return self._target_temperature
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the device is on."""
|
||||
return self._is_on
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
if self._is_on:
|
||||
return const.HVAC_MODE_HEAT
|
||||
return const.HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
modes = [const.HVAC_MODE_HEAT]
|
||||
if self.is_lockable:
|
||||
modes.append(const.HVAC_MODE_OFF)
|
||||
return modes
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
|
@ -100,18 +113,17 @@ class LcnClimate(LcnDevice, ClimateDevice):
|
|||
"""Return the minimum temperature."""
|
||||
return self._min_temp
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn on."""
|
||||
self._is_on = True
|
||||
self.address_connection.lock_regulator(self.regulator_id, False)
|
||||
await self.async_update_ha_state()
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
if hvac_mode == const.HVAC_MODE_HEAT:
|
||||
self._is_on = True
|
||||
self.address_connection.lock_regulator(self.regulator_id, False)
|
||||
elif hvac_mode == const.HVAC_MODE_OFF:
|
||||
self._is_on = False
|
||||
self.address_connection.lock_regulator(self.regulator_id, True)
|
||||
self._target_temperature = None
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn off."""
|
||||
self._is_on = False
|
||||
self.address_connection.lock_regulator(self.regulator_id, True)
|
||||
self._target_temperature = None
|
||||
await self.async_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
@ -122,7 +134,7 @@ class LcnClimate(LcnDevice, ClimateDevice):
|
|||
self._target_temperature = temperature
|
||||
self.address_connection.var_abs(
|
||||
self.setpoint, self._target_temperature, self.unit)
|
||||
await self.async_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
def input_received(self, input_obj):
|
||||
"""Set temperature value when LCN input object is received."""
|
||||
|
@ -134,7 +146,7 @@ class LcnClimate(LcnDevice, ClimateDevice):
|
|||
input_obj.get_value().to_var_unit(self.unit)
|
||||
elif input_obj.get_var() == self.setpoint:
|
||||
self._is_on = not input_obj.get_value().is_locked_regulator()
|
||||
if self.is_on:
|
||||
if self._is_on:
|
||||
self._target_temperature = \
|
||||
input_obj.get_value().to_var_unit(self.unit)
|
||||
|
||||
|
|
|
@ -2,20 +2,26 @@
|
|||
import logging
|
||||
import socket
|
||||
|
||||
from maxcube.device import \
|
||||
MAX_DEVICE_MODE_AUTOMATIC, \
|
||||
MAX_DEVICE_MODE_MANUAL, \
|
||||
MAX_DEVICE_MODE_VACATION, \
|
||||
MAX_DEVICE_MODE_BOOST
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
HVAC_MODE_AUTO, SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
|
||||
from . import DATA_KEY
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STATE_MANUAL = 'manual'
|
||||
STATE_BOOST = 'boost'
|
||||
STATE_VACATION = 'vacation'
|
||||
PRESET_MANUAL = 'manual'
|
||||
PRESET_BOOST = 'boost'
|
||||
PRESET_VACATION = 'vacation'
|
||||
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
@ -41,8 +47,7 @@ class MaxCubeClimate(ClimateDevice):
|
|||
def __init__(self, handler, name, rf_address):
|
||||
"""Initialize MAX! Cube ClimateDevice."""
|
||||
self._name = name
|
||||
self._operation_list = [STATE_AUTO, STATE_MANUAL, STATE_BOOST,
|
||||
STATE_VACATION]
|
||||
self._operation_list = [HVAC_MODE_AUTO]
|
||||
self._rf_address = rf_address
|
||||
self._cubehandle = handler
|
||||
|
||||
|
@ -87,13 +92,12 @@ class MaxCubeClimate(ClimateDevice):
|
|||
return self.map_temperature_max_hass(device.actual_temperature)
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation (auto, manual, boost, vacation)."""
|
||||
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||
return self.map_mode_max_hass(device.mode)
|
||||
return HVAC_MODE_AUTO
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return self._operation_list
|
||||
|
||||
|
@ -120,13 +124,25 @@ class MaxCubeClimate(ClimateDevice):
|
|||
_LOGGER.error("Setting target temperature failed")
|
||||
return False
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode."""
|
||||
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||
return self.map_mode_max_hass(device.mode)
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Return available preset modes."""
|
||||
return [
|
||||
PRESET_BOOST,
|
||||
PRESET_MANUAL,
|
||||
PRESET_VACATION,
|
||||
]
|
||||
|
||||
def set_preset_mode(self, preset_mode):
|
||||
"""Set new operation mode."""
|
||||
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||
mode = self.map_mode_hass_max(operation_mode)
|
||||
|
||||
if mode is None:
|
||||
return False
|
||||
mode = self.map_mode_hass_max(preset_mode) or MAX_DEVICE_MODE_AUTOMATIC
|
||||
|
||||
with self._cubehandle.mutex:
|
||||
try:
|
||||
|
@ -148,21 +164,13 @@ class MaxCubeClimate(ClimateDevice):
|
|||
return temperature
|
||||
|
||||
@staticmethod
|
||||
def map_mode_hass_max(operation_mode):
|
||||
def map_mode_hass_max(mode):
|
||||
"""Map Home Assistant Operation Modes to MAX! Operation Modes."""
|
||||
from maxcube.device import \
|
||||
MAX_DEVICE_MODE_AUTOMATIC, \
|
||||
MAX_DEVICE_MODE_MANUAL, \
|
||||
MAX_DEVICE_MODE_VACATION, \
|
||||
MAX_DEVICE_MODE_BOOST
|
||||
|
||||
if operation_mode == STATE_AUTO:
|
||||
mode = MAX_DEVICE_MODE_AUTOMATIC
|
||||
elif operation_mode == STATE_MANUAL:
|
||||
if mode == PRESET_MANUAL:
|
||||
mode = MAX_DEVICE_MODE_MANUAL
|
||||
elif operation_mode == STATE_VACATION:
|
||||
elif mode == PRESET_VACATION:
|
||||
mode = MAX_DEVICE_MODE_VACATION
|
||||
elif operation_mode == STATE_BOOST:
|
||||
elif mode == PRESET_BOOST:
|
||||
mode = MAX_DEVICE_MODE_BOOST
|
||||
else:
|
||||
mode = None
|
||||
|
@ -172,20 +180,12 @@ class MaxCubeClimate(ClimateDevice):
|
|||
@staticmethod
|
||||
def map_mode_max_hass(mode):
|
||||
"""Map MAX! Operation Modes to Home Assistant Operation Modes."""
|
||||
from maxcube.device import \
|
||||
MAX_DEVICE_MODE_AUTOMATIC, \
|
||||
MAX_DEVICE_MODE_MANUAL, \
|
||||
MAX_DEVICE_MODE_VACATION, \
|
||||
MAX_DEVICE_MODE_BOOST
|
||||
|
||||
if mode == MAX_DEVICE_MODE_AUTOMATIC:
|
||||
operation_mode = STATE_AUTO
|
||||
elif mode == MAX_DEVICE_MODE_MANUAL:
|
||||
operation_mode = STATE_MANUAL
|
||||
if mode == MAX_DEVICE_MODE_MANUAL:
|
||||
operation_mode = PRESET_MANUAL
|
||||
elif mode == MAX_DEVICE_MODE_VACATION:
|
||||
operation_mode = STATE_VACATION
|
||||
operation_mode = PRESET_VACATION
|
||||
elif mode == MAX_DEVICE_MODE_BOOST:
|
||||
operation_mode = STATE_BOOST
|
||||
operation_mode = PRESET_BOOST
|
||||
else:
|
||||
operation_mode = None
|
||||
|
||||
|
|
|
@ -3,27 +3,26 @@ import logging
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT,
|
||||
SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
|
||||
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, PRECISION_WHOLE, STATE_IDLE, STATE_OFF, STATE_ON,
|
||||
TEMP_CELSIUS)
|
||||
ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS)
|
||||
|
||||
from . import DATA_MELISSA
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_ON_OFF | SUPPORT_TARGET_TEMPERATURE)
|
||||
SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_TARGET_TEMPERATURE)
|
||||
|
||||
OP_MODES = [
|
||||
STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT
|
||||
HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_OFF
|
||||
]
|
||||
|
||||
FAN_MODES = [
|
||||
STATE_AUTO, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
|
||||
HVAC_MODE_AUTO, SPEED_HIGH, SPEED_MEDIUM, SPEED_LOW
|
||||
]
|
||||
|
||||
|
||||
|
@ -61,15 +60,7 @@ class MelissaClimate(ClimateDevice):
|
|||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return current state."""
|
||||
if self._cur_settings is not None:
|
||||
return self._cur_settings[self._api.STATE] in (
|
||||
self._api.STATE_ON, self._api.STATE_IDLE)
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the current fan mode."""
|
||||
if self._cur_settings is not None:
|
||||
return self.melissa_fan_to_hass(
|
||||
|
@ -93,19 +84,26 @@ class MelissaClimate(ClimateDevice):
|
|||
return PRECISION_WHOLE
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return the current operation mode."""
|
||||
if self._cur_settings is not None:
|
||||
return self.melissa_op_to_hass(
|
||||
self._cur_settings[self._api.MODE])
|
||||
if self._cur_settings is None:
|
||||
return None
|
||||
|
||||
is_on = self._cur_settings[self._api.STATE] in (
|
||||
self._api.STATE_ON, self._api.STATE_IDLE)
|
||||
|
||||
if not is_on:
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
return self.melissa_op_to_hass(self._cur_settings[self._api.MODE])
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return OP_MODES
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""List of available fan modes."""
|
||||
return FAN_MODES
|
||||
|
||||
|
@ -116,13 +114,6 @@ class MelissaClimate(ClimateDevice):
|
|||
return None
|
||||
return self._cur_settings[self._api.TEMP]
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return current state."""
|
||||
if self._cur_settings is not None:
|
||||
return self.melissa_state_to_hass(
|
||||
self._cur_settings[self._api.STATE])
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement which this thermostat uses."""
|
||||
|
@ -153,19 +144,15 @@ class MelissaClimate(ClimateDevice):
|
|||
melissa_fan_mode = self.hass_fan_to_melissa(fan_mode)
|
||||
await self.async_send({self._api.FAN: melissa_fan_mode})
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set operation mode."""
|
||||
mode = self.hass_mode_to_melissa(operation_mode)
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
await self.async_send({self._api.STATE: self._api.STATE_OFF})
|
||||
return
|
||||
|
||||
mode = self.hass_mode_to_melissa(hvac_mode)
|
||||
await self.async_send({self._api.MODE: mode})
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn on device."""
|
||||
await self.async_send({self._api.STATE: self._api.STATE_ON})
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn off device."""
|
||||
await self.async_send({self._api.STATE: self._api.STATE_OFF})
|
||||
|
||||
async def async_send(self, value):
|
||||
"""Send action to service."""
|
||||
try:
|
||||
|
@ -189,26 +176,16 @@ class MelissaClimate(ClimateDevice):
|
|||
_LOGGER.warning(
|
||||
'Unable to update entity %s', self.entity_id)
|
||||
|
||||
def melissa_state_to_hass(self, state):
|
||||
"""Translate Melissa states to hass states."""
|
||||
if state == self._api.STATE_ON:
|
||||
return STATE_ON
|
||||
if state == self._api.STATE_OFF:
|
||||
return STATE_OFF
|
||||
if state == self._api.STATE_IDLE:
|
||||
return STATE_IDLE
|
||||
return None
|
||||
|
||||
def melissa_op_to_hass(self, mode):
|
||||
"""Translate Melissa modes to hass states."""
|
||||
if mode == self._api.MODE_HEAT:
|
||||
return STATE_HEAT
|
||||
return HVAC_MODE_HEAT
|
||||
if mode == self._api.MODE_COOL:
|
||||
return STATE_COOL
|
||||
return HVAC_MODE_COOL
|
||||
if mode == self._api.MODE_DRY:
|
||||
return STATE_DRY
|
||||
return HVAC_MODE_DRY
|
||||
if mode == self._api.MODE_FAN:
|
||||
return STATE_FAN_ONLY
|
||||
return HVAC_MODE_FAN_ONLY
|
||||
_LOGGER.warning(
|
||||
"Operation mode %s could not be mapped to hass", mode)
|
||||
return None
|
||||
|
@ -216,7 +193,7 @@ class MelissaClimate(ClimateDevice):
|
|||
def melissa_fan_to_hass(self, fan):
|
||||
"""Translate Melissa fan modes to hass modes."""
|
||||
if fan == self._api.FAN_AUTO:
|
||||
return STATE_AUTO
|
||||
return HVAC_MODE_AUTO
|
||||
if fan == self._api.FAN_LOW:
|
||||
return SPEED_LOW
|
||||
if fan == self._api.FAN_MEDIUM:
|
||||
|
@ -228,19 +205,19 @@ class MelissaClimate(ClimateDevice):
|
|||
|
||||
def hass_mode_to_melissa(self, mode):
|
||||
"""Translate hass states to melissa modes."""
|
||||
if mode == STATE_HEAT:
|
||||
if mode == HVAC_MODE_HEAT:
|
||||
return self._api.MODE_HEAT
|
||||
if mode == STATE_COOL:
|
||||
if mode == HVAC_MODE_COOL:
|
||||
return self._api.MODE_COOL
|
||||
if mode == STATE_DRY:
|
||||
if mode == HVAC_MODE_DRY:
|
||||
return self._api.MODE_DRY
|
||||
if mode == STATE_FAN_ONLY:
|
||||
if mode == HVAC_MODE_FAN_ONLY:
|
||||
return self._api.MODE_FAN
|
||||
_LOGGER.warning("Melissa have no setting for %s mode", mode)
|
||||
|
||||
def hass_fan_to_melissa(self, fan):
|
||||
"""Translate hass fan modes to melissa modes."""
|
||||
if fan == STATE_AUTO:
|
||||
if fan == HVAC_MODE_AUTO:
|
||||
return self._api.FAN_AUTO
|
||||
if fan == SPEED_LOW:
|
||||
return self._api.FAN_LOW
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
"""Support for mill wifi-enabled home heaters."""
|
||||
|
||||
import logging
|
||||
|
||||
from mill import Mill
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
DOMAIN, STATE_HEAT,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
|
||||
SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE)
|
||||
DOMAIN, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE, FAN_ON)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME,
|
||||
STATE_ON, STATE_OFF, TEMP_CELSIUS)
|
||||
ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
|
@ -25,8 +23,7 @@ MAX_TEMP = 35
|
|||
MIN_TEMP = 5
|
||||
SERVICE_SET_ROOM_TEMP = 'mill_set_room_temperature'
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
||||
SUPPORT_FAN_MODE)
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
|
@ -44,7 +41,6 @@ SET_ROOM_TEMP_SCHEMA = vol.Schema({
|
|||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the Mill heater."""
|
||||
from mill import Mill
|
||||
mill_data_connection = Mill(config[CONF_USERNAME],
|
||||
config[CONF_PASSWORD],
|
||||
websession=async_get_clientsession(hass))
|
||||
|
@ -85,9 +81,7 @@ class MillHeater(ClimateDevice):
|
|||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
if self._heater.is_gen1:
|
||||
return SUPPORT_FLAGS
|
||||
return SUPPORT_FLAGS | SUPPORT_ON_OFF | SUPPORT_OPERATION_MODE
|
||||
return SUPPORT_FLAGS
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
|
@ -141,21 +135,14 @@ class MillHeater(ClimateDevice):
|
|||
return self._heater.current_temp
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return STATE_ON if self._heater.fan_status == 1 else STATE_OFF
|
||||
return FAN_ON if self._heater.fan_status == 1 else HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""List of available fan modes."""
|
||||
return [STATE_ON, STATE_OFF]
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if heater is on."""
|
||||
if self._heater.is_gen1:
|
||||
return True
|
||||
return self._heater.power_status == 1
|
||||
return [FAN_ON, HVAC_MODE_OFF]
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
|
@ -168,50 +155,48 @@ class MillHeater(ClimateDevice):
|
|||
return MAX_TEMP
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation."""
|
||||
return STATE_HEAT if self.is_on else STATE_OFF
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
if self._heater.is_gen1 or self._heater.power_status == 1:
|
||||
return HVAC_MODE_HEAT
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of available operation modes."""
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
if self._heater.is_gen1:
|
||||
return None
|
||||
return [STATE_HEAT, STATE_OFF]
|
||||
return [HVAC_MODE_HEAT]
|
||||
return [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if temperature is None:
|
||||
return
|
||||
await self._conn.set_heater_temp(self._heater.device_id,
|
||||
int(temperature))
|
||||
await self._conn.set_heater_temp(
|
||||
self._heater.device_id, int(temperature))
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode):
|
||||
"""Set new target fan mode."""
|
||||
fan_status = 1 if fan_mode == STATE_ON else 0
|
||||
await self._conn.heater_control(self._heater.device_id,
|
||||
fan_status=fan_status)
|
||||
fan_status = 1 if fan_mode == FAN_ON else 0
|
||||
await self._conn.heater_control(
|
||||
self._heater.device_id, fan_status=fan_status)
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn Mill unit on."""
|
||||
await self._conn.heater_control(self._heater.device_id,
|
||||
power_status=1)
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn Mill unit off."""
|
||||
await self._conn.heater_control(self._heater.device_id,
|
||||
power_status=0)
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
if hvac_mode == HVAC_MODE_HEAT:
|
||||
await self._conn.heater_control(
|
||||
self._heater.device_id, power_status=1)
|
||||
elif hvac_mode == HVAC_MODE_OFF and not self._heater.is_gen1:
|
||||
await self._conn.heater_control(
|
||||
self._heater.device_id, power_status=0)
|
||||
|
||||
async def async_update(self):
|
||||
"""Retrieve latest state."""
|
||||
self._heater = await self._conn.update_device(self._heater.device_id)
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
"""Set operation mode."""
|
||||
if operation_mode == STATE_HEAT:
|
||||
await self.async_turn_on()
|
||||
elif operation_mode == STATE_OFF and not self._heater.is_gen1:
|
||||
await self.async_turn_off()
|
||||
else:
|
||||
_LOGGER.error("Unrecognized operation mode: %s", operation_mode)
|
||||
|
|
|
@ -5,7 +5,8 @@ import struct
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||
from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_HEAT)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, CONF_SLAVE
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
@ -23,6 +24,7 @@ DATA_TYPE_INT = 'int'
|
|||
DATA_TYPE_UINT = 'uint'
|
||||
DATA_TYPE_FLOAT = 'float'
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
|
||||
HVAC_MODES = [HVAC_MODE_HEAT]
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_CURRENT_TEMP): cv.positive_int,
|
||||
|
@ -93,6 +95,16 @@ class ModbusThermostat(ClimateDevice):
|
|||
self._current_temperature = self.read_register(
|
||||
self._current_temperature_register)
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
"""Return the current HVAC mode."""
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the possible HVAC modes."""
|
||||
return HVAC_MODES
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the climate device."""
|
||||
|
|
|
@ -7,17 +7,15 @@ from homeassistant.components import climate, mqtt
|
|||
from homeassistant.components.climate import (
|
||||
PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, ClimateDevice)
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_OPERATION_MODE, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, STATE_AUTO,
|
||||
STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT, SUPPORT_AUX_HEAT,
|
||||
SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, SUPPORT_HOLD_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
ATTR_TARGET_TEMP_LOW,
|
||||
ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH)
|
||||
ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, HVAC_MODE_AUTO, HVAC_MODE_COOL,
|
||||
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||
SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE,
|
||||
SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, PRESET_AWAY,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_OFF,
|
||||
STATE_ON)
|
||||
ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_ON)
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
@ -48,6 +46,7 @@ CONF_FAN_MODE_STATE_TOPIC = 'fan_mode_state_topic'
|
|||
CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic'
|
||||
CONF_HOLD_STATE_TEMPLATE = 'hold_state_template'
|
||||
CONF_HOLD_STATE_TOPIC = 'hold_state_topic'
|
||||
CONF_HOLD_LIST = 'hold_modes'
|
||||
CONF_MODE_COMMAND_TOPIC = 'mode_command_topic'
|
||||
CONF_MODE_LIST = 'modes'
|
||||
CONF_MODE_STATE_TEMPLATE = 'mode_state_template'
|
||||
|
@ -127,17 +126,19 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
|
|||
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
|
||||
vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_FAN_MODE_LIST,
|
||||
default=[STATE_AUTO, SPEED_LOW,
|
||||
default=[HVAC_MODE_AUTO, SPEED_LOW,
|
||||
SPEED_MEDIUM, SPEED_HIGH]): cv.ensure_list,
|
||||
vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
||||
vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
||||
vol.Optional(CONF_HOLD_LIST, default=list): cv.ensure_list,
|
||||
vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_MODE_LIST,
|
||||
default=[STATE_AUTO, STATE_OFF, STATE_COOL, STATE_HEAT,
|
||||
STATE_DRY, STATE_FAN_ONLY]): cv.ensure_list,
|
||||
default=[HVAC_MODE_AUTO, HVAC_MODE_OFF, HVAC_MODE_COOL,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY]): cv.ensure_list,
|
||||
vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
|
@ -150,7 +151,7 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
|
|||
vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
|
||||
vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_SWING_MODE_LIST,
|
||||
default=[STATE_ON, STATE_OFF]): cv.ensure_list,
|
||||
default=[STATE_ON, HVAC_MODE_OFF]): cv.ensure_list,
|
||||
vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
||||
vol.Optional(CONF_TEMP_INITIAL, default=21): cv.positive_int,
|
||||
|
@ -275,9 +276,9 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None:
|
||||
self._current_fan_mode = SPEED_LOW
|
||||
if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None:
|
||||
self._current_swing_mode = STATE_OFF
|
||||
self._current_swing_mode = HVAC_MODE_OFF
|
||||
if self._topic[CONF_MODE_STATE_TOPIC] is None:
|
||||
self._current_operation = STATE_OFF
|
||||
self._current_operation = HVAC_MODE_OFF
|
||||
self._away = False
|
||||
self._hold = None
|
||||
self._aux = False
|
||||
|
@ -442,6 +443,9 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
"""Handle receiving hold mode via MQTT."""
|
||||
payload = render_template(msg, CONF_HOLD_STATE_TEMPLATE)
|
||||
|
||||
if payload == 'off':
|
||||
payload = None
|
||||
|
||||
self._hold = payload
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
@ -500,12 +504,12 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
return self._target_temp_high
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._current_operation
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return self._config[CONF_MODE_LIST]
|
||||
|
||||
|
@ -515,27 +519,39 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
return self._config[CONF_TEMP_STEP]
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return if away mode is on."""
|
||||
return self._away
|
||||
def preset_mode(self):
|
||||
"""Return preset mode."""
|
||||
if self._hold:
|
||||
return self._hold
|
||||
if self._away:
|
||||
return PRESET_AWAY
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_hold_mode(self):
|
||||
"""Return hold mode setting."""
|
||||
return self._hold
|
||||
def preset_modes(self):
|
||||
"""Return preset modes."""
|
||||
presets = []
|
||||
|
||||
if (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) or \
|
||||
(self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None):
|
||||
presets.append(PRESET_AWAY)
|
||||
|
||||
presets.extend(self._config[CONF_HOLD_LIST])
|
||||
|
||||
return presets
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
def is_aux_heat(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._aux
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._current_fan_mode
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return self._config[CONF_FAN_MODE_LIST]
|
||||
|
||||
|
@ -552,14 +568,14 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
setattr(self, attr, temp)
|
||||
|
||||
if (self._config[CONF_SEND_IF_OFF] or
|
||||
self._current_operation != STATE_OFF):
|
||||
self._current_operation != HVAC_MODE_OFF):
|
||||
self._publish(cmnd_topic, temp)
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperatures."""
|
||||
if kwargs.get(ATTR_OPERATION_MODE) is not None:
|
||||
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
|
||||
await self.async_set_operation_mode(operation_mode)
|
||||
if kwargs.get(ATTR_HVAC_MODE) is not None:
|
||||
operation_mode = kwargs.get(ATTR_HVAC_MODE)
|
||||
await self.async_set_hvac_mode(operation_mode)
|
||||
|
||||
self._set_temperature(
|
||||
kwargs.get(ATTR_TEMPERATURE), CONF_TEMP_COMMAND_TOPIC,
|
||||
|
@ -579,7 +595,7 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
async def async_set_swing_mode(self, swing_mode):
|
||||
"""Set new swing mode."""
|
||||
if (self._config[CONF_SEND_IF_OFF] or
|
||||
self._current_operation != STATE_OFF):
|
||||
self._current_operation != HVAC_MODE_OFF):
|
||||
self._publish(CONF_SWING_MODE_COMMAND_TOPIC,
|
||||
swing_mode)
|
||||
|
||||
|
@ -590,7 +606,7 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
async def async_set_fan_mode(self, fan_mode):
|
||||
"""Set new target temperature."""
|
||||
if (self._config[CONF_SEND_IF_OFF] or
|
||||
self._current_operation != STATE_OFF):
|
||||
self._current_operation != HVAC_MODE_OFF):
|
||||
self._publish(CONF_FAN_MODE_COMMAND_TOPIC,
|
||||
fan_mode)
|
||||
|
||||
|
@ -598,58 +614,83 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
self._current_fan_mode = fan_mode
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode) -> None:
|
||||
async def async_set_hvac_mode(self, hvac_mode) -> None:
|
||||
"""Set new operation mode."""
|
||||
if (self._current_operation == STATE_OFF and
|
||||
operation_mode != STATE_OFF):
|
||||
if (self._current_operation == HVAC_MODE_OFF and
|
||||
hvac_mode != HVAC_MODE_OFF):
|
||||
self._publish(CONF_POWER_COMMAND_TOPIC,
|
||||
self._config[CONF_PAYLOAD_ON])
|
||||
elif (self._current_operation != STATE_OFF and
|
||||
operation_mode == STATE_OFF):
|
||||
elif (self._current_operation != HVAC_MODE_OFF and
|
||||
hvac_mode == HVAC_MODE_OFF):
|
||||
self._publish(CONF_POWER_COMMAND_TOPIC,
|
||||
self._config[CONF_PAYLOAD_OFF])
|
||||
|
||||
self._publish(CONF_MODE_COMMAND_TOPIC,
|
||||
operation_mode)
|
||||
hvac_mode)
|
||||
|
||||
if self._topic[CONF_MODE_STATE_TOPIC] is None:
|
||||
self._current_operation = operation_mode
|
||||
self._current_operation = hvac_mode
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def current_swing_mode(self):
|
||||
def swing_mode(self):
|
||||
"""Return the swing setting."""
|
||||
return self._current_swing_mode
|
||||
|
||||
@property
|
||||
def swing_list(self):
|
||||
def swing_modes(self):
|
||||
"""List of available swing modes."""
|
||||
return self._config[CONF_SWING_MODE_LIST]
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode):
|
||||
"""Set a preset mode."""
|
||||
if preset_mode == self.preset_mode:
|
||||
return
|
||||
|
||||
# Track if we should optimistic update the state
|
||||
optimistic_update = False
|
||||
|
||||
if self._away:
|
||||
optimistic_update = optimistic_update or self._set_away_mode(False)
|
||||
elif preset_mode == PRESET_AWAY:
|
||||
optimistic_update = optimistic_update or self._set_away_mode(True)
|
||||
|
||||
if self._hold:
|
||||
optimistic_update = optimistic_update or self._set_hold_mode(None)
|
||||
elif preset_mode not in (None, PRESET_AWAY):
|
||||
optimistic_update = (optimistic_update or
|
||||
self._set_hold_mode(preset_mode))
|
||||
|
||||
if optimistic_update:
|
||||
self.async_write_ha_state()
|
||||
|
||||
def _set_away_mode(self, state):
|
||||
"""Set away mode.
|
||||
|
||||
Returns if we should optimistically write the state.
|
||||
"""
|
||||
self._publish(CONF_AWAY_MODE_COMMAND_TOPIC,
|
||||
self._config[CONF_PAYLOAD_ON] if state
|
||||
else self._config[CONF_PAYLOAD_OFF])
|
||||
|
||||
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None:
|
||||
self._away = state
|
||||
self.async_write_ha_state()
|
||||
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None:
|
||||
return False
|
||||
|
||||
async def async_turn_away_mode_on(self):
|
||||
"""Turn away mode on."""
|
||||
self._set_away_mode(True)
|
||||
self._away = state
|
||||
return True
|
||||
|
||||
async def async_turn_away_mode_off(self):
|
||||
"""Turn away mode off."""
|
||||
self._set_away_mode(False)
|
||||
def _set_hold_mode(self, hold_mode):
|
||||
"""Set hold mode.
|
||||
|
||||
async def async_set_hold_mode(self, hold_mode):
|
||||
"""Update hold mode on."""
|
||||
self._publish(CONF_HOLD_COMMAND_TOPIC, hold_mode)
|
||||
Returns if we should optimistically write the state.
|
||||
"""
|
||||
self._publish(CONF_HOLD_COMMAND_TOPIC, hold_mode or "off")
|
||||
|
||||
if self._topic[CONF_HOLD_STATE_TOPIC] is None:
|
||||
self._hold = hold_mode
|
||||
self.async_write_ha_state()
|
||||
if self._topic[CONF_HOLD_STATE_TOPIC] is not None:
|
||||
return False
|
||||
|
||||
self._hold = hold_mode
|
||||
return True
|
||||
|
||||
def _set_aux_heat(self, state):
|
||||
self._publish(CONF_AUX_COMMAND_TOPIC,
|
||||
|
@ -679,15 +720,11 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
|
||||
if (self._topic[CONF_TEMP_LOW_STATE_TOPIC] is not None) or \
|
||||
(self._topic[CONF_TEMP_LOW_COMMAND_TOPIC] is not None):
|
||||
support |= SUPPORT_TARGET_TEMPERATURE_LOW
|
||||
support |= SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||
|
||||
if (self._topic[CONF_TEMP_HIGH_STATE_TOPIC] is not None) or \
|
||||
(self._topic[CONF_TEMP_HIGH_COMMAND_TOPIC] is not None):
|
||||
support |= SUPPORT_TARGET_TEMPERATURE_HIGH
|
||||
|
||||
if (self._topic[CONF_MODE_COMMAND_TOPIC] is not None) or \
|
||||
(self._topic[CONF_MODE_STATE_TOPIC] is not None):
|
||||
support |= SUPPORT_OPERATION_MODE
|
||||
support |= SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||
|
||||
if (self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None) or \
|
||||
(self._topic[CONF_FAN_MODE_COMMAND_TOPIC] is not None):
|
||||
|
@ -698,12 +735,10 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
support |= SUPPORT_SWING_MODE
|
||||
|
||||
if (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) or \
|
||||
(self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None):
|
||||
support |= SUPPORT_AWAY_MODE
|
||||
|
||||
if (self._topic[CONF_HOLD_STATE_TOPIC] is not None) or \
|
||||
(self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None) or \
|
||||
(self._topic[CONF_HOLD_STATE_TOPIC] is not None) or \
|
||||
(self._topic[CONF_HOLD_COMMAND_TOPIC] is not None):
|
||||
support |= SUPPORT_HOLD_MODE
|
||||
support |= SUPPORT_PRESET_MODE
|
||||
|
||||
if (self._topic[CONF_AUX_STATE_TOPIC] is not None) or \
|
||||
(self._topic[CONF_AUX_COMMAND_TOPIC] is not None):
|
||||
|
|
|
@ -2,28 +2,30 @@
|
|||
from homeassistant.components import mysensors
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO,
|
||||
STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, HVAC_MODE_AUTO,
|
||||
HVAC_MODE_COOL, HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
HVAC_MODE_OFF)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
|
||||
DICT_HA_TO_MYS = {
|
||||
STATE_AUTO: 'AutoChangeOver',
|
||||
STATE_COOL: 'CoolOn',
|
||||
STATE_HEAT: 'HeatOn',
|
||||
STATE_OFF: 'Off',
|
||||
HVAC_MODE_AUTO: 'AutoChangeOver',
|
||||
HVAC_MODE_COOL: 'CoolOn',
|
||||
HVAC_MODE_HEAT: 'HeatOn',
|
||||
HVAC_MODE_OFF: 'Off',
|
||||
}
|
||||
DICT_MYS_TO_HA = {
|
||||
'AutoChangeOver': STATE_AUTO,
|
||||
'CoolOn': STATE_COOL,
|
||||
'HeatOn': STATE_HEAT,
|
||||
'Off': STATE_OFF,
|
||||
'AutoChangeOver': HVAC_MODE_AUTO,
|
||||
'CoolOn': HVAC_MODE_COOL,
|
||||
'HeatOn': HVAC_MODE_HEAT,
|
||||
'Off': HVAC_MODE_OFF,
|
||||
}
|
||||
|
||||
FAN_LIST = ['Auto', 'Min', 'Normal', 'Max']
|
||||
OPERATION_LIST = [STATE_OFF, STATE_AUTO, STATE_COOL, STATE_HEAT]
|
||||
OPERATION_LIST = [HVAC_MODE_OFF, HVAC_MODE_AUTO, HVAC_MODE_COOL,
|
||||
HVAC_MODE_HEAT]
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
|
@ -40,15 +42,14 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice):
|
|||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
features = SUPPORT_OPERATION_MODE
|
||||
features = 0
|
||||
set_req = self.gateway.const.SetReq
|
||||
if set_req.V_HVAC_SPEED in self._values:
|
||||
features = features | SUPPORT_FAN_MODE
|
||||
if (set_req.V_HVAC_SETPOINT_COOL in self._values and
|
||||
set_req.V_HVAC_SETPOINT_HEAT in self._values):
|
||||
features = (
|
||||
features | SUPPORT_TARGET_TEMPERATURE_HIGH |
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
features | SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||
else:
|
||||
features = features | SUPPORT_TARGET_TEMPERATURE
|
||||
return features
|
||||
|
@ -102,22 +103,22 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice):
|
|||
return float(temp) if temp is not None else None
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._values.get(self.value_type)
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""List of available operation modes."""
|
||||
return OPERATION_LIST
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._values.get(self.gateway.const.SetReq.V_HVAC_SPEED)
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""List of available fan modes."""
|
||||
return FAN_LIST
|
||||
|
||||
|
@ -161,14 +162,14 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice):
|
|||
self._values[set_req.V_HVAC_SPEED] = fan_mode
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target temperature."""
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, self.value_type,
|
||||
DICT_HA_TO_MYS[operation_mode])
|
||||
DICT_HA_TO_MYS[hvac_mode])
|
||||
if self.gateway.optimistic:
|
||||
# Optimistically assume that device has changed state
|
||||
self._values[self.value_type] = operation_mode
|
||||
self._values[self.value_type] = hvac_mode
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_update(self):
|
||||
|
|
|
@ -7,8 +7,6 @@ import threading
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_AWAY_MODE, SERVICE_SET_AWAY_MODE)
|
||||
from homeassistant.const import (
|
||||
CONF_BINARY_SENSORS, CONF_FILENAME, CONF_MONITORED_CONDITIONS,
|
||||
CONF_SENSORS, CONF_STRUCTURE, EVENT_HOMEASSISTANT_START,
|
||||
|
@ -45,6 +43,9 @@ ATTR_TRIP_ID = 'trip_id'
|
|||
AWAY_MODE_AWAY = 'away'
|
||||
AWAY_MODE_HOME = 'home'
|
||||
|
||||
ATTR_AWAY_MODE = 'away_mode'
|
||||
SERVICE_SET_AWAY_MODE = 'set_away_mode'
|
||||
|
||||
SENSOR_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS): vol.All(cv.ensure_list),
|
||||
})
|
||||
|
|
|
@ -5,13 +5,12 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, STATE_AUTO, STATE_COOL,
|
||||
STATE_ECO, STATE_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, FAN_AUTO, FAN_ON,
|
||||
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||
SUPPORT_PRESET_MODE, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE, PRESET_AWAY, PRESET_ECO)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_SCAN_INTERVAL, STATE_OFF, STATE_ON, TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT)
|
||||
ATTR_TEMPERATURE, CONF_SCAN_INTERVAL, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from . import DATA_NEST, DOMAIN as NEST_DOMAIN, SIGNAL_NEST_UPDATE
|
||||
|
@ -24,6 +23,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
})
|
||||
|
||||
NEST_MODE_HEAT_COOL = 'heat-cool'
|
||||
NEST_MODE_ECO = 'eco'
|
||||
NEST_MODE_HEAT = 'heat'
|
||||
NEST_MODE_COOL = 'cool'
|
||||
NEST_MODE_OFF = 'off'
|
||||
|
||||
PRESET_MODES = [PRESET_AWAY, PRESET_ECO]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
@ -53,29 +58,28 @@ class NestThermostat(ClimateDevice):
|
|||
self._unit = temp_unit
|
||||
self.structure = structure
|
||||
self.device = device
|
||||
self._fan_list = [STATE_ON, STATE_AUTO]
|
||||
self._fan_modes = [FAN_ON, FAN_AUTO]
|
||||
|
||||
# Set the default supported features
|
||||
self._support_flags = (SUPPORT_TARGET_TEMPERATURE |
|
||||
SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE)
|
||||
SUPPORT_PRESET_MODE)
|
||||
|
||||
# Not all nest devices support cooling and heating remove unused
|
||||
self._operation_list = [STATE_OFF]
|
||||
self._operation_list = []
|
||||
|
||||
if self.device.can_heat and self.device.can_cool:
|
||||
self._operation_list.append(HVAC_MODE_AUTO)
|
||||
self._support_flags = (self._support_flags |
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||
|
||||
# Add supported nest thermostat features
|
||||
if self.device.can_heat:
|
||||
self._operation_list.append(STATE_HEAT)
|
||||
self._operation_list.append(HVAC_MODE_HEAT)
|
||||
|
||||
if self.device.can_cool:
|
||||
self._operation_list.append(STATE_COOL)
|
||||
self._operation_list.append(HVAC_MODE_COOL)
|
||||
|
||||
if self.device.can_heat and self.device.can_cool:
|
||||
self._operation_list.append(STATE_AUTO)
|
||||
self._support_flags = (self._support_flags |
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH |
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
|
||||
self._operation_list.append(STATE_ECO)
|
||||
self._operation_list.append(HVAC_MODE_OFF)
|
||||
|
||||
# feature of device
|
||||
self._has_fan = self.device.has_fan
|
||||
|
@ -151,25 +155,29 @@ class NestThermostat(ClimateDevice):
|
|||
return self._temperature
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
if self._mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]:
|
||||
if self._mode in \
|
||||
(NEST_MODE_HEAT, NEST_MODE_COOL, NEST_MODE_OFF):
|
||||
return self._mode
|
||||
if self._mode == NEST_MODE_ECO:
|
||||
# We assume the first operation in operation list is the main one
|
||||
return self._operation_list[0]
|
||||
if self._mode == NEST_MODE_HEAT_COOL:
|
||||
return STATE_AUTO
|
||||
return HVAC_MODE_AUTO
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
if self._mode not in (NEST_MODE_HEAT_COOL, STATE_ECO):
|
||||
if self._mode not in (NEST_MODE_HEAT_COOL, NEST_MODE_ECO):
|
||||
return self._target_temperature
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature_low(self):
|
||||
"""Return the lower bound temperature we try to reach."""
|
||||
if self._mode == STATE_ECO:
|
||||
if self._mode == NEST_MODE_ECO:
|
||||
return self._eco_temperature[0]
|
||||
if self._mode == NEST_MODE_HEAT_COOL:
|
||||
return self._target_temperature[0]
|
||||
|
@ -178,17 +186,12 @@ class NestThermostat(ClimateDevice):
|
|||
@property
|
||||
def target_temperature_high(self):
|
||||
"""Return the upper bound temperature we try to reach."""
|
||||
if self._mode == STATE_ECO:
|
||||
if self._mode == NEST_MODE_ECO:
|
||||
return self._eco_temperature[1]
|
||||
if self._mode == NEST_MODE_HEAT_COOL:
|
||||
return self._target_temperature[1]
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return if away mode is on."""
|
||||
return self._away
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
import nest
|
||||
|
@ -211,46 +214,69 @@ class NestThermostat(ClimateDevice):
|
|||
# restore target temperature
|
||||
self.schedule_update_ha_state(True)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set operation mode."""
|
||||
if operation_mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]:
|
||||
device_mode = operation_mode
|
||||
elif operation_mode == STATE_AUTO:
|
||||
if hvac_mode in (HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF):
|
||||
device_mode = hvac_mode
|
||||
elif hvac_mode == HVAC_MODE_AUTO:
|
||||
device_mode = NEST_MODE_HEAT_COOL
|
||||
else:
|
||||
device_mode = STATE_OFF
|
||||
device_mode = HVAC_MODE_OFF
|
||||
_LOGGER.error(
|
||||
"An error occurred while setting device mode. "
|
||||
"Invalid operation mode: %s", operation_mode)
|
||||
"Invalid operation mode: %s", hvac_mode)
|
||||
self.device.mode = device_mode
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""List of available operation modes."""
|
||||
return self._operation_list
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away on."""
|
||||
self.structure.away = True
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return current preset mode."""
|
||||
if self._away:
|
||||
return PRESET_AWAY
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
self.structure.away = False
|
||||
if self._mode == NEST_MODE_ECO:
|
||||
return PRESET_ECO
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def preset_modes(self):
|
||||
"""Return preset modes."""
|
||||
return PRESET_MODES
|
||||
|
||||
def set_preset_mode(self, preset_mode):
|
||||
"""Set preset mode."""
|
||||
if preset_mode == self.preset_mode:
|
||||
return
|
||||
|
||||
if self._away:
|
||||
self.structure.away = False
|
||||
elif preset_mode == PRESET_AWAY:
|
||||
self.structure.away = True
|
||||
|
||||
if self.preset_mode == PRESET_ECO:
|
||||
self.device.mode = self._operation_list[0]
|
||||
elif preset_mode == PRESET_ECO:
|
||||
self.device.mode = NEST_MODE_ECO
|
||||
|
||||
@property
|
||||
def fan_mode(self):
|
||||
"""Return whether the fan is on."""
|
||||
if self._has_fan:
|
||||
# Return whether the fan is on
|
||||
return STATE_ON if self._fan else STATE_AUTO
|
||||
return FAN_ON if self._fan else FAN_AUTO
|
||||
# No Fan available so disable slider
|
||||
return None
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""List of available fan modes."""
|
||||
if self._has_fan:
|
||||
return self._fan_list
|
||||
return self._fan_modes
|
||||
return None
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
"""Support for Nest Thermostat sensors."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate.const import STATE_COOL, STATE_HEAT
|
||||
from homeassistant.const import (
|
||||
CONF_MONITORED_CONDITIONS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
|
||||
from . import CONF_SENSORS, DATA_NEST, DATA_NEST_CONFIG, NestSensorDevice
|
||||
|
||||
SENSOR_TYPES = ['humidity', 'operation_mode', 'hvac_state']
|
||||
SENSOR_TYPES = ['humidity', 'operation_mode', 'hvac_mode']
|
||||
|
||||
TEMP_SENSOR_TYPES = ['temperature', 'target']
|
||||
|
||||
|
@ -20,6 +19,9 @@ PROTECT_SENSOR_TYPES = ['co_status',
|
|||
|
||||
STRUCTURE_SENSOR_TYPES = ['eta']
|
||||
|
||||
STATE_HEAT = 'heat'
|
||||
STATE_COOL = 'cool'
|
||||
|
||||
# security_state is structure level sensor, but only meaningful when
|
||||
# Nest Cam exist
|
||||
STRUCTURE_CAMERA_SENSOR_TYPES = ['security_state']
|
||||
|
@ -34,7 +36,7 @@ SENSOR_DEVICE_CLASSES = {'humidity': DEVICE_CLASS_HUMIDITY}
|
|||
VARIABLE_NAME_MAPPING = {'eta': 'eta_begin', 'operation_mode': 'mode'}
|
||||
|
||||
VALUE_MAPPING = {
|
||||
'hvac_state': {
|
||||
'hvac_mode': {
|
||||
'heating': STATE_HEAT, 'cooling': STATE_COOL, 'off': STATE_OFF}}
|
||||
|
||||
SENSOR_TYPES_DEPRECATED = ['last_ip',
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Support for Netatmo Smart thermostats."""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Optional, List
|
||||
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
@ -8,21 +9,54 @@ import voluptuous as vol
|
|||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_HEAT, SUPPORT_ON_OFF, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, STATE_MANUAL, STATE_AUTO,
|
||||
STATE_ECO, STATE_COOL)
|
||||
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||
PRESET_AWAY,
|
||||
CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE
|
||||
)
|
||||
from homeassistant.const import (
|
||||
STATE_OFF, TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_NAME)
|
||||
TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_NAME, PRECISION_HALVES)
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from .const import DATA_NETATMO_AUTH
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PRESET_FROST_GUARD = 'frost_guard'
|
||||
PRESET_MAX = 'max'
|
||||
PRESET_SCHEDULE = 'schedule'
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE)
|
||||
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF]
|
||||
SUPPORT_PRESET = [
|
||||
PRESET_AWAY, PRESET_FROST_GUARD, PRESET_SCHEDULE, PRESET_MAX,
|
||||
]
|
||||
|
||||
STATE_NETATMO_SCHEDULE = 'schedule'
|
||||
STATE_NETATMO_HG = 'hg'
|
||||
STATE_NETATMO_MAX = PRESET_MAX
|
||||
STATE_NETATMO_AWAY = PRESET_AWAY
|
||||
STATE_NETATMO_OFF = "off"
|
||||
STATE_NETATMO_MANUAL = 'manual'
|
||||
|
||||
HVAC_MAP_NETATMO = {
|
||||
STATE_NETATMO_SCHEDULE: HVAC_MODE_AUTO,
|
||||
STATE_NETATMO_HG: HVAC_MODE_AUTO,
|
||||
STATE_NETATMO_MAX: HVAC_MODE_HEAT,
|
||||
STATE_NETATMO_OFF: HVAC_MODE_OFF,
|
||||
STATE_NETATMO_MANUAL: HVAC_MODE_AUTO,
|
||||
STATE_NETATMO_AWAY: HVAC_MODE_AUTO
|
||||
}
|
||||
|
||||
CURRENT_HVAC_MAP_NETATMO = {
|
||||
True: CURRENT_HVAC_HEAT,
|
||||
False: CURRENT_HVAC_IDLE,
|
||||
}
|
||||
|
||||
CONF_HOMES = 'homes'
|
||||
CONF_ROOMS = 'rooms'
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
|
||||
|
||||
HOME_CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
|
@ -33,34 +67,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
vol.Optional(CONF_HOMES): vol.All(cv.ensure_list, [HOME_CONFIG_SCHEMA])
|
||||
})
|
||||
|
||||
STATE_NETATMO_SCHEDULE = 'schedule'
|
||||
STATE_NETATMO_HG = 'hg'
|
||||
STATE_NETATMO_MAX = 'max'
|
||||
STATE_NETATMO_AWAY = 'away'
|
||||
STATE_NETATMO_OFF = STATE_OFF
|
||||
STATE_NETATMO_MANUAL = STATE_MANUAL
|
||||
|
||||
DICT_NETATMO_TO_HA = {
|
||||
STATE_NETATMO_SCHEDULE: STATE_AUTO,
|
||||
STATE_NETATMO_HG: STATE_COOL,
|
||||
STATE_NETATMO_MAX: STATE_HEAT,
|
||||
STATE_NETATMO_AWAY: STATE_ECO,
|
||||
STATE_NETATMO_OFF: STATE_OFF,
|
||||
STATE_NETATMO_MANUAL: STATE_MANUAL
|
||||
}
|
||||
|
||||
DICT_HA_TO_NETATMO = {
|
||||
STATE_AUTO: STATE_NETATMO_SCHEDULE,
|
||||
STATE_COOL: STATE_NETATMO_HG,
|
||||
STATE_HEAT: STATE_NETATMO_MAX,
|
||||
STATE_ECO: STATE_NETATMO_AWAY,
|
||||
STATE_OFF: STATE_NETATMO_OFF,
|
||||
STATE_MANUAL: STATE_NETATMO_MANUAL
|
||||
}
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_AWAY_MODE)
|
||||
|
||||
NA_THERM = 'NATherm1'
|
||||
NA_VALVE = 'NRV'
|
||||
|
||||
|
@ -115,28 +121,26 @@ class NetatmoThermostat(ClimateDevice):
|
|||
self._data = data
|
||||
self._state = None
|
||||
self._room_id = room_id
|
||||
room_name = self._data.homedata.rooms[self._data.home][room_id]['name']
|
||||
self._name = 'netatmo_{}'.format(room_name)
|
||||
self._room_name = self._data.homedata.rooms[
|
||||
self._data.home][room_id]['name']
|
||||
self._name = 'netatmo_{}'.format(self._room_name)
|
||||
self._current_temperature = None
|
||||
self._target_temperature = None
|
||||
self._preset = None
|
||||
self._away = None
|
||||
self._module_type = self._data.room_status[room_id]['module_type']
|
||||
if self._module_type == NA_VALVE:
|
||||
self._operation_list = [DICT_NETATMO_TO_HA[STATE_NETATMO_SCHEDULE],
|
||||
DICT_NETATMO_TO_HA[STATE_NETATMO_MANUAL],
|
||||
DICT_NETATMO_TO_HA[STATE_NETATMO_AWAY],
|
||||
DICT_NETATMO_TO_HA[STATE_NETATMO_HG]]
|
||||
self._support_flags = SUPPORT_FLAGS
|
||||
elif self._module_type == NA_THERM:
|
||||
self._operation_list = [DICT_NETATMO_TO_HA[STATE_NETATMO_SCHEDULE],
|
||||
DICT_NETATMO_TO_HA[STATE_NETATMO_MANUAL],
|
||||
DICT_NETATMO_TO_HA[STATE_NETATMO_AWAY],
|
||||
DICT_NETATMO_TO_HA[STATE_NETATMO_HG],
|
||||
DICT_NETATMO_TO_HA[STATE_NETATMO_MAX],
|
||||
DICT_NETATMO_TO_HA[STATE_NETATMO_OFF]]
|
||||
self._support_flags = SUPPORT_FLAGS | SUPPORT_ON_OFF
|
||||
self._operation_mode = None
|
||||
self._operation_list = [HVAC_MODE_AUTO, HVAC_MODE_HEAT]
|
||||
self._support_flags = SUPPORT_FLAGS
|
||||
self._hvac_mode = None
|
||||
self.update_without_throttle = False
|
||||
|
||||
try:
|
||||
self._module_type = self._data.room_status[room_id]['module_type']
|
||||
except KeyError:
|
||||
_LOGGER.error("Thermostat in %s not available", room_id)
|
||||
|
||||
if self._module_type == NA_THERM:
|
||||
self._operation_list.append(HVAC_MODE_OFF)
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
|
@ -155,113 +159,86 @@ class NetatmoThermostat(ClimateDevice):
|
|||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._data.room_status[self._room_id]['current_temperature']
|
||||
return self._current_temperature
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._data.room_status[self._room_id]['target_temperature']
|
||||
return self._target_temperature
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return the current state of the thermostat."""
|
||||
return self._operation_mode
|
||||
def target_temperature_step(self) -> Optional[float]:
|
||||
"""Return the supported step of target temperature."""
|
||||
return PRECISION_HALVES
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the operation modes list."""
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode."""
|
||||
return self._hvac_mode
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes."""
|
||||
return self._operation_list
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
module_type = self._data.room_status[self._room_id]['module_type']
|
||||
if module_type not in (NA_THERM, NA_VALVE):
|
||||
return {}
|
||||
state_attributes = {
|
||||
"home_id": self._data.homedata.gethomeId(self._data.home),
|
||||
"room_id": self._room_id,
|
||||
"setpoint_default_duration": self._data.setpoint_duration,
|
||||
"away_temperature": self._data.away_temperature,
|
||||
"hg_temperature": self._data.hg_temperature,
|
||||
"operation_mode": self._operation_mode,
|
||||
"module_type": module_type,
|
||||
"module_id": self._data.room_status[self._room_id]['module_id']
|
||||
}
|
||||
if module_type == NA_THERM:
|
||||
state_attributes["boiler_status"] = self._data.boilerstatus
|
||||
elif module_type == NA_VALVE:
|
||||
state_attributes["heating_power_request"] = \
|
||||
self._data.room_status[self._room_id]['heating_power_request']
|
||||
return state_attributes
|
||||
def hvac_action(self) -> Optional[str]:
|
||||
"""Return the current running hvac operation if supported."""
|
||||
if self._module_type == NA_THERM:
|
||||
return CURRENT_HVAC_MAP_NETATMO[self._data.boilerstatus]
|
||||
# Maybe it is a valve
|
||||
if self._data.room_status[self._room_id]['heating_power_request'] > 0:
|
||||
return CURRENT_HVAC_HEAT
|
||||
return CURRENT_HVAC_IDLE
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._away
|
||||
def set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set new target hvac mode."""
|
||||
mode = None
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if on."""
|
||||
return self.target_temperature > 0
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
mode = STATE_NETATMO_OFF
|
||||
elif hvac_mode == HVAC_MODE_AUTO:
|
||||
mode = STATE_NETATMO_SCHEDULE
|
||||
elif hvac_mode == HVAC_MODE_HEAT:
|
||||
mode = STATE_NETATMO_MAX
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away on."""
|
||||
self.set_operation_mode(DICT_NETATMO_TO_HA[STATE_NETATMO_AWAY])
|
||||
self.set_preset_mode(mode)
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
self.set_operation_mode(DICT_NETATMO_TO_HA[STATE_NETATMO_SCHEDULE])
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn Netatmo off."""
|
||||
_LOGGER.debug("Switching off ...")
|
||||
self.set_operation_mode(DICT_NETATMO_TO_HA[STATE_NETATMO_OFF])
|
||||
self.update_without_throttle = True
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def turn_on(self):
|
||||
"""Turn Netatmo on."""
|
||||
_LOGGER.debug("Switching on ...")
|
||||
_LOGGER.debug("Setting temperature first to %d ...",
|
||||
self._data.hg_temperature)
|
||||
self._data.homestatus.setroomThermpoint(
|
||||
self._data.homedata.gethomeId(self._data.home),
|
||||
self._room_id, STATE_NETATMO_MANUAL, self._data.hg_temperature)
|
||||
_LOGGER.debug("Setting operation mode to schedule ...")
|
||||
self._data.homestatus.setThermmode(
|
||||
self._data.homedata.gethomeId(self._data.home),
|
||||
STATE_NETATMO_SCHEDULE)
|
||||
self.update_without_throttle = True
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
|
||||
if not self.is_on:
|
||||
self.turn_on()
|
||||
if operation_mode in [DICT_NETATMO_TO_HA[STATE_NETATMO_MAX],
|
||||
DICT_NETATMO_TO_HA[STATE_NETATMO_OFF]]:
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
if preset_mode is None:
|
||||
self._data.homestatus.setroomThermpoint(
|
||||
self._data.homedata.gethomeId(self._data.home),
|
||||
self._room_id, DICT_HA_TO_NETATMO[operation_mode])
|
||||
elif operation_mode in [DICT_NETATMO_TO_HA[STATE_NETATMO_HG],
|
||||
DICT_NETATMO_TO_HA[STATE_NETATMO_SCHEDULE],
|
||||
DICT_NETATMO_TO_HA[STATE_NETATMO_AWAY]]:
|
||||
self._data.home_id, self._room_id, "off"
|
||||
)
|
||||
if preset_mode == STATE_NETATMO_MAX:
|
||||
self._data.homestatus.setroomThermpoint(
|
||||
self._data.home_id, self._room_id, preset_mode
|
||||
)
|
||||
elif preset_mode in [
|
||||
STATE_NETATMO_SCHEDULE, STATE_NETATMO_HG, STATE_NETATMO_AWAY
|
||||
]:
|
||||
self._data.homestatus.setThermmode(
|
||||
self._data.homedata.gethomeId(self._data.home),
|
||||
DICT_HA_TO_NETATMO[operation_mode])
|
||||
self.update_without_throttle = True
|
||||
self.schedule_update_ha_state()
|
||||
self._data.home_id, preset_mode
|
||||
)
|
||||
|
||||
@property
|
||||
def preset_mode(self) -> Optional[str]:
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
return self._preset
|
||||
|
||||
@property
|
||||
def preset_modes(self) -> Optional[List[str]]:
|
||||
"""Return a list of available preset modes."""
|
||||
return SUPPORT_PRESET
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature for 2 hours."""
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
if temp is None:
|
||||
return
|
||||
mode = STATE_NETATMO_MANUAL
|
||||
self._data.homestatus.setroomThermpoint(
|
||||
self._data.homedata.gethomeId(self._data.home),
|
||||
self._room_id, DICT_HA_TO_NETATMO[mode], temp)
|
||||
self._room_id, STATE_NETATMO_MANUAL, temp)
|
||||
self.update_without_throttle = True
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
|
@ -277,12 +254,20 @@ class NetatmoThermostat(ClimateDevice):
|
|||
_LOGGER.error("NetatmoThermostat::update() "
|
||||
"got exception.")
|
||||
return
|
||||
self._target_temperature = \
|
||||
self._data.room_status[self._room_id]['target_temperature']
|
||||
self._operation_mode = DICT_NETATMO_TO_HA[
|
||||
self._data.room_status[self._room_id]['setpoint_mode']]
|
||||
self._away = self._operation_mode == DICT_NETATMO_TO_HA[
|
||||
STATE_NETATMO_AWAY]
|
||||
try:
|
||||
self._current_temperature = \
|
||||
self._data.room_status[self._room_id]['current_temperature']
|
||||
self._target_temperature = \
|
||||
self._data.room_status[self._room_id]['target_temperature']
|
||||
self._preset = \
|
||||
self._data.room_status[self._room_id]["setpoint_mode"]
|
||||
except KeyError:
|
||||
_LOGGER.error(
|
||||
"The thermostat in room %s seems to be out of reach.",
|
||||
self._room_id
|
||||
)
|
||||
self._hvac_mode = HVAC_MAP_NETATMO[self._preset]
|
||||
self._away = self._hvac_mode == HVAC_MAP_NETATMO[STATE_NETATMO_AWAY]
|
||||
|
||||
|
||||
class HomeData:
|
||||
|
|
|
@ -6,8 +6,8 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
DOMAIN, STATE_AUTO, STATE_HEAT, STATE_IDLE, SUPPORT_HOLD_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_PRESET_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -17,29 +17,26 @@ from . import DOMAIN as NUHEAT_DOMAIN
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ICON = "mdi:thermometer"
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
|
||||
|
||||
# Hold modes
|
||||
MODE_AUTO = STATE_AUTO # Run device schedule
|
||||
MODE_AUTO = HVAC_MODE_AUTO # Run device schedule
|
||||
MODE_HOLD_TEMPERATURE = "temperature"
|
||||
MODE_TEMPORARY_HOLD = "temporary_temperature"
|
||||
|
||||
OPERATION_LIST = [STATE_HEAT, STATE_IDLE]
|
||||
OPERATION_LIST = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
|
||||
SCHEDULE_HOLD = 3
|
||||
SCHEDULE_RUN = 1
|
||||
SCHEDULE_TEMPORARY_HOLD = 2
|
||||
|
||||
SERVICE_RESUME_PROGRAM = "nuheat_resume_program"
|
||||
SERVICE_RESUME_PROGRAM = "resume_program"
|
||||
|
||||
RESUME_PROGRAM_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids
|
||||
})
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_HOLD_MODE |
|
||||
SUPPORT_OPERATION_MODE)
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
@ -70,7 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
thermostat.schedule_update_ha_state(True)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_RESUME_PROGRAM, resume_program_set_service,
|
||||
NUHEAT_DOMAIN, SERVICE_RESUME_PROGRAM, resume_program_set_service,
|
||||
schema=RESUME_PROGRAM_SCHEMA)
|
||||
|
||||
|
||||
|
@ -88,11 +85,6 @@ class NuHeatThermostat(ClimateDevice):
|
|||
"""Return the name of the thermostat."""
|
||||
return self._thermostat.room
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend."""
|
||||
return ICON
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
|
@ -115,12 +107,12 @@ class NuHeatThermostat(ClimateDevice):
|
|||
return self._thermostat.fahrenheit
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation. ie. heat, idle."""
|
||||
if self._thermostat.heating:
|
||||
return STATE_HEAT
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
return STATE_IDLE
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
|
@ -147,8 +139,8 @@ class NuHeatThermostat(ClimateDevice):
|
|||
return self._thermostat.target_fahrenheit
|
||||
|
||||
@property
|
||||
def current_hold_mode(self):
|
||||
"""Return current hold mode."""
|
||||
def preset_mode(self):
|
||||
"""Return current preset mode."""
|
||||
schedule_mode = self._thermostat.schedule_mode
|
||||
if schedule_mode == SCHEDULE_RUN:
|
||||
return MODE_AUTO
|
||||
|
@ -162,7 +154,15 @@ class NuHeatThermostat(ClimateDevice):
|
|||
return MODE_AUTO
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def preset_modes(self):
|
||||
"""Return available preset modes."""
|
||||
return [
|
||||
MODE_HOLD_TEMPERATURE,
|
||||
MODE_TEMPORARY_HOLD
|
||||
]
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return list of possible operation modes."""
|
||||
return OPERATION_LIST
|
||||
|
||||
|
@ -171,15 +171,15 @@ class NuHeatThermostat(ClimateDevice):
|
|||
self._thermostat.resume_schedule()
|
||||
self._force_update = True
|
||||
|
||||
def set_hold_mode(self, hold_mode):
|
||||
def set_preset_mode(self, preset_mode):
|
||||
"""Update the hold mode of the thermostat."""
|
||||
if hold_mode == MODE_AUTO:
|
||||
if preset_mode is None:
|
||||
schedule_mode = SCHEDULE_RUN
|
||||
|
||||
if hold_mode == MODE_HOLD_TEMPERATURE:
|
||||
elif preset_mode == MODE_HOLD_TEMPERATURE:
|
||||
schedule_mode = SCHEDULE_HOLD
|
||||
|
||||
if hold_mode == MODE_TEMPORARY_HOLD:
|
||||
elif preset_mode == MODE_TEMPORARY_HOLD:
|
||||
schedule_mode = SCHEDULE_TEMPORARY_HOLD
|
||||
|
||||
self._thermostat.schedule_mode = schedule_mode
|
||||
|
|
|
@ -1,29 +1,21 @@
|
|||
"""
|
||||
OpenEnergyMonitor Thermostat Support.
|
||||
|
||||
This provides a climate component for the ESP8266 based thermostat sold by
|
||||
OpenEnergyMonitor.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.oem/
|
||||
"""
|
||||
"""OpenEnergyMonitor Thermostat Support."""
|
||||
import logging
|
||||
|
||||
from oemthermostat import Thermostat
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
# Import the device class from the component that you want to support
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_HEAT, STATE_IDLE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_AWAY_MODE)
|
||||
CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, HVAC_MODE_AUTO,
|
||||
HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_USERNAME, CONF_PASSWORD,
|
||||
CONF_PORT, TEMP_CELSIUS, CONF_NAME)
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT,
|
||||
CONF_USERNAME, TEMP_CELSIUS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_AWAY_TEMP = 'away_temp'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
|
@ -31,22 +23,19 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
vol.Optional(CONF_PORT, default=80): cv.port,
|
||||
vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string,
|
||||
vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string,
|
||||
vol.Optional(CONF_AWAY_TEMP, default=14): vol.Coerce(float)
|
||||
})
|
||||
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
|
||||
SUPPORT_HVAC = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the oemthermostat platform."""
|
||||
from oemthermostat import Thermostat
|
||||
|
||||
name = config.get(CONF_NAME)
|
||||
host = config.get(CONF_HOST)
|
||||
port = config.get(CONF_PORT)
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
away_temp = config.get(CONF_AWAY_TEMP)
|
||||
|
||||
try:
|
||||
therm = Thermostat(
|
||||
|
@ -54,36 +43,48 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
except (ValueError, AssertionError, requests.RequestException):
|
||||
return False
|
||||
|
||||
add_entities((ThermostatDevice(hass, therm, name, away_temp), ), True)
|
||||
add_entities((ThermostatDevice(therm, name), ), True)
|
||||
|
||||
|
||||
class ThermostatDevice(ClimateDevice):
|
||||
"""Interface class for the oemthermostat module."""
|
||||
|
||||
def __init__(self, hass, thermostat, name, away_temp):
|
||||
def __init__(self, thermostat, name):
|
||||
"""Initialize the device."""
|
||||
self._name = name
|
||||
self.hass = hass
|
||||
|
||||
# Away mode stuff
|
||||
self._away = False
|
||||
self._away_temp = away_temp
|
||||
self._prev_temp = thermostat.setpoint
|
||||
|
||||
self.thermostat = thermostat
|
||||
# Set the thermostat mode to manual
|
||||
self.thermostat.mode = 2
|
||||
|
||||
# set up internal state varS
|
||||
self._state = None
|
||||
self._temperature = None
|
||||
self._setpoint = None
|
||||
self._mode = None
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_FLAGS
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
if self._mode == 2:
|
||||
return HVAC_MODE_HEAT
|
||||
if self._mode == 1:
|
||||
return HVAC_MODE_AUTO
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return SUPPORT_HVAC
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of this Thermostat."""
|
||||
|
@ -95,11 +96,13 @@ class ThermostatDevice(ClimateDevice):
|
|||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation i.e. heat, cool, idle."""
|
||||
def hvac_action(self):
|
||||
"""Return current hvac i.e. heat, cool, idle."""
|
||||
if not self._mode:
|
||||
return CURRENT_HVAC_OFF
|
||||
if self._state:
|
||||
return STATE_HEAT
|
||||
return STATE_IDLE
|
||||
return CURRENT_HVAC_HEAT
|
||||
return CURRENT_HVAC_IDLE
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
|
@ -111,36 +114,23 @@ class ThermostatDevice(ClimateDevice):
|
|||
"""Return the temperature we try to reach."""
|
||||
return self._setpoint
|
||||
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
if hvac_mode == HVAC_MODE_AUTO:
|
||||
self.thermostat.mode = 1
|
||||
elif hvac_mode == HVAC_MODE_HEAT:
|
||||
self.thermostat.mode = 2
|
||||
elif hvac_mode == HVAC_MODE_OFF:
|
||||
self.thermostat.mode = 0
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set the temperature."""
|
||||
# If we are setting the temp, then we don't want away mode anymore.
|
||||
self.turn_away_mode_off()
|
||||
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
self.thermostat.setpoint = temp
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._away
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away mode on."""
|
||||
if not self._away:
|
||||
self._prev_temp = self._setpoint
|
||||
|
||||
self.thermostat.setpoint = self._away_temp
|
||||
self._away = True
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away mode off."""
|
||||
if self._away:
|
||||
self.thermostat.setpoint = self._prev_temp
|
||||
|
||||
self._away = False
|
||||
|
||||
def update(self):
|
||||
"""Update local state."""
|
||||
self._setpoint = self.thermostat.setpoint
|
||||
self._temperature = self.thermostat.temperature
|
||||
self._state = self.thermostat.state
|
||||
self._mode = self.thermostat.mode
|
||||
|
|
|
@ -3,15 +3,15 @@ import logging
|
|||
|
||||
from pyotgw import vars as gw_vars
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice, ENTITY_ID_FORMAT
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_TARGET_TEMPERATURE)
|
||||
HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE,
|
||||
PRESET_AWAY, SUPPORT_PRESET_MODE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE,
|
||||
TEMP_CELSIUS)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
|
||||
from .const import (
|
||||
CONF_FLOOR_TEMP, CONF_PRECISION, DATA_GATEWAYS, DATA_OPENTHERM_GW)
|
||||
|
@ -19,13 +19,14 @@ from .const import (
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the opentherm_gw device."""
|
||||
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info]
|
||||
|
||||
gateway = OpenThermClimate(gw_dev)
|
||||
async_add_entities([gateway])
|
||||
|
||||
|
@ -36,12 +37,10 @@ class OpenThermClimate(ClimateDevice):
|
|||
def __init__(self, gw_dev):
|
||||
"""Initialize the device."""
|
||||
self._gateway = gw_dev
|
||||
self.entity_id = async_generate_entity_id(
|
||||
ENTITY_ID_FORMAT, gw_dev.gw_id, hass=gw_dev.hass)
|
||||
self.friendly_name = gw_dev.name
|
||||
self.floor_temp = gw_dev.climate_config[CONF_FLOOR_TEMP]
|
||||
self.temp_precision = gw_dev.climate_config.get(CONF_PRECISION)
|
||||
self._current_operation = STATE_IDLE
|
||||
self._current_operation = HVAC_MODE_OFF
|
||||
self._current_temperature = None
|
||||
self._new_target_temperature = None
|
||||
self._target_temperature = None
|
||||
|
@ -63,13 +62,15 @@ class OpenThermClimate(ClimateDevice):
|
|||
flame_on = status.get(gw_vars.DATA_SLAVE_FLAME_ON)
|
||||
cooling_active = status.get(gw_vars.DATA_SLAVE_COOLING_ACTIVE)
|
||||
if ch_active and flame_on:
|
||||
self._current_operation = STATE_HEAT
|
||||
self._current_operation = HVAC_MODE_HEAT
|
||||
elif cooling_active:
|
||||
self._current_operation = STATE_COOL
|
||||
self._current_operation = HVAC_MODE_COOL
|
||||
else:
|
||||
self._current_operation = STATE_IDLE
|
||||
self._current_operation = HVAC_MODE_OFF
|
||||
|
||||
self._current_temperature = status.get(gw_vars.DATA_ROOM_TEMP)
|
||||
temp_upd = status.get(gw_vars.DATA_ROOM_SETPOINT)
|
||||
|
||||
if self._target_temperature != temp_upd:
|
||||
self._new_target_temperature = None
|
||||
self._target_temperature = temp_upd
|
||||
|
@ -103,6 +104,11 @@ class OpenThermClimate(ClimateDevice):
|
|||
"""Return the friendly name."""
|
||||
return self.friendly_name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return self._gateway.gw_id
|
||||
|
||||
@property
|
||||
def precision(self):
|
||||
"""Return the precision of the system."""
|
||||
|
@ -123,7 +129,7 @@ class OpenThermClimate(ClimateDevice):
|
|||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._current_operation
|
||||
|
||||
|
@ -151,9 +157,19 @@ class OpenThermClimate(ClimateDevice):
|
|||
return self.precision
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._away_state_a or self._away_state_b
|
||||
def preset_mode(self):
|
||||
"""Return current preset mode."""
|
||||
if self._away_state_a or self._away_state_b:
|
||||
return PRESET_AWAY
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Available preset modes to set."""
|
||||
return [PRESET_AWAY]
|
||||
|
||||
def set_preset_mode(self, preset_mode):
|
||||
"""Set the preset mode."""
|
||||
_LOGGER.warning("Changing preset mode is not supported")
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
|
|
@ -3,7 +3,7 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_TARGET_TEMPERATURE)
|
||||
HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, PRECISION_TENTHS, TEMP_FAHRENHEIT,
|
||||
ATTR_TEMPERATURE)
|
||||
|
@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
|
||||
pdp = proliphix.PDP(host, username, password)
|
||||
|
||||
add_entities([ProliphixThermostat(pdp)])
|
||||
add_entities([ProliphixThermostat(pdp)], True)
|
||||
|
||||
|
||||
class ProliphixThermostat(ClimateDevice):
|
||||
|
@ -37,7 +37,6 @@ class ProliphixThermostat(ClimateDevice):
|
|||
def __init__(self, pdp):
|
||||
"""Initialize the thermostat."""
|
||||
self._pdp = pdp
|
||||
self._pdp.update()
|
||||
self._name = self._pdp.name
|
||||
|
||||
@property
|
||||
|
@ -91,15 +90,20 @@ class ProliphixThermostat(ClimateDevice):
|
|||
return self._pdp.setback
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return the current state of the thermostat."""
|
||||
state = self._pdp.hvac_state
|
||||
state = self._pdp.hvac_mode
|
||||
if state in (1, 2):
|
||||
return STATE_IDLE
|
||||
return HVAC_MODE_OFF
|
||||
if state == 3:
|
||||
return STATE_HEAT
|
||||
return HVAC_MODE_HEAT
|
||||
if state == 6:
|
||||
return STATE_COOL
|
||||
return HVAC_MODE_COOL
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return available HVAC modes."""
|
||||
return []
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
|
|
@ -3,15 +3,15 @@ import datetime
|
|||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
import radiotherm
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE,
|
||||
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE)
|
||||
SUPPORT_FAN_MODE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_HOST, PRECISION_HALVES, TEMP_FAHRENHEIT, STATE_ON,
|
||||
STATE_OFF)
|
||||
ATTR_TEMPERATURE, CONF_HOST, PRECISION_HALVES, TEMP_FAHRENHEIT, STATE_ON)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -20,37 +20,35 @@ ATTR_FAN = 'fan'
|
|||
ATTR_MODE = 'mode'
|
||||
|
||||
CONF_HOLD_TEMP = 'hold_temp'
|
||||
CONF_AWAY_TEMPERATURE_HEAT = 'away_temperature_heat'
|
||||
CONF_AWAY_TEMPERATURE_COOL = 'away_temperature_cool'
|
||||
|
||||
DEFAULT_AWAY_TEMPERATURE_HEAT = 60
|
||||
DEFAULT_AWAY_TEMPERATURE_COOL = 85
|
||||
|
||||
STATE_CIRCULATE = "circulate"
|
||||
|
||||
OPERATION_LIST = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF]
|
||||
CT30_FAN_OPERATION_LIST = [STATE_ON, STATE_AUTO]
|
||||
CT80_FAN_OPERATION_LIST = [STATE_ON, STATE_CIRCULATE, STATE_AUTO]
|
||||
OPERATION_LIST = [HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT,
|
||||
HVAC_MODE_OFF]
|
||||
CT30_FAN_OPERATION_LIST = [STATE_ON, HVAC_MODE_AUTO]
|
||||
CT80_FAN_OPERATION_LIST = [STATE_ON, STATE_CIRCULATE, HVAC_MODE_AUTO]
|
||||
|
||||
# Mappings from radiotherm json data codes to and from HASS state
|
||||
# flags. CODE is the thermostat integer code and these map to and
|
||||
# from HASS state flags.
|
||||
|
||||
# Programmed temperature mode of the thermostat.
|
||||
CODE_TO_TEMP_MODE = {0: STATE_OFF, 1: STATE_HEAT, 2: STATE_COOL, 3: STATE_AUTO}
|
||||
CODE_TO_TEMP_MODE = {
|
||||
0: HVAC_MODE_OFF, 1: HVAC_MODE_HEAT, 2: HVAC_MODE_COOL, 3: HVAC_MODE_AUTO
|
||||
}
|
||||
TEMP_MODE_TO_CODE = {v: k for k, v in CODE_TO_TEMP_MODE.items()}
|
||||
|
||||
# Programmed fan mode (circulate is supported by CT80 models)
|
||||
CODE_TO_FAN_MODE = {0: STATE_AUTO, 1: STATE_CIRCULATE, 2: STATE_ON}
|
||||
CODE_TO_FAN_MODE = {0: HVAC_MODE_AUTO, 1: STATE_CIRCULATE, 2: STATE_ON}
|
||||
FAN_MODE_TO_CODE = {v: k for k, v in CODE_TO_FAN_MODE.items()}
|
||||
|
||||
# Active thermostat state (is it heating or cooling?). In the future
|
||||
# this should probably made into heat and cool binary sensors.
|
||||
CODE_TO_TEMP_STATE = {0: STATE_IDLE, 1: STATE_HEAT, 2: STATE_COOL}
|
||||
CODE_TO_TEMP_STATE = {0: HVAC_MODE_OFF, 1: HVAC_MODE_HEAT, 2: HVAC_MODE_COOL}
|
||||
|
||||
# Active fan state. This is if the fan is actually on or not. In the
|
||||
# future this should probably made into a binary sensor for the fan.
|
||||
CODE_TO_FAN_STATE = {0: STATE_OFF, 1: STATE_ON}
|
||||
CODE_TO_FAN_STATE = {0: HVAC_MODE_OFF, 1: STATE_ON}
|
||||
|
||||
|
||||
def round_temp(temperature):
|
||||
|
@ -65,22 +63,13 @@ def round_temp(temperature):
|
|||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_HOST): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean,
|
||||
vol.Optional(CONF_AWAY_TEMPERATURE_HEAT,
|
||||
default=DEFAULT_AWAY_TEMPERATURE_HEAT):
|
||||
vol.All(vol.Coerce(float), round_temp),
|
||||
vol.Optional(CONF_AWAY_TEMPERATURE_COOL,
|
||||
default=DEFAULT_AWAY_TEMPERATURE_COOL):
|
||||
vol.All(vol.Coerce(float), round_temp),
|
||||
})
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_FAN_MODE | SUPPORT_AWAY_MODE)
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Radio Thermostat."""
|
||||
import radiotherm
|
||||
|
||||
hosts = []
|
||||
if CONF_HOST in config:
|
||||
hosts = config[CONF_HOST]
|
||||
|
@ -92,16 +81,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
return False
|
||||
|
||||
hold_temp = config.get(CONF_HOLD_TEMP)
|
||||
away_temps = [
|
||||
config.get(CONF_AWAY_TEMPERATURE_HEAT),
|
||||
config.get(CONF_AWAY_TEMPERATURE_COOL)
|
||||
]
|
||||
tstats = []
|
||||
|
||||
for host in hosts:
|
||||
try:
|
||||
tstat = radiotherm.get_thermostat(host)
|
||||
tstats.append(RadioThermostat(tstat, hold_temp, away_temps))
|
||||
tstats.append(RadioThermostat(tstat, hold_temp))
|
||||
except OSError:
|
||||
_LOGGER.exception("Unable to connect to Radio Thermostat: %s",
|
||||
host)
|
||||
|
@ -112,12 +97,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
class RadioThermostat(ClimateDevice):
|
||||
"""Representation of a Radio Thermostat."""
|
||||
|
||||
def __init__(self, device, hold_temp, away_temps):
|
||||
def __init__(self, device, hold_temp):
|
||||
"""Initialize the thermostat."""
|
||||
self.device = device
|
||||
self._target_temperature = None
|
||||
self._current_temperature = None
|
||||
self._current_operation = STATE_IDLE
|
||||
self._current_operation = HVAC_MODE_OFF
|
||||
self._name = None
|
||||
self._fmode = None
|
||||
self._fstate = None
|
||||
|
@ -125,12 +110,9 @@ class RadioThermostat(ClimateDevice):
|
|||
self._tstate = None
|
||||
self._hold_temp = hold_temp
|
||||
self._hold_set = False
|
||||
self._away = False
|
||||
self._away_temps = away_temps
|
||||
self._prev_temp = None
|
||||
|
||||
# Fan circulate mode is only supported by the CT80 models.
|
||||
import radiotherm
|
||||
self._is_model_ct80 = isinstance(
|
||||
self.device, radiotherm.thermostat.CT80)
|
||||
|
||||
|
@ -172,14 +154,14 @@ class RadioThermostat(ClimateDevice):
|
|||
}
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""List of available fan modes."""
|
||||
if self._is_model_ct80:
|
||||
return CT80_FAN_OPERATION_LIST
|
||||
return CT30_FAN_OPERATION_LIST
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return whether the fan is on."""
|
||||
return self._fmode
|
||||
|
||||
|
@ -195,12 +177,12 @@ class RadioThermostat(ClimateDevice):
|
|||
return self._current_temperature
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return the current operation. head, cool idle."""
|
||||
return self._current_operation
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the operation modes list."""
|
||||
return OPERATION_LIST
|
||||
|
||||
|
@ -209,16 +191,6 @@ class RadioThermostat(ClimateDevice):
|
|||
"""Return the temperature we try to reach."""
|
||||
return self._target_temperature
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._away
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if on."""
|
||||
return self._tstate != STATE_IDLE
|
||||
|
||||
def update(self):
|
||||
"""Update and validate the data from the thermostat."""
|
||||
# Radio thermostats are very slow, and sometimes don't respond
|
||||
|
@ -235,7 +207,6 @@ class RadioThermostat(ClimateDevice):
|
|||
self._name = self.device.name['raw']
|
||||
|
||||
# Request the current state from the thermostat.
|
||||
import radiotherm
|
||||
try:
|
||||
data = self.device.tstat['raw']
|
||||
except radiotherm.validate.RadiothermTstatError:
|
||||
|
@ -253,20 +224,20 @@ class RadioThermostat(ClimateDevice):
|
|||
self._tstate = CODE_TO_TEMP_STATE[data['tstate']]
|
||||
|
||||
self._current_operation = self._tmode
|
||||
if self._tmode == STATE_COOL:
|
||||
if self._tmode == HVAC_MODE_COOL:
|
||||
self._target_temperature = data['t_cool']
|
||||
elif self._tmode == STATE_HEAT:
|
||||
elif self._tmode == HVAC_MODE_HEAT:
|
||||
self._target_temperature = data['t_heat']
|
||||
elif self._tmode == STATE_AUTO:
|
||||
elif self._tmode == HVAC_MODE_AUTO:
|
||||
# This doesn't really work - tstate is only set if the HVAC is
|
||||
# active. If it's idle, we don't know what to do with the target
|
||||
# temperature.
|
||||
if self._tstate == STATE_COOL:
|
||||
if self._tstate == HVAC_MODE_COOL:
|
||||
self._target_temperature = data['t_cool']
|
||||
elif self._tstate == STATE_HEAT:
|
||||
elif self._tstate == HVAC_MODE_HEAT:
|
||||
self._target_temperature = data['t_heat']
|
||||
else:
|
||||
self._current_operation = STATE_IDLE
|
||||
self._current_operation = HVAC_MODE_OFF
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
@ -276,20 +247,20 @@ class RadioThermostat(ClimateDevice):
|
|||
|
||||
temperature = round_temp(temperature)
|
||||
|
||||
if self._current_operation == STATE_COOL:
|
||||
if self._current_operation == HVAC_MODE_COOL:
|
||||
self.device.t_cool = temperature
|
||||
elif self._current_operation == STATE_HEAT:
|
||||
elif self._current_operation == HVAC_MODE_HEAT:
|
||||
self.device.t_heat = temperature
|
||||
elif self._current_operation == STATE_AUTO:
|
||||
if self._tstate == STATE_COOL:
|
||||
elif self._current_operation == HVAC_MODE_AUTO:
|
||||
if self._tstate == HVAC_MODE_COOL:
|
||||
self.device.t_cool = temperature
|
||||
elif self._tstate == STATE_HEAT:
|
||||
elif self._tstate == HVAC_MODE_HEAT:
|
||||
self.device.t_heat = temperature
|
||||
|
||||
# Only change the hold if requested or if hold mode was turned
|
||||
# on and we haven't set it yet.
|
||||
if kwargs.get('hold_changed', False) or not self._hold_set:
|
||||
if self._hold_temp or self._away:
|
||||
if self._hold_temp:
|
||||
self.device.hold = 1
|
||||
self._hold_set = True
|
||||
else:
|
||||
|
@ -306,34 +277,13 @@ class RadioThermostat(ClimateDevice):
|
|||
'minute': now.minute
|
||||
}
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set operation mode (auto, cool, heat, off)."""
|
||||
if operation_mode in (STATE_OFF, STATE_AUTO):
|
||||
self.device.tmode = TEMP_MODE_TO_CODE[operation_mode]
|
||||
if hvac_mode in (HVAC_MODE_OFF, HVAC_MODE_AUTO):
|
||||
self.device.tmode = TEMP_MODE_TO_CODE[hvac_mode]
|
||||
|
||||
# Setting t_cool or t_heat automatically changes tmode.
|
||||
elif operation_mode == STATE_COOL:
|
||||
elif hvac_mode == HVAC_MODE_COOL:
|
||||
self.device.t_cool = self._target_temperature
|
||||
elif operation_mode == STATE_HEAT:
|
||||
elif hvac_mode == HVAC_MODE_HEAT:
|
||||
self.device.t_heat = self._target_temperature
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away on.
|
||||
|
||||
The RTCOA app simulates away mode by using a hold.
|
||||
"""
|
||||
away_temp = None
|
||||
if not self._away:
|
||||
self._prev_temp = self._target_temperature
|
||||
if self._current_operation == STATE_HEAT:
|
||||
away_temp = self._away_temps[0]
|
||||
elif self._current_operation == STATE_COOL:
|
||||
away_temp = self._away_temps[1]
|
||||
|
||||
self._away = True
|
||||
self.set_temperature(temperature=away_temp, hold_changed=True)
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
self._away = False
|
||||
self.set_temperature(temperature=self._prev_temp, hold_changed=True)
|
||||
|
|
|
@ -6,27 +6,29 @@ import logging
|
|||
import aiohttp
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
import pysensibo
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
DOMAIN, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE,
|
||||
SUPPORT_ON_OFF, STATE_HEAT, STATE_COOL, STATE_FAN_ONLY, STATE_DRY,
|
||||
STATE_AUTO)
|
||||
HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_DRY,
|
||||
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
|
||||
SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_STATE, ATTR_TEMPERATURE, CONF_API_KEY, CONF_ID,
|
||||
STATE_ON, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.util.temperature import convert as convert_temperature
|
||||
|
||||
from .const import DOMAIN as SENSIBO_DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ALL = ['all']
|
||||
TIMEOUT = 10
|
||||
|
||||
SERVICE_ASSUME_STATE = 'sensibo_assume_state'
|
||||
SERVICE_ASSUME_STATE = 'assume_state'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_API_KEY): cv.string,
|
||||
|
@ -45,18 +47,16 @@ _INITIAL_FETCH_FIELDS = 'id,' + _FETCH_FIELDS
|
|||
|
||||
FIELD_TO_FLAG = {
|
||||
'fanLevel': SUPPORT_FAN_MODE,
|
||||
'mode': SUPPORT_OPERATION_MODE,
|
||||
'swing': SUPPORT_SWING_MODE,
|
||||
'targetTemperature': SUPPORT_TARGET_TEMPERATURE,
|
||||
'on': SUPPORT_ON_OFF,
|
||||
}
|
||||
|
||||
SENSIBO_TO_HA = {
|
||||
"cool": STATE_COOL,
|
||||
"heat": STATE_HEAT,
|
||||
"fan": STATE_FAN_ONLY,
|
||||
"auto": STATE_AUTO,
|
||||
"dry": STATE_DRY
|
||||
"cool": HVAC_MODE_COOL,
|
||||
"heat": HVAC_MODE_HEAT,
|
||||
"fan": HVAC_MODE_FAN_ONLY,
|
||||
"auto": HVAC_MODE_HEAT_COOL,
|
||||
"dry": HVAC_MODE_DRY
|
||||
}
|
||||
|
||||
HA_TO_SENSIBO = {value: key for key, value in SENSIBO_TO_HA.items()}
|
||||
|
@ -65,8 +65,6 @@ HA_TO_SENSIBO = {value: key for key, value in SENSIBO_TO_HA.items()}
|
|||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up Sensibo devices."""
|
||||
import pysensibo
|
||||
|
||||
client = pysensibo.SensiboClient(
|
||||
config[CONF_API_KEY], session=async_get_clientsession(hass),
|
||||
timeout=TIMEOUT)
|
||||
|
@ -82,29 +80,32 @@ async def async_setup_platform(hass, config, async_add_entities,
|
|||
_LOGGER.exception('Failed to connect to Sensibo servers.')
|
||||
raise PlatformNotReady
|
||||
|
||||
if devices:
|
||||
async_add_entities(devices)
|
||||
if not devices:
|
||||
return
|
||||
|
||||
async def async_assume_state(service):
|
||||
"""Set state according to external service call.."""
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||
if entity_ids:
|
||||
target_climate = [device for device in devices
|
||||
if device.entity_id in entity_ids]
|
||||
else:
|
||||
target_climate = devices
|
||||
async_add_entities(devices)
|
||||
|
||||
update_tasks = []
|
||||
for climate in target_climate:
|
||||
await climate.async_assume_state(
|
||||
service.data.get(ATTR_STATE))
|
||||
update_tasks.append(climate.async_update_ha_state(True))
|
||||
async def async_assume_state(service):
|
||||
"""Set state according to external service call.."""
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||
if entity_ids:
|
||||
target_climate = [device for device in devices
|
||||
if device.entity_id in entity_ids]
|
||||
else:
|
||||
target_climate = devices
|
||||
|
||||
if update_tasks:
|
||||
await asyncio.wait(update_tasks)
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_ASSUME_STATE, async_assume_state,
|
||||
schema=ASSUME_STATE_SCHEMA)
|
||||
update_tasks = []
|
||||
for climate in target_climate:
|
||||
await climate.async_assume_state(
|
||||
service.data.get(ATTR_STATE))
|
||||
update_tasks.append(climate.async_update_ha_state(True))
|
||||
|
||||
if update_tasks:
|
||||
await asyncio.wait(update_tasks)
|
||||
|
||||
hass.services.async_register(
|
||||
SENSIBO_DOMAIN, SERVICE_ASSUME_STATE, async_assume_state,
|
||||
schema=ASSUME_STATE_SCHEMA)
|
||||
|
||||
|
||||
class SensiboClimate(ClimateDevice):
|
||||
|
@ -136,6 +137,7 @@ class SensiboClimate(ClimateDevice):
|
|||
capabilities = data['remoteCapabilities']
|
||||
self._operations = [SENSIBO_TO_HA[mode] for mode
|
||||
in capabilities['modes']]
|
||||
self._operations.append(HVAC_MODE_OFF)
|
||||
self._current_capabilities = \
|
||||
capabilities['modes'][self._ac_states['mode']]
|
||||
temperature_unit_key = data.get('temperatureUnit') or \
|
||||
|
@ -189,7 +191,7 @@ class SensiboClimate(ClimateDevice):
|
|||
return None
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return SENSIBO_TO_HA.get(self._ac_states['mode'])
|
||||
|
||||
|
@ -214,27 +216,27 @@ class SensiboClimate(ClimateDevice):
|
|||
self.temperature_unit)
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""List of available operation modes."""
|
||||
return self._operations
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._ac_states.get('fanLevel')
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""List of available fan modes."""
|
||||
return self._current_capabilities.get('fanLevels')
|
||||
|
||||
@property
|
||||
def current_swing_mode(self):
|
||||
def swing_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._ac_states.get('swing')
|
||||
|
||||
@property
|
||||
def swing_list(self):
|
||||
def swing_modes(self):
|
||||
"""List of available swing modes."""
|
||||
return self._current_capabilities.get('swing')
|
||||
|
||||
|
@ -243,11 +245,6 @@ class SensiboClimate(ClimateDevice):
|
|||
"""Return the name of the entity."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if AC is on."""
|
||||
return self._ac_states['on']
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature."""
|
||||
|
@ -294,11 +291,23 @@ class SensiboClimate(ClimateDevice):
|
|||
await self._client.async_set_ac_state_property(
|
||||
self._id, 'fanLevel', fan_mode, self._ac_states)
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target operation mode."""
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
with async_timeout.timeout(TIMEOUT):
|
||||
await self._client.async_set_ac_state_property(
|
||||
self._id, 'on', False, self._ac_states)
|
||||
return
|
||||
|
||||
# Turn on if not currently on.
|
||||
if not self._ac_states['on']:
|
||||
with async_timeout.timeout(TIMEOUT):
|
||||
await self._client.async_set_ac_state_property(
|
||||
self._id, 'on', True, self._ac_states)
|
||||
|
||||
with async_timeout.timeout(TIMEOUT):
|
||||
await self._client.async_set_ac_state_property(
|
||||
self._id, 'mode', HA_TO_SENSIBO[operation_mode],
|
||||
self._id, 'mode', HA_TO_SENSIBO[hvac_mode],
|
||||
self._ac_states)
|
||||
|
||||
async def async_set_swing_mode(self, swing_mode):
|
||||
|
@ -307,40 +316,29 @@ class SensiboClimate(ClimateDevice):
|
|||
await self._client.async_set_ac_state_property(
|
||||
self._id, 'swing', swing_mode, self._ac_states)
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn Sensibo unit on."""
|
||||
with async_timeout.timeout(TIMEOUT):
|
||||
await self._client.async_set_ac_state_property(
|
||||
self._id, 'on', True, self._ac_states)
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn Sensibo unit on."""
|
||||
with async_timeout.timeout(TIMEOUT):
|
||||
await self._client.async_set_ac_state_property(
|
||||
self._id, 'on', False, self._ac_states)
|
||||
|
||||
async def async_assume_state(self, state):
|
||||
"""Set external state."""
|
||||
change_needed = (state != STATE_OFF and not self.is_on) \
|
||||
or (state == STATE_OFF and self.is_on)
|
||||
change_needed = \
|
||||
(state != HVAC_MODE_OFF and not self._ac_states['on']) \
|
||||
or (state == HVAC_MODE_OFF and self._ac_states['on'])
|
||||
|
||||
if change_needed:
|
||||
with async_timeout.timeout(TIMEOUT):
|
||||
await self._client.async_set_ac_state_property(
|
||||
self._id,
|
||||
'on',
|
||||
state != STATE_OFF, # value
|
||||
state != HVAC_MODE_OFF, # value
|
||||
self._ac_states,
|
||||
True # assumed_state
|
||||
)
|
||||
|
||||
if state in [STATE_ON, STATE_OFF]:
|
||||
if state in [STATE_ON, HVAC_MODE_OFF]:
|
||||
self._external_state = None
|
||||
else:
|
||||
self._external_state = state
|
||||
|
||||
async def async_update(self):
|
||||
"""Retrieve latest state."""
|
||||
import pysensibo
|
||||
try:
|
||||
with async_timeout.timeout(TIMEOUT):
|
||||
data = await self._client.async_get_device(
|
||||
|
|
3
homeassistant/components/sensibo/const.py
Normal file
3
homeassistant/components/sensibo/const.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""Constants for Sensibo."""
|
||||
|
||||
DOMAIN = "sensibo"
|
|
@ -0,0 +1,9 @@
|
|||
assume_state:
|
||||
description: Set Sensibo device to external state.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to change.
|
||||
example: 'climate.kitchen'
|
||||
state:
|
||||
description: State to set.
|
||||
example: 'idle'
|
|
@ -8,51 +8,59 @@ from pysmartthings import Attribute, Capability
|
|||
from homeassistant.components.climate import (
|
||||
DOMAIN as CLIMATE_DOMAIN, ClimateDevice)
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
|
||||
SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH,
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, HVAC_MODE_AUTO,
|
||||
HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT,
|
||||
HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
|
||||
from . import SmartThingsEntity
|
||||
from .const import DATA_BROKERS, DOMAIN
|
||||
|
||||
ATTR_OPERATION_STATE = 'operation_state'
|
||||
MODE_TO_STATE = {
|
||||
'auto': STATE_AUTO,
|
||||
'cool': STATE_COOL,
|
||||
'eco': STATE_ECO,
|
||||
'rush hour': STATE_ECO,
|
||||
'emergency heat': STATE_HEAT,
|
||||
'heat': STATE_HEAT,
|
||||
'off': STATE_OFF
|
||||
'auto': HVAC_MODE_HEAT_COOL,
|
||||
'cool': HVAC_MODE_COOL,
|
||||
'eco': HVAC_MODE_AUTO,
|
||||
'rush hour': HVAC_MODE_AUTO,
|
||||
'emergency heat': HVAC_MODE_HEAT,
|
||||
'heat': HVAC_MODE_HEAT,
|
||||
'off': HVAC_MODE_OFF
|
||||
}
|
||||
STATE_TO_MODE = {
|
||||
STATE_AUTO: 'auto',
|
||||
STATE_COOL: 'cool',
|
||||
STATE_ECO: 'eco',
|
||||
STATE_HEAT: 'heat',
|
||||
STATE_OFF: 'off'
|
||||
HVAC_MODE_HEAT_COOL: 'auto',
|
||||
HVAC_MODE_COOL: 'cool',
|
||||
HVAC_MODE_HEAT: 'heat',
|
||||
HVAC_MODE_OFF: 'off'
|
||||
}
|
||||
|
||||
OPERATING_STATE_TO_ACTION = {
|
||||
"cooling": CURRENT_HVAC_COOL,
|
||||
"fan only": None,
|
||||
"heating": CURRENT_HVAC_HEAT,
|
||||
"idle": CURRENT_HVAC_IDLE,
|
||||
"pending cool": CURRENT_HVAC_COOL,
|
||||
"pending heat": CURRENT_HVAC_HEAT,
|
||||
"vent economizer": None
|
||||
}
|
||||
|
||||
AC_MODE_TO_STATE = {
|
||||
'auto': STATE_AUTO,
|
||||
'cool': STATE_COOL,
|
||||
'dry': STATE_DRY,
|
||||
'coolClean': STATE_COOL,
|
||||
'dryClean': STATE_DRY,
|
||||
'heat': STATE_HEAT,
|
||||
'heatClean': STATE_HEAT,
|
||||
'fanOnly': STATE_FAN_ONLY
|
||||
'auto': HVAC_MODE_HEAT_COOL,
|
||||
'cool': HVAC_MODE_COOL,
|
||||
'dry': HVAC_MODE_DRY,
|
||||
'coolClean': HVAC_MODE_COOL,
|
||||
'dryClean': HVAC_MODE_DRY,
|
||||
'heat': HVAC_MODE_HEAT,
|
||||
'heatClean': HVAC_MODE_HEAT,
|
||||
'fanOnly': HVAC_MODE_FAN_ONLY
|
||||
}
|
||||
STATE_TO_AC_MODE = {
|
||||
STATE_AUTO: 'auto',
|
||||
STATE_COOL: 'cool',
|
||||
STATE_DRY: 'dry',
|
||||
STATE_HEAT: 'heat',
|
||||
STATE_FAN_ONLY: 'fanOnly'
|
||||
HVAC_MODE_HEAT_COOL: 'auto',
|
||||
HVAC_MODE_COOL: 'cool',
|
||||
HVAC_MODE_DRY: 'dry',
|
||||
HVAC_MODE_HEAT: 'heat',
|
||||
HVAC_MODE_FAN_ONLY: 'fanOnly'
|
||||
}
|
||||
|
||||
UNIT_MAP = {
|
||||
|
@ -139,14 +147,13 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||
"""Init the class."""
|
||||
super().__init__(device)
|
||||
self._supported_features = self._determine_features()
|
||||
self._current_operation = None
|
||||
self._operations = None
|
||||
self._hvac_mode = None
|
||||
self._hvac_modes = None
|
||||
|
||||
def _determine_features(self):
|
||||
flags = SUPPORT_OPERATION_MODE \
|
||||
| SUPPORT_TARGET_TEMPERATURE \
|
||||
| SUPPORT_TARGET_TEMPERATURE_LOW \
|
||||
| SUPPORT_TARGET_TEMPERATURE_HIGH
|
||||
flags = \
|
||||
SUPPORT_TARGET_TEMPERATURE \
|
||||
| SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||
if self._device.get_capability(
|
||||
Capability.thermostat_fan_mode, Capability.thermostat):
|
||||
flags |= SUPPORT_FAN_MODE
|
||||
|
@ -160,9 +167,9 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||
# the entity state ahead of receiving the confirming push updates
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target operation mode."""
|
||||
mode = STATE_TO_MODE[operation_mode]
|
||||
mode = STATE_TO_MODE[hvac_mode]
|
||||
await self._device.set_thermostat_mode(mode, set_status=True)
|
||||
|
||||
# State is set optimistically in the command above, therefore update
|
||||
|
@ -172,7 +179,7 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new operation mode and target temperatures."""
|
||||
# Operation state
|
||||
operation_state = kwargs.get(ATTR_OPERATION_MODE)
|
||||
operation_state = kwargs.get(ATTR_HVAC_MODE)
|
||||
if operation_state:
|
||||
mode = STATE_TO_MODE[operation_state]
|
||||
await self._device.set_thermostat_mode(mode, set_status=True)
|
||||
|
@ -181,9 +188,9 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||
# Heat/cool setpoint
|
||||
heating_setpoint = None
|
||||
cooling_setpoint = None
|
||||
if self.current_operation == STATE_HEAT:
|
||||
if self.hvac_mode == HVAC_MODE_HEAT:
|
||||
heating_setpoint = kwargs.get(ATTR_TEMPERATURE)
|
||||
elif self.current_operation == STATE_COOL:
|
||||
elif self.hvac_mode == HVAC_MODE_COOL:
|
||||
cooling_setpoint = kwargs.get(ATTR_TEMPERATURE)
|
||||
else:
|
||||
heating_setpoint = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
||||
|
@ -204,10 +211,10 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||
async def async_update(self):
|
||||
"""Update the attributes of the climate device."""
|
||||
thermostat_mode = self._device.status.thermostat_mode
|
||||
self._current_operation = MODE_TO_STATE.get(thermostat_mode)
|
||||
if self._current_operation is None:
|
||||
self._hvac_mode = MODE_TO_STATE.get(thermostat_mode)
|
||||
if self._hvac_mode is None:
|
||||
_LOGGER.debug('Device %s (%s) returned an invalid'
|
||||
'thermostat mode: %s', self._device.label,
|
||||
'hvac mode: %s', self._device.label,
|
||||
self._device.device_id, thermostat_mode)
|
||||
|
||||
supported_modes = self._device.status.supported_thermostat_modes
|
||||
|
@ -222,49 +229,47 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||
'supported thermostat mode: %s',
|
||||
self._device.label, self._device.device_id,
|
||||
mode)
|
||||
self._operations = operations
|
||||
self._hvac_modes = operations
|
||||
else:
|
||||
_LOGGER.debug('Device %s (%s) returned invalid supported '
|
||||
'thermostat modes: %s', self._device.label,
|
||||
self._device.device_id, supported_modes)
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._device.status.thermostat_fan_mode
|
||||
|
||||
@property
|
||||
def current_humidity(self):
|
||||
"""Return the current humidity."""
|
||||
return self._device.status.humidity
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._current_operation
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._device.status.temperature
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
return {
|
||||
ATTR_OPERATION_STATE:
|
||||
self._device.status.thermostat_operating_state
|
||||
}
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._device.status.thermostat_fan_mode
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return self._device.status.supported_thermostat_fan_modes
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_action(self) -> Optional[str]:
|
||||
"""Return the current running hvac operation if supported."""
|
||||
return OPERATING_STATE_TO_ACTION.get(
|
||||
self._device.status.thermostat_operating_state)
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._hvac_mode
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return self._operations
|
||||
return self._hvac_modes
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
@ -274,23 +279,23 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
if self.current_operation == STATE_COOL:
|
||||
if self.hvac_mode == HVAC_MODE_COOL:
|
||||
return self._device.status.cooling_setpoint
|
||||
if self.current_operation == STATE_HEAT:
|
||||
if self.hvac_mode == HVAC_MODE_HEAT:
|
||||
return self._device.status.heating_setpoint
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature_high(self):
|
||||
"""Return the highbound target temperature we try to reach."""
|
||||
if self.current_operation == STATE_AUTO:
|
||||
if self.hvac_mode == HVAC_MODE_HEAT_COOL:
|
||||
return self._device.status.cooling_setpoint
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature_low(self):
|
||||
"""Return the lowbound target temperature we try to reach."""
|
||||
if self.current_operation == STATE_AUTO:
|
||||
if self.hvac_mode == HVAC_MODE_HEAT_COOL:
|
||||
return self._device.status.heating_setpoint
|
||||
return None
|
||||
|
||||
|
@ -307,7 +312,7 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
|
|||
def __init__(self, device):
|
||||
"""Init the class."""
|
||||
super().__init__(device)
|
||||
self._operations = None
|
||||
self._hvac_modes = None
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode):
|
||||
"""Set new target fan mode."""
|
||||
|
@ -316,10 +321,10 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
|
|||
# the entity state ahead of receiving the confirming push updates
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target operation mode."""
|
||||
await self._device.set_air_conditioner_mode(
|
||||
STATE_TO_AC_MODE[operation_mode], set_status=True)
|
||||
STATE_TO_AC_MODE[hvac_mode], set_status=True)
|
||||
# State is set optimistically in the command above, therefore update
|
||||
# the entity state ahead of receiving the confirming push updates
|
||||
self.async_schedule_update_ha_state()
|
||||
|
@ -328,9 +333,9 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
|
|||
"""Set new target temperature."""
|
||||
tasks = []
|
||||
# operation mode
|
||||
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
|
||||
operation_mode = kwargs.get(ATTR_HVAC_MODE)
|
||||
if operation_mode:
|
||||
tasks.append(self.async_set_operation_mode(operation_mode))
|
||||
tasks.append(self.async_set_hvac_mode(operation_mode))
|
||||
# temperature
|
||||
tasks.append(self._device.set_cooling_setpoint(
|
||||
kwargs[ATTR_TEMPERATURE], set_status=True))
|
||||
|
@ -339,20 +344,6 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
|
|||
# the entity state ahead of receiving the confirming push updates
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn device on."""
|
||||
await self._device.switch_on(set_status=True)
|
||||
# State is set optimistically in the command above, therefore update
|
||||
# the entity state ahead of receiving the confirming push updates
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn device off."""
|
||||
await self._device.switch_off(set_status=True)
|
||||
# State is set optimistically in the command above, therefore update
|
||||
# the entity state ahead of receiving the confirming push updates
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the calculated fields of the AC."""
|
||||
operations = set()
|
||||
|
@ -364,17 +355,7 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
|
|||
_LOGGER.debug('Device %s (%s) returned an invalid supported '
|
||||
'AC mode: %s', self._device.label,
|
||||
self._device.device_id, mode)
|
||||
self._operations = operations
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._device.status.fan_mode
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return AC_MODE_TO_STATE.get(self._device.status.air_conditioner_mode)
|
||||
self._hvac_modes = operations
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
|
@ -407,25 +388,30 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
|
|||
return state_attributes
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._device.status.fan_mode
|
||||
|
||||
@property
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return self._device.status.supported_ac_fan_modes
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if on."""
|
||||
return self._device.status.switch
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return AC_MODE_TO_STATE.get(self._device.status.air_conditioner_mode)
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return self._operations
|
||||
return self._hvac_modes
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the supported features."""
|
||||
return SUPPORT_OPERATION_MODE | SUPPORT_TARGET_TEMPERATURE \
|
||||
| SUPPORT_FAN_MODE | SUPPORT_ON_OFF
|
||||
return SUPPORT_TARGET_TEMPERATURE \
|
||||
| SUPPORT_FAN_MODE
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
|
|
|
@ -4,13 +4,13 @@ import logging
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
|
||||
from . import DOMAIN as SPIDER_DOMAIN
|
||||
|
||||
FAN_LIST = [
|
||||
SUPPORT_FAN = [
|
||||
'Auto',
|
||||
'Low',
|
||||
'Medium',
|
||||
|
@ -20,15 +20,15 @@ FAN_LIST = [
|
|||
'Boost 30',
|
||||
]
|
||||
|
||||
OPERATION_LIST = [
|
||||
STATE_HEAT,
|
||||
STATE_COOL,
|
||||
SUPPORT_HVAC = [
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_COOL,
|
||||
]
|
||||
|
||||
HA_STATE_TO_SPIDER = {
|
||||
STATE_COOL: 'Cool',
|
||||
STATE_HEAT: 'Heat',
|
||||
STATE_IDLE: 'Idle',
|
||||
HVAC_MODE_COOL: 'Cool',
|
||||
HVAC_MODE_HEAT: 'Heat',
|
||||
HVAC_MODE_OFF: 'Idle',
|
||||
}
|
||||
|
||||
SPIDER_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_SPIDER.items()}
|
||||
|
@ -59,9 +59,6 @@ class SpiderThermostat(ClimateDevice):
|
|||
"""Return the list of supported features."""
|
||||
supports = SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
if self.thermostat.has_operation_mode:
|
||||
supports |= SUPPORT_OPERATION_MODE
|
||||
|
||||
if self.thermostat.has_fan_mode:
|
||||
supports |= SUPPORT_FAN_MODE
|
||||
|
||||
|
@ -108,14 +105,14 @@ class SpiderThermostat(ClimateDevice):
|
|||
return self.thermostat.maximum_temperature
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return SPIDER_STATE_TO_HA[self.thermostat.operation_mode]
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return OPERATION_LIST
|
||||
return SUPPORT_HVAC
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
@ -125,13 +122,13 @@ class SpiderThermostat(ClimateDevice):
|
|||
|
||||
self.thermostat.set_temperature(temperature)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target operation mode."""
|
||||
self.thermostat.set_operation_mode(
|
||||
HA_STATE_TO_SPIDER.get(operation_mode))
|
||||
HA_STATE_TO_SPIDER.get(hvac_mode))
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self.thermostat.current_fan_speed
|
||||
|
||||
|
@ -140,9 +137,9 @@ class SpiderThermostat(ClimateDevice):
|
|||
self.thermostat.set_fan_speed(fan_mode)
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""List of available fan modes."""
|
||||
return FAN_LIST
|
||||
return SUPPORT_FAN
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data."""
|
||||
|
|
|
@ -3,10 +3,9 @@ import logging
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_ECO, STATE_MANUAL, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS)
|
||||
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_ECO,
|
||||
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
|
||||
from . import DOMAIN as STE_DOMAIN
|
||||
|
||||
|
@ -14,21 +13,39 @@ DEPENDENCIES = ['stiebel_eltron']
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PRESET_DAY = 'day'
|
||||
PRESET_SETBACK = 'setback'
|
||||
PRESET_EMERGENCY = 'emergency'
|
||||
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
|
||||
OPERATION_MODES = [STATE_AUTO, STATE_MANUAL, STATE_ECO, STATE_OFF]
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
SUPPORT_HVAC = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
SUPPORT_PRESET = [PRESET_ECO, PRESET_DAY, PRESET_EMERGENCY, PRESET_SETBACK]
|
||||
|
||||
# Mapping STIEBEL ELTRON states to homeassistant states.
|
||||
STE_TO_HA_STATE = {'AUTOMATIC': STATE_AUTO,
|
||||
'MANUAL MODE': STATE_MANUAL,
|
||||
'STANDBY': STATE_ECO,
|
||||
'DAY MODE': STATE_ON,
|
||||
'SETBACK MODE': STATE_ON,
|
||||
'DHW': STATE_OFF,
|
||||
'EMERGENCY OPERATION': STATE_ON}
|
||||
# Mapping STIEBEL ELTRON states to homeassistant states/preset.
|
||||
STE_TO_HA_HVAC = {
|
||||
'AUTOMATIC': HVAC_MODE_AUTO,
|
||||
'MANUAL MODE': HVAC_MODE_HEAT,
|
||||
'STANDBY': HVAC_MODE_AUTO,
|
||||
'DAY MODE': HVAC_MODE_AUTO,
|
||||
'SETBACK MODE': HVAC_MODE_AUTO,
|
||||
'DHW': HVAC_MODE_OFF,
|
||||
'EMERGENCY OPERATION': HVAC_MODE_AUTO
|
||||
}
|
||||
|
||||
# Mapping homeassistant states to STIEBEL ELTRON states.
|
||||
HA_TO_STE_STATE = {value: key for key, value in STE_TO_HA_STATE.items()}
|
||||
STE_TO_HA_PRESET = {
|
||||
'STANDBY': PRESET_ECO,
|
||||
'DAY MODE': PRESET_DAY,
|
||||
'SETBACK MODE': PRESET_SETBACK,
|
||||
'EMERGENCY OPERATION': PRESET_EMERGENCY,
|
||||
}
|
||||
|
||||
HA_TO_STE_HVAC = {
|
||||
HVAC_MODE_AUTO: 'AUTOMATIC',
|
||||
HVAC_MODE_HEAT: 'MANUAL MODE',
|
||||
HVAC_MODE_OFF: 'DHW',
|
||||
}
|
||||
|
||||
HA_TO_STE_PRESET = {k: i for i, k in STE_TO_HA_PRESET.items()}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
@ -48,8 +65,7 @@ class StiebelEltron(ClimateDevice):
|
|||
self._target_temperature = None
|
||||
self._current_temperature = None
|
||||
self._current_humidity = None
|
||||
self._operation_modes = OPERATION_MODES
|
||||
self._current_operation = None
|
||||
self._operation = None
|
||||
self._filter_alarm = None
|
||||
self._force_update = False
|
||||
self._ste_data = ste_data
|
||||
|
@ -68,7 +84,7 @@ class StiebelEltron(ClimateDevice):
|
|||
self._current_temperature = self._ste_data.api.get_current_temp()
|
||||
self._current_humidity = self._ste_data.api.get_current_humidity()
|
||||
self._filter_alarm = self._ste_data.api.get_filter_alarm_status()
|
||||
self._current_operation = self._ste_data.api.get_operation()
|
||||
self._operation = self._ste_data.api.get_operation()
|
||||
|
||||
_LOGGER.debug("Update %s, current temp: %s", self._name,
|
||||
self._current_temperature)
|
||||
|
@ -116,6 +132,41 @@ class StiebelEltron(ClimateDevice):
|
|||
"""Return the maximum temperature."""
|
||||
return 30.0
|
||||
|
||||
@property
|
||||
def current_humidity(self):
|
||||
"""Return the current humidity."""
|
||||
return float("{0:.1f}".format(self._current_humidity))
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""List of the operation modes."""
|
||||
return SUPPORT_HVAC
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return STE_TO_HA_HVAC.get(self._operation)
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
return STE_TO_HA_PRESET.get(self._operation)
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Return a list of available preset modes."""
|
||||
return SUPPORT_PRESET
|
||||
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new operation mode."""
|
||||
if self.preset_mode:
|
||||
return
|
||||
new_mode = HA_TO_STE_HVAC.get(hvac_mode)
|
||||
_LOGGER.debug("set_hvac_mode: %s -> %s", self._operation,
|
||||
new_mode)
|
||||
self._ste_data.api.set_operation(new_mode)
|
||||
self._force_update = True
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
target_temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
@ -124,26 +175,10 @@ class StiebelEltron(ClimateDevice):
|
|||
self._ste_data.api.set_target_temp(target_temperature)
|
||||
self._force_update = True
|
||||
|
||||
@property
|
||||
def current_humidity(self):
|
||||
"""Return the current humidity."""
|
||||
return float("{0:.1f}".format(self._current_humidity))
|
||||
|
||||
# Handle SUPPORT_OPERATION_MODE
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of the operation modes."""
|
||||
return self._operation_modes
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return STE_TO_HA_STATE.get(self._current_operation)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new operation mode."""
|
||||
new_mode = HA_TO_STE_STATE.get(operation_mode)
|
||||
_LOGGER.debug("set_operation_mode: %s -> %s", self._current_operation,
|
||||
def set_preset_mode(self, preset_mode: str):
|
||||
"""Set new preset mode."""
|
||||
new_mode = HA_TO_STE_PRESET.get(preset_mode)
|
||||
_LOGGER.debug("set_hvac_mode: %s -> %s", self._operation,
|
||||
new_mode)
|
||||
self._ste_data.api.set_operation(new_mode)
|
||||
self._force_update = True
|
||||
|
|
|
@ -3,7 +3,9 @@ import logging
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_ON_OFF)
|
||||
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, FAN_HIGH, FAN_LOW, FAN_MIDDLE,
|
||||
FAN_OFF, HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY,
|
||||
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS)
|
||||
from homeassistant.util.temperature import convert as convert_temperature
|
||||
|
@ -27,23 +29,24 @@ CONST_MODE_FAN_HIGH = 'HIGH'
|
|||
CONST_MODE_FAN_MIDDLE = 'MIDDLE'
|
||||
CONST_MODE_FAN_LOW = 'LOW'
|
||||
|
||||
FAN_MODES_LIST = {
|
||||
CONST_MODE_FAN_HIGH: 'High',
|
||||
CONST_MODE_FAN_MIDDLE: 'Middle',
|
||||
CONST_MODE_FAN_LOW: 'Low',
|
||||
CONST_MODE_OFF: 'Off',
|
||||
FAN_MAP_TADO = {
|
||||
'HIGH': FAN_HIGH,
|
||||
'MIDDLE': FAN_MIDDLE,
|
||||
'LOW': FAN_LOW,
|
||||
}
|
||||
|
||||
OPERATION_LIST = {
|
||||
CONST_OVERLAY_MANUAL: 'Manual',
|
||||
CONST_OVERLAY_TIMER: 'Timer',
|
||||
CONST_OVERLAY_TADO_MODE: 'Tado mode',
|
||||
CONST_MODE_SMART_SCHEDULE: 'Smart schedule',
|
||||
CONST_MODE_OFF: 'Off',
|
||||
HVAC_MAP_TADO = {
|
||||
'MANUAL': HVAC_MODE_HEAT,
|
||||
'TIMER': HVAC_MODE_AUTO,
|
||||
'TADO_MODE': HVAC_MODE_AUTO,
|
||||
'SMART_SCHEDULE': HVAC_MODE_AUTO,
|
||||
'OFF': HVAC_MODE_OFF
|
||||
}
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_ON_OFF)
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF]
|
||||
SUPPORT_FAN = [FAN_HIGH, FAN_MIDDLE, FAN_HIGH, FAN_OFF]
|
||||
SUPPORT_PRESET = [PRESET_AWAY]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
@ -159,41 +162,62 @@ class TadoClimate(ClimateDevice):
|
|||
return self._cur_temp
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current readable operation mode."""
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
return HVAC_MAP_TADO.get(self._current_operation)
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return SUPPORT_HVAC
|
||||
|
||||
@property
|
||||
def hvac_action(self):
|
||||
"""Return the current running hvac operation if supported.
|
||||
|
||||
Need to be one of CURRENT_HVAC_*.
|
||||
"""
|
||||
if self._cooling:
|
||||
return "Cooling"
|
||||
return OPERATION_LIST.get(self._current_operation)
|
||||
return CURRENT_HVAC_COOL
|
||||
return CURRENT_HVAC_HEAT
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the list of available operation modes (readable)."""
|
||||
return list(OPERATION_LIST.values())
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
if self.ac_mode:
|
||||
return FAN_MODES_LIST.get(self._current_fan)
|
||||
return FAN_MAP_TADO.get(self._current_fan)
|
||||
return None
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""List of available fan modes."""
|
||||
if self.ac_mode:
|
||||
return list(FAN_MODES_LIST.values())
|
||||
return SUPPORT_FAN
|
||||
return None
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
if self._is_away:
|
||||
return PRESET_AWAY
|
||||
return None
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Return a list of available preset modes."""
|
||||
return SUPPORT_PRESET
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement used by the platform."""
|
||||
return self._unit
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return true if away mode is on."""
|
||||
return self._is_away
|
||||
|
||||
@property
|
||||
def target_temperature_step(self):
|
||||
"""Return the supported step of target temperature."""
|
||||
|
@ -204,27 +228,6 @@ class TadoClimate(ClimateDevice):
|
|||
"""Return the temperature we try to reach."""
|
||||
return self._target_temp
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if heater is on."""
|
||||
return self._device_is_active
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn device off."""
|
||||
_LOGGER.info("Switching mytado.com to OFF for zone %s",
|
||||
self.zone_name)
|
||||
|
||||
self._current_operation = CONST_MODE_OFF
|
||||
self._control_heating()
|
||||
|
||||
def turn_on(self):
|
||||
"""Turn device on."""
|
||||
_LOGGER.info("Switching mytado.com to %s mode for zone %s",
|
||||
self._overlay_mode, self.zone_name)
|
||||
|
||||
self._current_operation = self._overlay_mode
|
||||
self._control_heating()
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
@ -236,20 +239,25 @@ class TadoClimate(ClimateDevice):
|
|||
self._target_temp = temperature
|
||||
self._control_heating()
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def set_operation_mode(self, readable_operation_mode):
|
||||
"""Set new operation mode."""
|
||||
operation_mode = CONST_MODE_SMART_SCHEDULE
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
mode = None
|
||||
|
||||
for mode, readable in OPERATION_LIST.items():
|
||||
if readable == readable_operation_mode:
|
||||
operation_mode = mode
|
||||
break
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
mode = CONST_MODE_OFF
|
||||
elif hvac_mode == HVAC_MODE_AUTO:
|
||||
mode = CONST_MODE_SMART_SCHEDULE
|
||||
elif hvac_mode == HVAC_MODE_HEAT:
|
||||
mode = CONST_OVERLAY_MANUAL
|
||||
|
||||
self._current_operation = operation_mode
|
||||
self._current_operation = mode
|
||||
self._overlay_mode = None
|
||||
self._control_heating()
|
||||
|
||||
def set_preset_mode(self, preset_mode):
|
||||
"""Set new preset mode."""
|
||||
pass
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
"""Return the minimum temperature."""
|
||||
|
|
|
@ -99,6 +99,11 @@ class TeslaDevice(Entity):
|
|||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
return self.tesla_id
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling state."""
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
"""Support for Tesla binary sensor."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
ENTITY_ID_FORMAT, BinarySensorDevice)
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
|
||||
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
||||
|
||||
|
@ -25,7 +24,6 @@ class TeslaBinarySensor(TeslaDevice, BinarySensorDevice):
|
|||
"""Initialise of a Tesla binary sensor."""
|
||||
super().__init__(tesla_device, controller)
|
||||
self._state = False
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
||||
self._sensor_type = sensor_type
|
||||
|
||||
@property
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
"""Support for Tesla HVAC system."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
|
||||
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
OPERATION_LIST = [STATE_ON, STATE_OFF]
|
||||
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
|
||||
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
@ -29,27 +26,31 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
|
|||
def __init__(self, tesla_device, controller):
|
||||
"""Initialize the Tesla device."""
|
||||
super().__init__(tesla_device, controller)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
||||
self._target_temperature = None
|
||||
self._temperature = None
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_FLAGS
|
||||
return SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. On or Off."""
|
||||
mode = self.tesla_device.is_hvac_enabled()
|
||||
if mode:
|
||||
return OPERATION_LIST[0] # On
|
||||
return OPERATION_LIST[1] # Off
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
if self.tesla_device.is_hvac_enabled():
|
||||
return HVAC_MODE_HEAT
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of available operation modes."""
|
||||
return OPERATION_LIST
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return SUPPORT_HVAC
|
||||
|
||||
def update(self):
|
||||
"""Call by the Tesla device callback to update state."""
|
||||
|
@ -84,10 +85,10 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
|
|||
if temperature:
|
||||
self.tesla_device.set_temperature(temperature)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set HVAC mode (auto, cool, heat, off)."""
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
_LOGGER.debug("Setting mode for: %s", self._name)
|
||||
if operation_mode == OPERATION_LIST[1]: # off
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
self.tesla_device.set_status(False)
|
||||
elif operation_mode == OPERATION_LIST[0]: # heat
|
||||
elif hvac_mode == HVAC_MODE_HEAT:
|
||||
self.tesla_device.set_status(True)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Support for Tesla door locks."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.lock import ENTITY_ID_FORMAT, LockDevice
|
||||
from homeassistant.components.lock import LockDevice
|
||||
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
|
||||
|
||||
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
||||
|
@ -23,7 +23,6 @@ class TeslaLock(TeslaDevice, LockDevice):
|
|||
"""Initialise of the lock."""
|
||||
self._state = None
|
||||
super().__init__(tesla_device, controller)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
||||
|
||||
def lock(self, **kwargs):
|
||||
"""Send the lock command."""
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from homeassistant.components.sensor import ENTITY_ID_FORMAT
|
||||
from homeassistant.const import (
|
||||
LENGTH_KILOMETERS, LENGTH_MILES, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
@ -41,10 +40,13 @@ class TeslaSensor(TeslaDevice, Entity):
|
|||
|
||||
if self.type:
|
||||
self._name = '{} ({})'.format(self.tesla_device.name, self.type)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(
|
||||
'{}_{}'.format(self.tesla_id, self.type))
|
||||
else:
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
if self.type:
|
||||
return "{}_{}".format(self.tesla_id, self.type)
|
||||
return self.tesla_id
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Support for Tesla charger switches."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
|
||||
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
||||
|
@ -28,7 +28,6 @@ class ChargerSwitch(TeslaDevice, SwitchDevice):
|
|||
"""Initialise of the switch."""
|
||||
self._state = None
|
||||
super().__init__(tesla_device, controller)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Send the on command."""
|
||||
|
@ -60,7 +59,6 @@ class RangeSwitch(TeslaDevice, SwitchDevice):
|
|||
"""Initialise of the switch."""
|
||||
self._state = None
|
||||
super().__init__(tesla_device, controller)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Send the on command."""
|
||||
|
|
|
@ -3,13 +3,15 @@ from concurrent import futures
|
|||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from pytfiac import Tfiac
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT,
|
||||
SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
FAN_AUTO, FAN_HIGH, FAN_LOW, FAN_MEDIUM, HVAC_MODE_AUTO, HVAC_MODE_COOL,
|
||||
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
SWING_BOTH, SWING_HORIZONTAL, SWING_OFF, SWING_VERTICAL)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, CONF_HOST, TEMP_FAHRENHEIT
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
@ -23,22 +25,23 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
MIN_TEMP = 61
|
||||
MAX_TEMP = 88
|
||||
OPERATION_MAP = {
|
||||
STATE_HEAT: 'heat',
|
||||
STATE_AUTO: 'selfFeel',
|
||||
STATE_DRY: 'dehumi',
|
||||
STATE_FAN_ONLY: 'fan',
|
||||
STATE_COOL: 'cool',
|
||||
|
||||
HVAC_MAP = {
|
||||
HVAC_MODE_HEAT: 'heat',
|
||||
HVAC_MODE_AUTO: 'selfFeel',
|
||||
HVAC_MODE_DRY: 'dehumi',
|
||||
HVAC_MODE_FAN_ONLY: 'fan',
|
||||
HVAC_MODE_COOL: 'cool',
|
||||
HVAC_MODE_OFF: 'off'
|
||||
}
|
||||
OPERATION_MAP_REV = {
|
||||
v: k for k, v in OPERATION_MAP.items()}
|
||||
FAN_LIST = ['Auto', 'Low', 'Middle', 'High']
|
||||
SWING_LIST = [
|
||||
'Off',
|
||||
'Vertical',
|
||||
'Horizontal',
|
||||
'Both',
|
||||
]
|
||||
|
||||
HVAC_MAP_REV = {v: k for k, v in HVAC_MAP.items()}
|
||||
|
||||
SUPPORT_FAN = [FAN_AUTO, FAN_HIGH, FAN_MEDIUM, FAN_LOW]
|
||||
SUPPORT_SWING = [SWING_OFF, SWING_HORIZONTAL, SWING_VERTICAL, SWING_BOTH]
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_SWING_MODE |
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
|
||||
CURR_TEMP = 'current_temp'
|
||||
TARGET_TEMP = 'target_temp'
|
||||
|
@ -51,8 +54,6 @@ ON_MODE = 'is_on'
|
|||
async def async_setup_platform(hass, config, async_add_devices,
|
||||
discovery_info=None):
|
||||
"""Set up the TFIAC climate device."""
|
||||
from pytfiac import Tfiac
|
||||
|
||||
tfiac_client = Tfiac(config[CONF_HOST])
|
||||
try:
|
||||
await tfiac_client.update()
|
||||
|
@ -86,8 +87,7 @@ class TfiacClimate(ClimateDevice):
|
|||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return (SUPPORT_FAN_MODE | SUPPORT_ON_OFF | SUPPORT_OPERATION_MODE
|
||||
| SUPPORT_SWING_MODE | SUPPORT_TARGET_TEMPERATURE)
|
||||
return SUPPORT_FLAGS
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
|
@ -120,64 +120,62 @@ class TfiacClimate(ClimateDevice):
|
|||
return self._client.status['current_temp']
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
operation = self._client.status['operation']
|
||||
return OPERATION_MAP_REV.get(operation, operation)
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
if self._client.status[ON_MODE] != 'on':
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
state = self._client.status['operation']
|
||||
return HVAC_MAP_REV.get(state)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if on."""
|
||||
return self._client.status[ON_MODE] == 'on'
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return list(HVAC_MAP)
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return sorted(OPERATION_MAP)
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._client.status['fan_mode']
|
||||
return self._client.status['fan_mode'].lower()
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return FAN_LIST
|
||||
return SUPPORT_FAN
|
||||
|
||||
@property
|
||||
def current_swing_mode(self):
|
||||
def swing_mode(self):
|
||||
"""Return the swing setting."""
|
||||
return self._client.status['swing_mode']
|
||||
return self._client.status['swing_mode'].lower()
|
||||
|
||||
@property
|
||||
def swing_list(self):
|
||||
def swing_modes(self):
|
||||
"""List of available swing modes."""
|
||||
return SWING_LIST
|
||||
return SUPPORT_SWING
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
if kwargs.get(ATTR_TEMPERATURE) is not None:
|
||||
await self._client.set_state(TARGET_TEMP,
|
||||
kwargs.get(ATTR_TEMPERATURE))
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
if temp is not None:
|
||||
await self._client.set_state(TARGET_TEMP, temp)
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
"""Set new operation mode."""
|
||||
await self._client.set_state(OPERATION_MODE,
|
||||
OPERATION_MAP[operation_mode])
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
await self._client.set_state(ON_MODE, 'off')
|
||||
else:
|
||||
await self._client.set_state(OPERATION_MODE, HVAC_MAP[hvac_mode])
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode):
|
||||
"""Set new fan mode."""
|
||||
await self._client.set_state(FAN_MODE, fan_mode)
|
||||
await self._client.set_state(FAN_MODE, fan_mode.capitalize())
|
||||
|
||||
async def async_set_swing_mode(self, swing_mode):
|
||||
"""Set new swing mode."""
|
||||
await self._client.set_swing(swing_mode)
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn device on."""
|
||||
await self._client.set_state(ON_MODE, 'on')
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn device off."""
|
||||
await self._client.set_state(ON_MODE, 'off')
|
||||
await self._client.set_swing(swing_mode.capitalize())
|
||||
|
|
|
@ -6,8 +6,8 @@ from typing import Any, Dict, List
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_COOL, STATE_ECO, STATE_HEAT, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
HVAC_MODE_HEAT, PRESET_AWAY, PRESET_COMFORT, PRESET_HOME, PRESET_SLEEP,
|
||||
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
@ -17,20 +17,12 @@ from .const import DATA_TOON_CLIENT, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
SUPPORT_PRESET = [PRESET_AWAY, PRESET_COMFORT, PRESET_HOME, PRESET_SLEEP]
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
|
||||
SCAN_INTERVAL = timedelta(seconds=300)
|
||||
|
||||
HA_TOON = {
|
||||
STATE_AUTO: 'Comfort',
|
||||
STATE_HEAT: 'Home',
|
||||
STATE_ECO: 'Away',
|
||||
STATE_COOL: 'Sleep',
|
||||
}
|
||||
|
||||
TOON_HA = {value: key for key, value in HA_TOON.items()}
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry,
|
||||
async_add_entities) -> None:
|
||||
|
@ -64,20 +56,36 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateDevice):
|
|||
"""Return the list of supported features."""
|
||||
return SUPPORT_FLAGS
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> List[str]:
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return [HVAC_MODE_HEAT]
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_operation(self) -> str:
|
||||
"""Return current operation i.e. comfort, home, away."""
|
||||
return TOON_HA.get(self._state)
|
||||
def preset_mode(self) -> str:
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
return self._state.lower()
|
||||
|
||||
@property
|
||||
def operation_list(self) -> List[str]:
|
||||
"""Return a list of available operation modes."""
|
||||
return list(HA_TOON.keys())
|
||||
def preset_modes(self) -> List[str]:
|
||||
"""Return a list of available preset modes."""
|
||||
return SUPPORT_PRESET
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float:
|
||||
|
@ -111,9 +119,13 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateDevice):
|
|||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
self.toon.thermostat = temperature
|
||||
|
||||
def set_operation_mode(self, operation_mode: str) -> None:
|
||||
"""Set new operation mode."""
|
||||
self.toon.thermostat_state = HA_TOON[operation_mode]
|
||||
def set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
self.toon.thermostat_state = preset_mode
|
||||
|
||||
def set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set new target hvac mode."""
|
||||
pass
|
||||
|
||||
def update(self) -> None:
|
||||
"""Update local state."""
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
"""Platform for Roth Touchline heat pump controller."""
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_HEAT)
|
||||
from homeassistant.const import CONF_HOST, TEMP_CELSIUS, ATTR_TEMPERATURE
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
@ -52,6 +53,22 @@ class Touchline(ClimateDevice):
|
|||
self._current_temperature = self.unit.get_current_temperature()
|
||||
self._target_temperature = self.unit.get_target_temperature()
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> List[str]:
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return [HVAC_MODE_HEAT]
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling state."""
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
"""Support for the Tuya climate devices."""
|
||||
from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_COOL, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
|
||||
SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT,
|
||||
SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF)
|
||||
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
|
@ -13,11 +12,10 @@ from . import DATA_TUYA, TuyaDevice
|
|||
DEVICE_TYPE = 'climate'
|
||||
|
||||
HA_STATE_TO_TUYA = {
|
||||
STATE_AUTO: 'auto',
|
||||
STATE_COOL: 'cold',
|
||||
STATE_ECO: 'eco',
|
||||
STATE_FAN_ONLY: 'wind',
|
||||
STATE_HEAT: 'hot',
|
||||
HVAC_MODE_AUTO: 'auto',
|
||||
HVAC_MODE_COOL: 'cold',
|
||||
HVAC_MODE_FAN_ONLY: 'wind',
|
||||
HVAC_MODE_HEAT: 'hot',
|
||||
}
|
||||
|
||||
TUYA_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_TUYA.items()}
|
||||
|
@ -47,7 +45,7 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
|
|||
"""Init climate device."""
|
||||
super().__init__(tuya)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
|
||||
self.operations = []
|
||||
self.operations = [HVAC_MODE_OFF]
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Create operation list when add to hass."""
|
||||
|
@ -55,15 +53,11 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
|
|||
modes = self.tuya.operation_list()
|
||||
if modes is None:
|
||||
return
|
||||
|
||||
for mode in modes:
|
||||
if mode in TUYA_STATE_TO_HA:
|
||||
self.operations.append(TUYA_STATE_TO_HA[mode])
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if climate is on."""
|
||||
return self.tuya.state()
|
||||
|
||||
@property
|
||||
def precision(self):
|
||||
"""Return the precision of the system."""
|
||||
|
@ -73,22 +67,23 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
|
|||
def temperature_unit(self):
|
||||
"""Return the unit of measurement used by the platform."""
|
||||
unit = self.tuya.temperature_unit()
|
||||
if unit == 'CELSIUS':
|
||||
return TEMP_CELSIUS
|
||||
if unit == 'FAHRENHEIT':
|
||||
return TEMP_FAHRENHEIT
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
if not self.tuya.state():
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
mode = self.tuya.current_operation()
|
||||
if mode is None:
|
||||
return None
|
||||
return TUYA_STATE_TO_HA.get(mode)
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return self.operations
|
||||
|
||||
|
@ -108,14 +103,14 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
|
|||
return self.tuya.target_temperature_step()
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self.tuya.current_fan_mode()
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return self.tuya.fan_list()
|
||||
return self.tuya.fan_modes()
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
@ -126,26 +121,22 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
|
|||
"""Set new target fan mode."""
|
||||
self.tuya.set_fan_mode(fan_mode)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target operation mode."""
|
||||
self.tuya.set_operation_mode(HA_STATE_TO_TUYA.get(operation_mode))
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
self.tuya.turn_off()
|
||||
|
||||
def turn_on(self):
|
||||
"""Turn device on."""
|
||||
self.tuya.turn_on()
|
||||
if not self.tuya.state():
|
||||
self.tuya.turn_on()
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn device off."""
|
||||
self.tuya.turn_off()
|
||||
self.tuya.set_operation_mode(HA_STATE_TO_TUYA.get(hvac_mode))
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
supports = SUPPORT_ON_OFF
|
||||
supports = 0
|
||||
if self.tuya.support_target_temperature():
|
||||
supports = supports | SUPPORT_TARGET_TEMPERATURE
|
||||
if self.tuya.support_mode():
|
||||
supports = supports | SUPPORT_OPERATION_MODE
|
||||
if self.tuya.support_wind_speed():
|
||||
supports = supports | SUPPORT_FAN_MODE
|
||||
return supports
|
||||
|
|
|
@ -3,15 +3,13 @@ import logging
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_HEAT, SUPPORT_TARGET_TEMPERATURE)
|
||||
HVAC_MODE_HEAT, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
|
||||
from . import DOMAIN as VELBUS_DOMAIN, VelbusEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
|
@ -34,7 +32,7 @@ class VelbusClimate(VelbusEntity, ClimateDevice):
|
|||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list off supported features."""
|
||||
return SUPPORT_FLAGS
|
||||
return SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
|
@ -49,9 +47,20 @@ class VelbusClimate(VelbusEntity, ClimateDevice):
|
|||
return self._module.get_state(self._channel)
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation."""
|
||||
return STATE_HEAT
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return [HVAC_MODE_HEAT]
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
|
@ -65,3 +74,7 @@ class VelbusClimate(VelbusEntity, ClimateDevice):
|
|||
return
|
||||
self._module.set_temp(temp)
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
pass
|
||||
|
|
|
@ -5,29 +5,30 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_HUMIDITY, SUPPORT_AWAY_MODE,
|
||||
SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW,
|
||||
SUPPORT_HOLD_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_HUMIDITY, SUPPORT_PRESET_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE, PRESET_AWAY,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
HVAC_MODE_OFF)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_TIMEOUT,
|
||||
CONF_USERNAME, PRECISION_WHOLE, STATE_OFF, STATE_ON, TEMP_CELSIUS,
|
||||
CONF_USERNAME, PRECISION_WHOLE, STATE_ON, TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_FAN_STATE = 'fan_state'
|
||||
ATTR_HVAC_STATE = 'hvac_state'
|
||||
ATTR_HVAC_STATE = 'hvac_mode'
|
||||
|
||||
CONF_HUMIDIFIER = 'humidifier'
|
||||
|
||||
DEFAULT_SSL = False
|
||||
|
||||
VALID_FAN_STATES = [STATE_ON, STATE_AUTO]
|
||||
VALID_THERMOSTAT_MODES = [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_AUTO]
|
||||
VALID_FAN_STATES = [STATE_ON, HVAC_MODE_AUTO]
|
||||
VALID_THERMOSTAT_MODES = [HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF,
|
||||
HVAC_MODE_AUTO]
|
||||
|
||||
HOLD_MODE_OFF = 'off'
|
||||
HOLD_MODE_TEMPERATURE = 'temperature'
|
||||
|
@ -84,18 +85,14 @@ class VenstarThermostat(ClimateDevice):
|
|||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
features = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE |
|
||||
SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE |
|
||||
SUPPORT_HOLD_MODE)
|
||||
SUPPORT_PRESET_MODE)
|
||||
|
||||
if self._client.mode == self._client.MODE_AUTO:
|
||||
features |= (SUPPORT_TARGET_TEMPERATURE_HIGH |
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
features |= (SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||
|
||||
if (self._humidifier and
|
||||
hasattr(self._client, 'hum_active')):
|
||||
features |= (SUPPORT_TARGET_HUMIDITY |
|
||||
SUPPORT_TARGET_HUMIDITY_HIGH |
|
||||
SUPPORT_TARGET_HUMIDITY_LOW)
|
||||
features |= SUPPORT_TARGET_HUMIDITY
|
||||
|
||||
return features
|
||||
|
||||
|
@ -121,12 +118,12 @@ class VenstarThermostat(ClimateDevice):
|
|||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return VALID_FAN_STATES
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return VALID_THERMOSTAT_MODES
|
||||
|
||||
|
@ -141,21 +138,21 @@ class VenstarThermostat(ClimateDevice):
|
|||
return self._client.get_indoor_humidity()
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
if self._client.mode == self._client.MODE_HEAT:
|
||||
return STATE_HEAT
|
||||
return HVAC_MODE_HEAT
|
||||
if self._client.mode == self._client.MODE_COOL:
|
||||
return STATE_COOL
|
||||
return HVAC_MODE_COOL
|
||||
if self._client.mode == self._client.MODE_AUTO:
|
||||
return STATE_AUTO
|
||||
return STATE_OFF
|
||||
return HVAC_MODE_AUTO
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
if self._client.fan == self._client.FAN_AUTO:
|
||||
return STATE_AUTO
|
||||
return HVAC_MODE_AUTO
|
||||
return STATE_ON
|
||||
|
||||
@property
|
||||
|
@ -205,24 +202,28 @@ class VenstarThermostat(ClimateDevice):
|
|||
return 60
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return the status of away mode."""
|
||||
return self._client.away == self._client.AWAY_AWAY
|
||||
|
||||
@property
|
||||
def current_hold_mode(self):
|
||||
"""Return the status of hold mode."""
|
||||
def preset_mode(self):
|
||||
"""Return current preset."""
|
||||
if self._client.away:
|
||||
return PRESET_AWAY
|
||||
if self._client.schedule == 0:
|
||||
return HOLD_MODE_TEMPERATURE
|
||||
return HOLD_MODE_OFF
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Return valid preset modes."""
|
||||
return [
|
||||
PRESET_AWAY,
|
||||
HOLD_MODE_TEMPERATURE,
|
||||
]
|
||||
|
||||
def _set_operation_mode(self, operation_mode):
|
||||
"""Change the operation mode (internal)."""
|
||||
if operation_mode == STATE_HEAT:
|
||||
if operation_mode == HVAC_MODE_HEAT:
|
||||
success = self._client.set_mode(self._client.MODE_HEAT)
|
||||
elif operation_mode == STATE_COOL:
|
||||
elif operation_mode == HVAC_MODE_COOL:
|
||||
success = self._client.set_mode(self._client.MODE_COOL)
|
||||
elif operation_mode == STATE_AUTO:
|
||||
elif operation_mode == HVAC_MODE_AUTO:
|
||||
success = self._client.set_mode(self._client.MODE_AUTO)
|
||||
else:
|
||||
success = self._client.set_mode(self._client.MODE_OFF)
|
||||
|
@ -234,7 +235,7 @@ class VenstarThermostat(ClimateDevice):
|
|||
def set_temperature(self, **kwargs):
|
||||
"""Set a new target temperature."""
|
||||
set_temp = True
|
||||
operation_mode = kwargs.get(ATTR_OPERATION_MODE, self._client.mode)
|
||||
operation_mode = kwargs.get(ATTR_HVAC_MODE, self._client.mode)
|
||||
temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
||||
temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
@ -268,9 +269,9 @@ class VenstarThermostat(ClimateDevice):
|
|||
if not success:
|
||||
_LOGGER.error("Failed to change the fan mode")
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target operation mode."""
|
||||
self._set_operation_mode(operation_mode)
|
||||
self._set_operation_mode(hvac_mode)
|
||||
|
||||
def set_humidity(self, humidity):
|
||||
"""Set new target humidity."""
|
||||
|
@ -279,29 +280,21 @@ class VenstarThermostat(ClimateDevice):
|
|||
if not success:
|
||||
_LOGGER.error("Failed to change the target humidity level")
|
||||
|
||||
def set_hold_mode(self, hold_mode):
|
||||
def set_preset_mode(self, preset_mode):
|
||||
"""Set the hold mode."""
|
||||
if hold_mode == HOLD_MODE_TEMPERATURE:
|
||||
if preset_mode == PRESET_AWAY:
|
||||
success = self._client.set_away(self._client.AWAY_AWAY)
|
||||
elif preset_mode == HOLD_MODE_TEMPERATURE:
|
||||
success = self._client.set_schedule(0)
|
||||
elif hold_mode == HOLD_MODE_OFF:
|
||||
success = self._client.set_schedule(1)
|
||||
elif preset_mode is None:
|
||||
success = False
|
||||
if self._client.away:
|
||||
success = self._client.set_away(self._client.AWAY_HOME)
|
||||
if self._client.schedule == 0:
|
||||
success = success and self._client.set_schedule(1)
|
||||
else:
|
||||
_LOGGER.error("Unknown hold mode: %s", hold_mode)
|
||||
_LOGGER.error("Unknown hold mode: %s", preset_mode)
|
||||
success = False
|
||||
|
||||
if not success:
|
||||
_LOGGER.error("Failed to change the schedule/hold state")
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Activate away mode."""
|
||||
success = self._client.set_away(self._client.AWAY_AWAY)
|
||||
|
||||
if not success:
|
||||
_LOGGER.error("Failed to activate away mode")
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Deactivate away mode."""
|
||||
success = self._client.set_away(self._client.AWAY_HOME)
|
||||
|
||||
if not success:
|
||||
_LOGGER.error("Failed to deactivate away mode")
|
||||
|
|
|
@ -3,21 +3,22 @@ import logging
|
|||
|
||||
from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
FAN_AUTO, FAN_ON, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_OFF, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
from homeassistant.util import convert
|
||||
|
||||
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
OPERATION_LIST = [STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_OFF]
|
||||
FAN_OPERATION_LIST = [STATE_ON, STATE_AUTO]
|
||||
FAN_OPERATION_LIST = [FAN_ON, FAN_AUTO]
|
||||
|
||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_FAN_MODE)
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
SUPPORT_HVAC = [
|
||||
HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF
|
||||
]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities_callback, discovery_info=None):
|
||||
|
@ -41,42 +42,44 @@ class VeraThermostat(VeraDevice, ClimateDevice):
|
|||
return SUPPORT_FLAGS
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
mode = self.vera_device.get_hvac_mode()
|
||||
if mode == 'HeatOn':
|
||||
return OPERATION_LIST[0] # Heat
|
||||
return HVAC_MODE_HEAT
|
||||
if mode == 'CoolOn':
|
||||
return OPERATION_LIST[1] # Cool
|
||||
return HVAC_MODE_COOL
|
||||
if mode == 'AutoChangeOver':
|
||||
return OPERATION_LIST[2] # Auto
|
||||
if mode == 'Off':
|
||||
return OPERATION_LIST[3] # Off
|
||||
return 'Off'
|
||||
return HVAC_MODE_HEAT_COOL
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return OPERATION_LIST
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return SUPPORT_HVAC
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
mode = self.vera_device.get_fan_mode()
|
||||
if mode == "ContinuousOn":
|
||||
return FAN_OPERATION_LIST[0] # on
|
||||
if mode == "Auto":
|
||||
return FAN_OPERATION_LIST[1] # auto
|
||||
return "Auto"
|
||||
return FAN_ON
|
||||
return FAN_AUTO
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return a list of available fan modes."""
|
||||
return FAN_OPERATION_LIST
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
"""Set new target temperature."""
|
||||
if fan_mode == FAN_OPERATION_LIST[0]:
|
||||
if fan_mode == FAN_ON:
|
||||
self.vera_device.fan_on()
|
||||
else:
|
||||
self.vera_device.fan_auto()
|
||||
|
@ -107,7 +110,7 @@ class VeraThermostat(VeraDevice, ClimateDevice):
|
|||
@property
|
||||
def operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self.vera_device.get_hvac_state()
|
||||
return self.vera_device.get_hvac_mode()
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
|
@ -119,21 +122,13 @@ class VeraThermostat(VeraDevice, ClimateDevice):
|
|||
if kwargs.get(ATTR_TEMPERATURE) is not None:
|
||||
self.vera_device.set_temperature(kwargs.get(ATTR_TEMPERATURE))
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set HVAC mode (auto, cool, heat, off)."""
|
||||
if operation_mode == OPERATION_LIST[3]: # off
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
self.vera_device.turn_off()
|
||||
elif operation_mode == OPERATION_LIST[2]: # auto
|
||||
elif hvac_mode == HVAC_MODE_HEAT_COOL:
|
||||
self.vera_device.turn_auto_on()
|
||||
elif operation_mode == OPERATION_LIST[1]: # cool
|
||||
elif hvac_mode == HVAC_MODE_COOL:
|
||||
self.vera_device.turn_cool_on()
|
||||
elif operation_mode == OPERATION_LIST[0]: # heat
|
||||
elif hvac_mode == HVAC_MODE_HEAT:
|
||||
self.vera_device.turn_heat_on()
|
||||
|
||||
def turn_fan_on(self):
|
||||
"""Turn fan on."""
|
||||
self.vera_device.fan_on()
|
||||
|
||||
def turn_fan_off(self):
|
||||
"""Turn fan off."""
|
||||
self.vera_device.fan_auto()
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
"""Support for Wink thermostats and Air Conditioners."""
|
||||
import logging
|
||||
|
||||
import pywink
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_CURRENT_HUMIDITY, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||
STATE_AUTO, STATE_COOL, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
|
||||
SUPPORT_AUX_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL,
|
||||
CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, FAN_AUTO, FAN_HIGH,
|
||||
FAN_LOW, FAN_MEDIUM, FAN_ON, HVAC_MODE_AUTO, HVAC_MODE_COOL,
|
||||
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_ECO,
|
||||
SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, PRECISION_TENTHS, STATE_OFF, STATE_ON, STATE_UNKNOWN,
|
||||
TEMP_CELSIUS)
|
||||
ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS)
|
||||
from homeassistant.helpers.temperature import display_temp as show_temp
|
||||
|
||||
from . import DOMAIN, WinkDevice
|
||||
|
@ -23,36 +25,30 @@ ATTR_OCCUPIED = 'occupied'
|
|||
ATTR_SCHEDULE_ENABLED = 'schedule_enabled'
|
||||
ATTR_SMART_TEMPERATURE = 'smart_temperature'
|
||||
ATTR_TOTAL_CONSUMPTION = 'total_consumption'
|
||||
ATTR_HEAT_ON = 'heat_on'
|
||||
ATTR_COOL_ON = 'cool_on'
|
||||
|
||||
SPEED_LOW = 'low'
|
||||
SPEED_MEDIUM = 'medium'
|
||||
SPEED_HIGH = 'high'
|
||||
|
||||
HA_STATE_TO_WINK = {
|
||||
STATE_AUTO: 'auto',
|
||||
STATE_COOL: 'cool_only',
|
||||
STATE_ECO: 'eco',
|
||||
STATE_FAN_ONLY: 'fan_only',
|
||||
STATE_HEAT: 'heat_only',
|
||||
STATE_OFF: 'off',
|
||||
HA_HVAC_TO_WINK = {
|
||||
HVAC_MODE_AUTO: 'auto',
|
||||
HVAC_MODE_COOL: 'cool_only',
|
||||
HVAC_MODE_FAN_ONLY: 'fan_only',
|
||||
HVAC_MODE_HEAT: 'heat_only',
|
||||
HVAC_MODE_OFF: 'off',
|
||||
}
|
||||
|
||||
WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()}
|
||||
WINK_HVAC_TO_HA = {value: key for key, value in HA_HVAC_TO_WINK.items()}
|
||||
|
||||
SUPPORT_FLAGS_THERMOSTAT = (
|
||||
SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH |
|
||||
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_AWAY_MODE | SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT)
|
||||
SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_RANGE |
|
||||
SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT)
|
||||
SUPPORT_FAN_THERMOSTAT = [FAN_AUTO, FAN_ON]
|
||||
SUPPORT_PRESET_THERMOSTAT = [PRESET_AWAY, PRESET_ECO]
|
||||
|
||||
SUPPORT_FLAGS_AC = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
|
||||
SUPPORT_FAN_MODE)
|
||||
SUPPORT_FLAGS_AC = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
SUPPORT_FAN_AC = [FAN_HIGH, FAN_LOW, FAN_MEDIUM]
|
||||
SUPPORT_PRESET_AC = [PRESET_ECO]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Wink climate devices."""
|
||||
import pywink
|
||||
for climate in pywink.get_thermostats():
|
||||
_id = climate.object_id() + climate.name()
|
||||
if _id not in hass.data[DOMAIN]['unique_ids']:
|
||||
|
@ -85,17 +81,6 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||
def device_state_attributes(self):
|
||||
"""Return the optional device state attributes."""
|
||||
data = {}
|
||||
target_temp_high = self.target_temperature_high
|
||||
target_temp_low = self.target_temperature_low
|
||||
if target_temp_high is not None:
|
||||
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
|
||||
self.hass, self.target_temperature_high, self.temperature_unit,
|
||||
PRECISION_TENTHS)
|
||||
if target_temp_low is not None:
|
||||
data[ATTR_TARGET_TEMP_LOW] = show_temp(
|
||||
self.hass, self.target_temperature_low, self.temperature_unit,
|
||||
PRECISION_TENTHS)
|
||||
|
||||
if self.external_temperature is not None:
|
||||
data[ATTR_EXTERNAL_TEMPERATURE] = show_temp(
|
||||
self.hass, self.external_temperature, self.temperature_unit,
|
||||
|
@ -110,16 +95,6 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||
if self.eco_target is not None:
|
||||
data[ATTR_ECO_TARGET] = self.eco_target
|
||||
|
||||
if self.heat_on is not None:
|
||||
data[ATTR_HEAT_ON] = self.heat_on
|
||||
|
||||
if self.cool_on is not None:
|
||||
data[ATTR_COOL_ON] = self.cool_on
|
||||
|
||||
current_humidity = self.current_humidity
|
||||
if current_humidity is not None:
|
||||
data[ATTR_CURRENT_HUMIDITY] = current_humidity
|
||||
|
||||
return data
|
||||
|
||||
@property
|
||||
|
@ -160,27 +135,19 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||
return self.wink.occupied()
|
||||
|
||||
@property
|
||||
def heat_on(self):
|
||||
"""Return whether or not the heat is actually heating."""
|
||||
return self.wink.heat_on()
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
mode = self.wink.current_mode()
|
||||
if mode == "eco":
|
||||
return PRESET_ECO
|
||||
if self.wink.away():
|
||||
return PRESET_AWAY
|
||||
return None
|
||||
|
||||
@property
|
||||
def cool_on(self):
|
||||
"""Return whether or not the heat is actually heating."""
|
||||
return self.wink.cool_on()
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
if not self.wink.is_on():
|
||||
current_op = STATE_OFF
|
||||
else:
|
||||
current_op = WINK_STATE_TO_HA.get(self.wink.current_hvac_mode())
|
||||
if current_op == 'aux':
|
||||
return STATE_HEAT
|
||||
if current_op is None:
|
||||
current_op = STATE_UNKNOWN
|
||||
return current_op
|
||||
def preset_modes(self):
|
||||
"""Return a list of available preset modes."""
|
||||
return SUPPORT_PRESET_THERMOSTAT
|
||||
|
||||
@property
|
||||
def target_humidity(self):
|
||||
|
@ -199,51 +166,96 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
if self.current_operation != STATE_AUTO and not self.is_away_mode_on:
|
||||
if self.current_operation == STATE_COOL:
|
||||
if self.hvac_mode != HVAC_MODE_AUTO and not self.wink.away():
|
||||
if self.hvac_mode == HVAC_MODE_COOL:
|
||||
return self.wink.current_max_set_point()
|
||||
if self.current_operation == STATE_HEAT:
|
||||
if self.hvac_mode == HVAC_MODE_HEAT:
|
||||
return self.wink.current_min_set_point()
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature_low(self):
|
||||
"""Return the lower bound temperature we try to reach."""
|
||||
if self.current_operation == STATE_AUTO:
|
||||
if self.hvac_mode == HVAC_MODE_AUTO:
|
||||
return self.wink.current_min_set_point()
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature_high(self):
|
||||
"""Return the higher bound temperature we try to reach."""
|
||||
if self.current_operation == STATE_AUTO:
|
||||
if self.hvac_mode == HVAC_MODE_AUTO:
|
||||
return self.wink.current_max_set_point()
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
"""Return if away mode is on."""
|
||||
return self.wink.away()
|
||||
|
||||
@property
|
||||
def is_aux_heat_on(self):
|
||||
def is_aux_heat(self):
|
||||
"""Return true if aux heater."""
|
||||
if 'aux' not in self.wink.hvac_modes():
|
||||
return None
|
||||
|
||||
if self.wink.current_hvac_mode() == 'aux':
|
||||
if self.wink.hvac_action_mode() == 'aux':
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
if not self.wink.is_on():
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
wink_mode = self.wink.current_mode()
|
||||
if wink_mode == "aux":
|
||||
return HVAC_MODE_HEAT
|
||||
if wink_mode == "eco":
|
||||
return HVAC_MODE_AUTO
|
||||
return WINK_HVAC_TO_HA.get(wink_mode)
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
hvac_list = [HVAC_MODE_OFF]
|
||||
|
||||
modes = self.wink.modes()
|
||||
for mode in modes:
|
||||
if mode in ("eco", "aux"):
|
||||
continue
|
||||
try:
|
||||
ha_mode = WINK_HVAC_TO_HA[mode]
|
||||
hvac_list.append(ha_mode)
|
||||
except KeyError:
|
||||
_LOGGER.error(
|
||||
"Invalid operation mode mapping. %s doesn't map. "
|
||||
"Please report this.", mode)
|
||||
return hvac_list
|
||||
|
||||
@property
|
||||
def hvac_action(self):
|
||||
"""Return the current running hvac operation if supported.
|
||||
|
||||
Need to be one of CURRENT_HVAC_*.
|
||||
"""
|
||||
if not self.wink.is_on():
|
||||
return CURRENT_HVAC_OFF
|
||||
if self.wink.cool_on:
|
||||
return CURRENT_HVAC_COOL
|
||||
if self.wink.heat_on:
|
||||
return CURRENT_HVAC_HEAT
|
||||
return CURRENT_HVAC_IDLE
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
target_temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
||||
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||
if target_temp is not None:
|
||||
if self.current_operation == STATE_COOL:
|
||||
if self.hvac_mode == HVAC_MODE_COOL:
|
||||
target_temp_high = target_temp
|
||||
if self.current_operation == STATE_HEAT:
|
||||
if self.hvac_mode == HVAC_MODE_HEAT:
|
||||
target_temp_low = target_temp
|
||||
if target_temp_low is not None:
|
||||
target_temp_low = target_temp_low
|
||||
|
@ -251,54 +263,37 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||
target_temp_high = target_temp_high
|
||||
self.wink.set_temperature(target_temp_low, target_temp_high)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set operation mode."""
|
||||
op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
|
||||
# The only way to disable aux heat is with the toggle
|
||||
if self.is_aux_heat_on and op_mode_to_set == STATE_HEAT:
|
||||
return
|
||||
self.wink.set_operation_mode(op_mode_to_set)
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
hvac_mode_to_set = HA_HVAC_TO_WINK.get(hvac_mode)
|
||||
self.wink.set_operation_mode(hvac_mode_to_set)
|
||||
|
||||
def set_preset_mode(self, preset_mode):
|
||||
"""Set new preset mode."""
|
||||
# Away
|
||||
if preset_mode != PRESET_AWAY and self.wink.away():
|
||||
self.wink.set_away_mode(False)
|
||||
elif preset_mode == PRESET_AWAY:
|
||||
self.wink.set_away_mode()
|
||||
|
||||
if preset_mode == PRESET_ECO:
|
||||
self.wink.set_operation_mode("eco")
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of available operation modes."""
|
||||
op_list = ['off']
|
||||
modes = self.wink.hvac_modes()
|
||||
for mode in modes:
|
||||
if mode == 'aux':
|
||||
continue
|
||||
ha_mode = WINK_STATE_TO_HA.get(mode)
|
||||
if ha_mode is not None:
|
||||
op_list.append(ha_mode)
|
||||
else:
|
||||
error = "Invalid operation mode mapping. " + mode + \
|
||||
" doesn't map. Please report this."
|
||||
_LOGGER.error(error)
|
||||
return op_list
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away on."""
|
||||
self.wink.set_away_mode()
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
self.wink.set_away_mode(False)
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return whether the fan is on."""
|
||||
if self.wink.current_fan_mode() == 'on':
|
||||
return STATE_ON
|
||||
return FAN_ON
|
||||
if self.wink.current_fan_mode() == 'auto':
|
||||
return STATE_AUTO
|
||||
return FAN_AUTO
|
||||
# No Fan available so disable slider
|
||||
return None
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""List of available fan modes."""
|
||||
if self.wink.has_fan():
|
||||
return self.wink.fan_modes()
|
||||
return SUPPORT_FAN_THERMOSTAT
|
||||
return None
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
|
@ -311,7 +306,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||
|
||||
def turn_aux_heat_off(self):
|
||||
"""Turn auxiliary heater off."""
|
||||
self.set_operation_mode(STATE_HEAT)
|
||||
self.wink.set_operation_mode('heat_only')
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
|
@ -319,17 +314,17 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||
minimum = 7 # Default minimum
|
||||
min_min = self.wink.min_min_set_point()
|
||||
min_max = self.wink.min_max_set_point()
|
||||
if self.current_operation == STATE_HEAT:
|
||||
if self.hvac_mode == HVAC_MODE_HEAT:
|
||||
if min_min:
|
||||
return_value = min_min
|
||||
else:
|
||||
return_value = minimum
|
||||
elif self.current_operation == STATE_COOL:
|
||||
elif self.hvac_mode == HVAC_MODE_COOL:
|
||||
if min_max:
|
||||
return_value = min_max
|
||||
else:
|
||||
return_value = minimum
|
||||
elif self.current_operation == STATE_AUTO:
|
||||
elif self.hvac_mode == HVAC_MODE_AUTO:
|
||||
if min_min and min_max:
|
||||
return_value = min(min_min, min_max)
|
||||
else:
|
||||
|
@ -344,17 +339,17 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||
maximum = 35 # Default maximum
|
||||
max_min = self.wink.max_min_set_point()
|
||||
max_max = self.wink.max_max_set_point()
|
||||
if self.current_operation == STATE_HEAT:
|
||||
if self.hvac_mode == HVAC_MODE_HEAT:
|
||||
if max_min:
|
||||
return_value = max_min
|
||||
else:
|
||||
return_value = maximum
|
||||
elif self.current_operation == STATE_COOL:
|
||||
elif self.hvac_mode == HVAC_MODE_COOL:
|
||||
if max_max:
|
||||
return_value = max_max
|
||||
else:
|
||||
return_value = maximum
|
||||
elif self.current_operation == STATE_AUTO:
|
||||
elif self.hvac_mode == HVAC_MODE_AUTO:
|
||||
if max_min and max_max:
|
||||
return_value = min(max_min, max_max)
|
||||
else:
|
||||
|
@ -382,16 +377,6 @@ class WinkAC(WinkDevice, ClimateDevice):
|
|||
def device_state_attributes(self):
|
||||
"""Return the optional device state attributes."""
|
||||
data = {}
|
||||
target_temp_high = self.target_temperature_high
|
||||
target_temp_low = self.target_temperature_low
|
||||
if target_temp_high is not None:
|
||||
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
|
||||
self.hass, self.target_temperature_high, self.temperature_unit,
|
||||
PRECISION_TENTHS)
|
||||
if target_temp_low is not None:
|
||||
data[ATTR_TARGET_TEMP_LOW] = show_temp(
|
||||
self.hass, self.target_temperature_low, self.temperature_unit,
|
||||
PRECISION_TENTHS)
|
||||
data[ATTR_TOTAL_CONSUMPTION] = self.wink.total_consumption()
|
||||
data[ATTR_SCHEDULE_ENABLED] = self.wink.schedule_enabled()
|
||||
|
||||
|
@ -403,47 +388,67 @@ class WinkAC(WinkDevice, ClimateDevice):
|
|||
return self.wink.current_temperature()
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. auto_eco, cool_only, fan_only."""
|
||||
if not self.wink.is_on():
|
||||
current_op = STATE_OFF
|
||||
else:
|
||||
wink_mode = self.wink.current_mode()
|
||||
if wink_mode == "auto_eco":
|
||||
wink_mode = "eco"
|
||||
current_op = WINK_STATE_TO_HA.get(wink_mode)
|
||||
if current_op is None:
|
||||
current_op = STATE_UNKNOWN
|
||||
return current_op
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
mode = self.wink.current_mode()
|
||||
if mode == "auto_eco":
|
||||
return PRESET_ECO
|
||||
return None
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""List of available operation modes."""
|
||||
op_list = ['off']
|
||||
def preset_modes(self):
|
||||
"""Return a list of available preset modes."""
|
||||
return SUPPORT_PRESET_AC
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
if not self.wink.is_on():
|
||||
return HVAC_MODE_OFF
|
||||
|
||||
wink_mode = self.wink.current_mode()
|
||||
if wink_mode == "auto_eco":
|
||||
return HVAC_MODE_AUTO
|
||||
return WINK_HVAC_TO_HA.get(wink_mode)
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
hvac_list = [HVAC_MODE_OFF]
|
||||
|
||||
modes = self.wink.modes()
|
||||
for mode in modes:
|
||||
if mode == "auto_eco":
|
||||
mode = "eco"
|
||||
ha_mode = WINK_STATE_TO_HA.get(mode)
|
||||
if ha_mode is not None:
|
||||
op_list.append(ha_mode)
|
||||
else:
|
||||
error = "Invalid operation mode mapping. " + mode + \
|
||||
" doesn't map. Please report this."
|
||||
_LOGGER.error(error)
|
||||
return op_list
|
||||
continue
|
||||
try:
|
||||
ha_mode = WINK_HVAC_TO_HA[mode]
|
||||
hvac_list.append(ha_mode)
|
||||
except KeyError:
|
||||
_LOGGER.error(
|
||||
"Invalid operation mode mapping. %s doesn't map. "
|
||||
"Please report this.", mode)
|
||||
return hvac_list
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
target_temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
self.wink.set_temperature(target_temp)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set operation mode."""
|
||||
op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
|
||||
if op_mode_to_set == 'eco':
|
||||
op_mode_to_set = 'auto_eco'
|
||||
self.wink.set_operation_mode(op_mode_to_set)
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
hvac_mode_to_set = HA_HVAC_TO_WINK.get(hvac_mode)
|
||||
self.wink.set_operation_mode(hvac_mode_to_set)
|
||||
|
||||
def set_preset_mode(self, preset_mode):
|
||||
"""Set new preset mode."""
|
||||
if preset_mode == PRESET_ECO:
|
||||
self.wink.set_operation_mode("auto_eco")
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
|
@ -451,7 +456,7 @@ class WinkAC(WinkDevice, ClimateDevice):
|
|||
return self.wink.current_max_set_point()
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""
|
||||
Return the current fan mode.
|
||||
|
||||
|
@ -460,15 +465,15 @@ class WinkAC(WinkDevice, ClimateDevice):
|
|||
"""
|
||||
speed = self.wink.current_fan_speed()
|
||||
if speed <= 0.33:
|
||||
return SPEED_LOW
|
||||
return FAN_LOW
|
||||
if speed <= 0.66:
|
||||
return SPEED_MEDIUM
|
||||
return SPEED_HIGH
|
||||
return FAN_MEDIUM
|
||||
return FAN_HIGH
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return a list of available fan modes."""
|
||||
return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||
return SUPPORT_FAN_AC
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
"""
|
||||
|
@ -477,10 +482,10 @@ class WinkAC(WinkDevice, ClimateDevice):
|
|||
The official Wink app only supports 3 modes [low, medium, high]
|
||||
which are equal to [0.33, 0.66, 1.0] respectively.
|
||||
"""
|
||||
if fan_mode == SPEED_LOW:
|
||||
if fan_mode == FAN_LOW:
|
||||
speed = 0.33
|
||||
elif fan_mode == SPEED_MEDIUM:
|
||||
elif fan_mode == FAN_MEDIUM:
|
||||
speed = 0.66
|
||||
elif fan_mode == SPEED_HIGH:
|
||||
elif fan_mode == FAN_HIGH:
|
||||
speed = 1.0
|
||||
self.wink.set_ac_fan_speed(speed)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
"""Support for the EZcontrol XS1 gateway."""
|
||||
import asyncio
|
||||
from functools import partial
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
import xs1_api_client
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_USERNAME)
|
||||
|
@ -40,20 +40,7 @@ XS1_COMPONENTS = [
|
|||
UPDATE_LOCK = asyncio.Lock()
|
||||
|
||||
|
||||
def _create_controller_api(host, port, ssl, user, password):
|
||||
"""Create an api instance to use for communication."""
|
||||
import xs1_api_client
|
||||
|
||||
try:
|
||||
return xs1_api_client.XS1(
|
||||
host=host, port=port, ssl=ssl, user=user, password=password)
|
||||
except ConnectionError as error:
|
||||
_LOGGER.error("Failed to create XS1 API client "
|
||||
"because of a connection error: %s", error)
|
||||
return None
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
def setup(hass, config):
|
||||
"""Set up XS1 Component."""
|
||||
_LOGGER.debug("Initializing XS1")
|
||||
|
||||
|
@ -64,9 +51,12 @@ async def async_setup(hass, config):
|
|||
password = config[DOMAIN].get(CONF_PASSWORD)
|
||||
|
||||
# initialize XS1 API
|
||||
xs1 = await hass.async_add_executor_job(
|
||||
partial(_create_controller_api, host, port, ssl, user, password))
|
||||
if xs1 is None:
|
||||
try:
|
||||
xs1 = xs1_api_client.XS1(
|
||||
host=host, port=port, ssl=ssl, user=user, password=password)
|
||||
except ConnectionError as error:
|
||||
_LOGGER.error("Failed to create XS1 API client "
|
||||
"because of a connection error: %s", error)
|
||||
return False
|
||||
|
||||
_LOGGER.debug(
|
||||
|
@ -74,10 +64,8 @@ async def async_setup(hass, config):
|
|||
|
||||
hass.data[DOMAIN] = {}
|
||||
|
||||
actuators = await hass.async_add_executor_job(
|
||||
partial(xs1.get_all_actuators, enabled=True))
|
||||
sensors = await hass.async_add_executor_job(
|
||||
partial(xs1.get_all_sensors, enabled=True))
|
||||
actuators = xs1.get_all_actuators(enabled=True)
|
||||
sensors = xs1.get_all_sensors(enabled=True)
|
||||
|
||||
hass.data[DOMAIN][ACTUATORS] = actuators
|
||||
hass.data[DOMAIN][SENSORS] = sensors
|
||||
|
@ -85,9 +73,7 @@ async def async_setup(hass, config):
|
|||
_LOGGER.debug("Loading components for XS1 platform...")
|
||||
# Load components for supported devices
|
||||
for component in XS1_COMPONENTS:
|
||||
hass.async_create_task(
|
||||
discovery.async_load_platform(
|
||||
hass, component, DOMAIN, {}, config))
|
||||
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -102,5 +88,4 @@ class XS1DeviceEntity(Entity):
|
|||
async def async_update(self):
|
||||
"""Retrieve latest device state."""
|
||||
async with UPDATE_LOCK:
|
||||
await self.hass.async_add_executor_job(
|
||||
partial(self.device.update))
|
||||
await self.hass.async_add_executor_job(self.device.update)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
"""Support for XS1 climate devices."""
|
||||
from functools import partial
|
||||
import logging
|
||||
|
||||
from xs1_api_client.api_constants import ActuatorType
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE
|
||||
from homeassistant.components.climate.const import (
|
||||
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_HEAT)
|
||||
from homeassistant.const import ATTR_TEMPERATURE
|
||||
|
||||
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity
|
||||
|
@ -13,12 +15,11 @@ _LOGGER = logging.getLogger(__name__)
|
|||
MIN_TEMP = 8
|
||||
MAX_TEMP = 25
|
||||
|
||||
SUPPORT_HVAC = [HVAC_MODE_HEAT]
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the XS1 thermostat platform."""
|
||||
from xs1_api_client.api_constants import ActuatorType
|
||||
|
||||
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
|
||||
sensors = hass.data[COMPONENT_DOMAIN][SENSORS]
|
||||
|
||||
|
@ -37,7 +38,7 @@ async def async_setup_platform(
|
|||
thermostat_entities.append(
|
||||
XS1ThermostatEntity(actuator, matching_sensor))
|
||||
|
||||
async_add_entities(thermostat_entities)
|
||||
add_entities(thermostat_entities)
|
||||
|
||||
|
||||
class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice):
|
||||
|
@ -58,6 +59,22 @@ class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice):
|
|||
"""Flag supported features."""
|
||||
return SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return SUPPORT_HVAC
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
|
@ -95,9 +112,12 @@ class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice):
|
|||
if self.sensor is not None:
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
pass
|
||||
|
||||
async def async_update(self):
|
||||
"""Also update the sensor when available."""
|
||||
await super().async_update()
|
||||
if self.sensor is not None:
|
||||
await self.hass.async_add_executor_job(
|
||||
partial(self.sensor.update))
|
||||
if self.sensor is None:
|
||||
await self.hass.async_add_executor_job(self.sensor.update)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Support for XS1 sensors."""
|
||||
import logging
|
||||
|
||||
from xs1_api_client.api_constants import ActuatorType
|
||||
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity
|
||||
|
@ -8,11 +10,8 @@ from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the XS1 sensor platform."""
|
||||
from xs1_api_client.api_constants import ActuatorType
|
||||
|
||||
sensors = hass.data[COMPONENT_DOMAIN][SENSORS]
|
||||
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
|
||||
|
||||
|
@ -28,7 +27,7 @@ async def async_setup_platform(
|
|||
if not belongs_to_climate_actuator:
|
||||
sensor_entities.append(XS1Sensor(sensor))
|
||||
|
||||
async_add_entities(sensor_entities)
|
||||
add_entities(sensor_entities)
|
||||
|
||||
|
||||
class XS1Sensor(XS1DeviceEntity, Entity):
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Support for XS1 switches."""
|
||||
import logging
|
||||
|
||||
from xs1_api_client.api_constants import ActuatorType
|
||||
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
|
||||
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity
|
||||
|
@ -8,11 +10,8 @@ from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the XS1 switch platform."""
|
||||
from xs1_api_client.api_constants import ActuatorType
|
||||
|
||||
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
|
||||
|
||||
switch_entities = []
|
||||
|
@ -21,7 +20,7 @@ async def async_setup_platform(
|
|||
(actuator.type() == ActuatorType.DIMMER):
|
||||
switch_entities.append(XS1SwitchEntity(actuator))
|
||||
|
||||
async_add_entities(switch_entities)
|
||||
add_entities(switch_entities)
|
||||
|
||||
|
||||
class XS1SwitchEntity(XS1DeviceEntity, ToggleEntity):
|
||||
|
|
|
@ -3,16 +3,17 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_OPERATION_MODE, STATE_COOL, STATE_DRY,
|
||||
STATE_FAN_ONLY, STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (ATTR_TEMPERATURE, CONF_HOST, CONF_PORT,
|
||||
EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS)
|
||||
ATTR_HVAC_MODE, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP,
|
||||
TEMP_CELSIUS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (async_dispatcher_connect,
|
||||
async_dispatcher_send)
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect, async_dispatcher_send)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -31,6 +32,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||
cv.positive_int,
|
||||
})
|
||||
|
||||
SUPPORT_HVAC = [HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_DRY,
|
||||
HVAC_MODE_FAN_ONLY]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the ZhongHong HVAC platform."""
|
||||
|
@ -86,7 +90,6 @@ class ZhongHongClimate(ClimateDevice):
|
|||
self._current_temperature = None
|
||||
self._target_temperature = None
|
||||
self._current_fan_mode = None
|
||||
self._is_on = None
|
||||
self.is_initialized = False
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
|
@ -106,7 +109,6 @@ class ZhongHongClimate(ClimateDevice):
|
|||
self._current_fan_mode = self._device.current_fan_mode
|
||||
if self._device.target_temperature:
|
||||
self._target_temperature = self._device.target_temperature
|
||||
self._is_on = self._device.is_on
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
|
@ -128,8 +130,7 @@ class ZhongHongClimate(ClimateDevice):
|
|||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
return (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
| SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF)
|
||||
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
|
@ -137,14 +138,14 @@ class ZhongHongClimate(ClimateDevice):
|
|||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
def hvac_mode(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._current_operation
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return [STATE_COOL, STATE_HEAT, STATE_DRY, STATE_FAN_ONLY]
|
||||
return SUPPORT_HVAC
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
|
@ -167,12 +168,12 @@ class ZhongHongClimate(ClimateDevice):
|
|||
return self._device.is_on
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan setting."""
|
||||
return self._current_fan_mode
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return the list of available fan modes."""
|
||||
return self._device.fan_list
|
||||
|
||||
|
@ -200,13 +201,13 @@ class ZhongHongClimate(ClimateDevice):
|
|||
if temperature is not None:
|
||||
self._device.set_temperature(temperature)
|
||||
|
||||
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
|
||||
operation_mode = kwargs.get(ATTR_HVAC_MODE)
|
||||
if operation_mode is not None:
|
||||
self.set_operation_mode(operation_mode)
|
||||
self.set_hvac_mode(operation_mode)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target operation mode."""
|
||||
self._device.set_operation_mode(operation_mode.upper())
|
||||
self._device.set_operation_mode(hvac_mode.upper())
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
"""Set new target fan mode."""
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
"""Support for Z-Wave climate devices."""
|
||||
# Because we do not compile openzwave on CI
|
||||
import logging
|
||||
from homeassistant.core import callback
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
DOMAIN, STATE_AUTO, STATE_COOL, STATE_HEAT,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE)
|
||||
from homeassistant.const import (
|
||||
STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
|
||||
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF,
|
||||
DOMAIN, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF,
|
||||
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from . import ZWaveDeviceEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -29,13 +30,21 @@ DEVICE_MAPPINGS = {
|
|||
REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120
|
||||
}
|
||||
|
||||
STATE_MAPPINGS = {
|
||||
'Off': STATE_OFF,
|
||||
'Heat': STATE_HEAT,
|
||||
'Heat Mode': STATE_HEAT,
|
||||
'Heat (Default)': STATE_HEAT,
|
||||
'Cool': STATE_COOL,
|
||||
'Auto': STATE_AUTO,
|
||||
HVAC_STATE_MAPPINGS = {
|
||||
'Off': HVAC_MODE_OFF,
|
||||
'Heat': HVAC_MODE_HEAT,
|
||||
'Heat Mode': HVAC_MODE_HEAT,
|
||||
'Heat (Default)': HVAC_MODE_HEAT,
|
||||
'Cool': HVAC_MODE_COOL,
|
||||
'Auto': HVAC_MODE_HEAT_COOL,
|
||||
}
|
||||
|
||||
|
||||
HVAC_CURRENT_MAPPINGS = {
|
||||
"Idle": CURRENT_HVAC_IDLE,
|
||||
"Heat": CURRENT_HVAC_HEAT,
|
||||
"Cool": CURRENT_HVAC_COOL,
|
||||
"Off": CURRENT_HVAC_OFF,
|
||||
}
|
||||
|
||||
|
||||
|
@ -69,15 +78,15 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
|
||||
self._target_temperature = None
|
||||
self._current_temperature = None
|
||||
self._current_operation = None
|
||||
self._operation_list = None
|
||||
self._operation_mapping = None
|
||||
self._operating_state = None
|
||||
self._hvac_action = None
|
||||
self._hvac_list = None
|
||||
self._hvac_mapping = None
|
||||
self._hvac_mode = None
|
||||
self._current_fan_mode = None
|
||||
self._fan_list = None
|
||||
self._fan_modes = None
|
||||
self._fan_state = None
|
||||
self._current_swing_mode = None
|
||||
self._swing_list = None
|
||||
self._swing_modes = None
|
||||
self._unit = temp_unit
|
||||
_LOGGER.debug("temp_unit is %s", self._unit)
|
||||
self._zxt_120 = None
|
||||
|
@ -100,8 +109,6 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||
support = SUPPORT_TARGET_TEMPERATURE
|
||||
if self.values.fan_mode:
|
||||
support |= SUPPORT_FAN_MODE
|
||||
if self.values.mode:
|
||||
support |= SUPPORT_OPERATION_MODE
|
||||
if self._zxt_120 == 1 and self.values.zxt_120_swing_mode:
|
||||
support |= SUPPORT_SWING_MODE
|
||||
return support
|
||||
|
@ -110,23 +117,23 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||
"""Handle the data changes for node values."""
|
||||
# Operation Mode
|
||||
if self.values.mode:
|
||||
self._operation_list = []
|
||||
self._operation_mapping = {}
|
||||
operation_list = self.values.mode.data_items
|
||||
if operation_list:
|
||||
for mode in operation_list:
|
||||
ha_mode = STATE_MAPPINGS.get(mode)
|
||||
if ha_mode and ha_mode not in self._operation_mapping:
|
||||
self._operation_mapping[ha_mode] = mode
|
||||
self._operation_list.append(ha_mode)
|
||||
self._hvac_list = []
|
||||
self._hvac_mapping = {}
|
||||
hvac_list = self.values.mode.data_items
|
||||
if hvac_list:
|
||||
for mode in hvac_list:
|
||||
ha_mode = HVAC_STATE_MAPPINGS.get(mode)
|
||||
if ha_mode and ha_mode not in self._hvac_mapping:
|
||||
self._hvac_mapping[ha_mode] = mode
|
||||
self._hvac_list.append(ha_mode)
|
||||
continue
|
||||
self._operation_list.append(mode)
|
||||
self._hvac_list.append(mode)
|
||||
current_mode = self.values.mode.data
|
||||
self._current_operation = next(
|
||||
(key for key, value in self._operation_mapping.items()
|
||||
self._hvac_mode = next(
|
||||
(key for key, value in self._hvac_mapping.items()
|
||||
if value == current_mode), current_mode)
|
||||
_LOGGER.debug("self._operation_list=%s", self._operation_list)
|
||||
_LOGGER.debug("self._current_operation=%s", self._current_operation)
|
||||
_LOGGER.debug("self._hvac_list=%s", self._hvac_list)
|
||||
_LOGGER.debug("self._hvac_action=%s", self._hvac_action)
|
||||
|
||||
# Current Temp
|
||||
if self.values.temperature:
|
||||
|
@ -138,20 +145,20 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||
# Fan Mode
|
||||
if self.values.fan_mode:
|
||||
self._current_fan_mode = self.values.fan_mode.data
|
||||
fan_list = self.values.fan_mode.data_items
|
||||
if fan_list:
|
||||
self._fan_list = list(fan_list)
|
||||
_LOGGER.debug("self._fan_list=%s", self._fan_list)
|
||||
fan_modes = self.values.fan_mode.data_items
|
||||
if fan_modes:
|
||||
self._fan_modes = list(fan_modes)
|
||||
_LOGGER.debug("self._fan_modes=%s", self._fan_modes)
|
||||
_LOGGER.debug("self._current_fan_mode=%s",
|
||||
self._current_fan_mode)
|
||||
# Swing mode
|
||||
if self._zxt_120 == 1:
|
||||
if self.values.zxt_120_swing_mode:
|
||||
self._current_swing_mode = self.values.zxt_120_swing_mode.data
|
||||
swing_list = self.values.zxt_120_swing_mode.data_items
|
||||
if swing_list:
|
||||
self._swing_list = list(swing_list)
|
||||
_LOGGER.debug("self._swing_list=%s", self._swing_list)
|
||||
swing_modes = self.values.zxt_120_swing_mode.data_items
|
||||
if swing_modes:
|
||||
self._swing_modes = list(swing_modes)
|
||||
_LOGGER.debug("self._swing_modes=%s", self._swing_modes)
|
||||
_LOGGER.debug("self._current_swing_mode=%s",
|
||||
self._current_swing_mode)
|
||||
# Set point
|
||||
|
@ -168,31 +175,32 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||
|
||||
# Operating state
|
||||
if self.values.operating_state:
|
||||
self._operating_state = self.values.operating_state.data
|
||||
mode = self.values.operating_state.data
|
||||
self._hvac_action = HVAC_CURRENT_MAPPINGS.get(mode)
|
||||
|
||||
# Fan operating state
|
||||
if self.values.fan_state:
|
||||
self._fan_state = self.values.fan_state.data
|
||||
|
||||
@property
|
||||
def current_fan_mode(self):
|
||||
def fan_mode(self):
|
||||
"""Return the fan speed set."""
|
||||
return self._current_fan_mode
|
||||
|
||||
@property
|
||||
def fan_list(self):
|
||||
def fan_modes(self):
|
||||
"""Return a list of available fan modes."""
|
||||
return self._fan_list
|
||||
return self._fan_modes
|
||||
|
||||
@property
|
||||
def current_swing_mode(self):
|
||||
def swing_mode(self):
|
||||
"""Return the swing mode set."""
|
||||
return self._current_swing_mode
|
||||
|
||||
@property
|
||||
def swing_list(self):
|
||||
def swing_modes(self):
|
||||
"""Return a list of available swing modes."""
|
||||
return self._swing_list
|
||||
return self._swing_modes
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
|
@ -209,14 +217,30 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||
return self._current_temperature
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return the current operation mode."""
|
||||
return self._current_operation
|
||||
def hvac_mode(self):
|
||||
"""Return hvac operation ie. heat, cool mode.
|
||||
|
||||
Need to be one of HVAC_MODE_*.
|
||||
"""
|
||||
if self.values.mode:
|
||||
return self._hvac_mode
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return a list of available operation modes."""
|
||||
return self._operation_list
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
||||
Need to be a subset of HVAC_MODES.
|
||||
"""
|
||||
return self._hvac_list
|
||||
|
||||
@property
|
||||
def hvac_action(self):
|
||||
"""Return the current running hvac operation if supported.
|
||||
|
||||
Need to be one of CURRENT_HVAC_*.
|
||||
"""
|
||||
return self._hvac_action
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
|
@ -225,36 +249,24 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
if kwargs.get(ATTR_TEMPERATURE) is not None:
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
else:
|
||||
if kwargs.get(ATTR_TEMPERATURE) is None:
|
||||
return
|
||||
|
||||
self.values.primary.data = temperature
|
||||
self.values.primary.data = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
"""Set new target fan mode."""
|
||||
if self.values.fan_mode:
|
||||
self.values.fan_mode.data = fan_mode
|
||||
if not self.values.fan_mode:
|
||||
return
|
||||
self.values.fan_mode.data = fan_mode
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set new target operation mode."""
|
||||
if self.values.mode:
|
||||
self.values.mode.data = self._operation_mapping.get(
|
||||
operation_mode, operation_mode)
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
if not self.values.mode:
|
||||
return
|
||||
self.values.mode.data = self._hvac_mapping.get(hvac_mode, hvac_mode)
|
||||
|
||||
def set_swing_mode(self, swing_mode):
|
||||
"""Set new target swing mode."""
|
||||
if self._zxt_120 == 1:
|
||||
if self.values.zxt_120_swing_mode:
|
||||
self.values.zxt_120_swing_mode.data = swing_mode
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
data = super().device_state_attributes
|
||||
if self._operating_state:
|
||||
data[ATTR_OPERATING_STATE] = self._operating_state
|
||||
if self._fan_state:
|
||||
data[ATTR_FAN_STATE] = self._fan_state
|
||||
return data
|
||||
|
|
|
@ -218,15 +218,11 @@ def state_as_number(state: State) -> float:
|
|||
|
||||
Raises ValueError if this is not possible.
|
||||
"""
|
||||
from homeassistant.components.climate.const import (
|
||||
STATE_HEAT, STATE_COOL, STATE_IDLE)
|
||||
|
||||
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON,
|
||||
STATE_OPEN, STATE_HOME, STATE_HEAT, STATE_COOL):
|
||||
STATE_OPEN, STATE_HOME):
|
||||
return 1
|
||||
if state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
|
||||
STATE_BELOW_HORIZON, STATE_CLOSED, STATE_NOT_HOME,
|
||||
STATE_IDLE):
|
||||
STATE_BELOW_HORIZON, STATE_CLOSED, STATE_NOT_HOME):
|
||||
return 0
|
||||
|
||||
return float(state.state)
|
||||
|
|
|
@ -10,7 +10,7 @@ certifi>=2019.6.16
|
|||
cryptography==2.7
|
||||
distro==1.4.0
|
||||
hass-nabucasa==0.15
|
||||
home-assistant-frontend==20190702.0
|
||||
home-assistant-frontend==20190705.0
|
||||
importlib-metadata==0.18
|
||||
jinja2>=2.10.1
|
||||
netdisco==2.6.0
|
||||
|
|
|
@ -442,8 +442,7 @@ eternalegypt==0.0.7
|
|||
# evdev==0.6.1
|
||||
|
||||
# homeassistant.components.evohome
|
||||
# homeassistant.components.honeywell
|
||||
evohomeclient==0.3.2
|
||||
evohomeclient==0.3.3
|
||||
|
||||
# homeassistant.components.dlib_face_detect
|
||||
# homeassistant.components.dlib_face_identify
|
||||
|
@ -602,7 +601,7 @@ hole==0.3.0
|
|||
holidays==0.9.10
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20190702.0
|
||||
home-assistant-frontend==20190705.0
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.4
|
||||
|
@ -611,7 +610,7 @@ homeassistant-pyozw==0.1.4
|
|||
homekit[IP]==0.14.0
|
||||
|
||||
# homeassistant.components.homematicip_cloud
|
||||
homematicip==0.10.7
|
||||
homematicip==0.10.9
|
||||
|
||||
# homeassistant.components.horizon
|
||||
horimote==0.4.1
|
||||
|
|
|
@ -109,8 +109,7 @@ enocean==0.50
|
|||
ephem==3.7.6.0
|
||||
|
||||
# homeassistant.components.evohome
|
||||
# homeassistant.components.honeywell
|
||||
evohomeclient==0.3.2
|
||||
evohomeclient==0.3.3
|
||||
|
||||
# homeassistant.components.feedreader
|
||||
feedparser-homeassistant==5.2.2.dev1
|
||||
|
@ -160,13 +159,13 @@ hdate==0.8.8
|
|||
holidays==0.9.10
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20190702.0
|
||||
home-assistant-frontend==20190705.0
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
homekit[IP]==0.14.0
|
||||
|
||||
# homeassistant.components.homematicip_cloud
|
||||
homematicip==0.10.7
|
||||
homematicip==0.10.9
|
||||
|
||||
# homeassistant.components.google
|
||||
# homeassistant.components.remember_the_milk
|
||||
|
|
|
@ -823,14 +823,15 @@ async def test_thermostat(hass):
|
|||
'climate.test_thermostat',
|
||||
'cool',
|
||||
{
|
||||
'operation_mode': 'cool',
|
||||
'temperature': 70.0,
|
||||
'target_temp_high': 80.0,
|
||||
'target_temp_low': 60.0,
|
||||
'current_temperature': 75.0,
|
||||
'friendly_name': "Test Thermostat",
|
||||
'supported_features': 1 | 2 | 4 | 128,
|
||||
'operation_list': ['heat', 'cool', 'auto', 'off'],
|
||||
'hvac_modes': ['heat', 'cool', 'auto', 'off'],
|
||||
'preset_mode': None,
|
||||
'preset_modes': ['eco'],
|
||||
'min_temp': 50,
|
||||
'max_temp': 90,
|
||||
}
|
||||
|
@ -948,22 +949,22 @@ async def test_thermostat(hass):
|
|||
# Setting mode, the payload can be an object with a value attribute...
|
||||
call, msg = await assert_request_calls_service(
|
||||
'Alexa.ThermostatController', 'SetThermostatMode',
|
||||
'climate#test_thermostat', 'climate.set_operation_mode',
|
||||
'climate#test_thermostat', 'climate.set_hvac_mode',
|
||||
hass,
|
||||
payload={'thermostatMode': {'value': 'HEAT'}}
|
||||
)
|
||||
assert call.data['operation_mode'] == 'heat'
|
||||
assert call.data['hvac_mode'] == 'heat'
|
||||
properties = ReportedProperties(msg['context']['properties'])
|
||||
properties.assert_equal(
|
||||
'Alexa.ThermostatController', 'thermostatMode', 'HEAT')
|
||||
|
||||
call, msg = await assert_request_calls_service(
|
||||
'Alexa.ThermostatController', 'SetThermostatMode',
|
||||
'climate#test_thermostat', 'climate.set_operation_mode',
|
||||
'climate#test_thermostat', 'climate.set_hvac_mode',
|
||||
hass,
|
||||
payload={'thermostatMode': {'value': 'COOL'}}
|
||||
)
|
||||
assert call.data['operation_mode'] == 'cool'
|
||||
assert call.data['hvac_mode'] == 'cool'
|
||||
properties = ReportedProperties(msg['context']['properties'])
|
||||
properties.assert_equal(
|
||||
'Alexa.ThermostatController', 'thermostatMode', 'COOL')
|
||||
|
@ -971,18 +972,18 @@ async def test_thermostat(hass):
|
|||
# ...it can also be just the mode.
|
||||
call, msg = await assert_request_calls_service(
|
||||
'Alexa.ThermostatController', 'SetThermostatMode',
|
||||
'climate#test_thermostat', 'climate.set_operation_mode',
|
||||
'climate#test_thermostat', 'climate.set_hvac_mode',
|
||||
hass,
|
||||
payload={'thermostatMode': 'HEAT'}
|
||||
)
|
||||
assert call.data['operation_mode'] == 'heat'
|
||||
assert call.data['hvac_mode'] == 'heat'
|
||||
properties = ReportedProperties(msg['context']['properties'])
|
||||
properties.assert_equal(
|
||||
'Alexa.ThermostatController', 'thermostatMode', 'HEAT')
|
||||
|
||||
msg = await assert_request_fails(
|
||||
'Alexa.ThermostatController', 'SetThermostatMode',
|
||||
'climate#test_thermostat', 'climate.set_operation_mode',
|
||||
'climate#test_thermostat', 'climate.set_hvac_mode',
|
||||
hass,
|
||||
payload={'thermostatMode': {'value': 'INVALID'}}
|
||||
)
|
||||
|
@ -991,11 +992,20 @@ async def test_thermostat(hass):
|
|||
|
||||
call, _ = await assert_request_calls_service(
|
||||
'Alexa.ThermostatController', 'SetThermostatMode',
|
||||
'climate#test_thermostat', 'climate.set_operation_mode',
|
||||
'climate#test_thermostat', 'climate.set_hvac_mode',
|
||||
hass,
|
||||
payload={'thermostatMode': 'OFF'}
|
||||
)
|
||||
assert call.data['operation_mode'] == 'off'
|
||||
assert call.data['hvac_mode'] == 'off'
|
||||
|
||||
# Assert we can call presets
|
||||
call, msg = await assert_request_calls_service(
|
||||
'Alexa.ThermostatController', 'SetThermostatMode',
|
||||
'climate#test_thermostat', 'climate.set_preset_mode',
|
||||
hass,
|
||||
payload={'thermostatMode': 'ECO'}
|
||||
)
|
||||
assert call.data['preset_mode'] == 'eco'
|
||||
|
||||
|
||||
async def test_exclude_filters(hass):
|
||||
|
|
|
@ -5,66 +5,39 @@ components. Instead call the service directly.
|
|||
"""
|
||||
from homeassistant.components.climate import _LOGGER
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_FAN_MODE, ATTR_HOLD_MODE,
|
||||
ATTR_HUMIDITY, ATTR_OPERATION_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AWAY_MODE, SERVICE_SET_HOLD_MODE,
|
||||
SERVICE_SET_AUX_HEAT, SERVICE_SET_TEMPERATURE, SERVICE_SET_HUMIDITY,
|
||||
SERVICE_SET_FAN_MODE, SERVICE_SET_OPERATION_MODE, SERVICE_SET_SWING_MODE)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE)
|
||||
ATTR_AUX_HEAT, ATTR_FAN_MODE, ATTR_HUMIDITY, ATTR_HVAC_MODE,
|
||||
ATTR_PRESET_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE,
|
||||
SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE,
|
||||
SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE
|
||||
from homeassistant.loader import bind_hass
|
||||
|
||||
|
||||
async def async_set_away_mode(hass, away_mode, entity_id=None):
|
||||
"""Turn all or specified climate devices away mode on."""
|
||||
async def async_set_preset_mode(hass, preset_mode, entity_id=None):
|
||||
"""Set new preset mode."""
|
||||
data = {
|
||||
ATTR_AWAY_MODE: away_mode
|
||||
ATTR_PRESET_MODE: preset_mode
|
||||
}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN, SERVICE_SET_AWAY_MODE, data, blocking=True)
|
||||
DOMAIN, SERVICE_SET_PRESET_MODE, data, blocking=True)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def set_away_mode(hass, away_mode, entity_id=None):
|
||||
"""Turn all or specified climate devices away mode on."""
|
||||
def set_preset_mode(hass, preset_mode, entity_id=None):
|
||||
"""Set new preset mode."""
|
||||
data = {
|
||||
ATTR_AWAY_MODE: away_mode
|
||||
ATTR_PRESET_MODE: preset_mode
|
||||
}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
|
||||
|
||||
|
||||
async def async_set_hold_mode(hass, hold_mode, entity_id=None):
|
||||
"""Set new hold mode."""
|
||||
data = {
|
||||
ATTR_HOLD_MODE: hold_mode
|
||||
}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN, SERVICE_SET_HOLD_MODE, data, blocking=True)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def set_hold_mode(hass, hold_mode, entity_id=None):
|
||||
"""Set new hold mode."""
|
||||
data = {
|
||||
ATTR_HOLD_MODE: hold_mode
|
||||
}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_HOLD_MODE, data)
|
||||
hass.services.call(DOMAIN, SERVICE_SET_PRESET_MODE, data)
|
||||
|
||||
|
||||
async def async_set_aux_heat(hass, aux_heat, entity_id=None):
|
||||
|
@ -95,7 +68,7 @@ def set_aux_heat(hass, aux_heat, entity_id=None):
|
|||
|
||||
async def async_set_temperature(hass, temperature=None, entity_id=None,
|
||||
target_temp_high=None, target_temp_low=None,
|
||||
operation_mode=None):
|
||||
hvac_mode=None):
|
||||
"""Set new target temperature."""
|
||||
kwargs = {
|
||||
key: value for key, value in [
|
||||
|
@ -103,7 +76,7 @@ async def async_set_temperature(hass, temperature=None, entity_id=None,
|
|||
(ATTR_TARGET_TEMP_HIGH, target_temp_high),
|
||||
(ATTR_TARGET_TEMP_LOW, target_temp_low),
|
||||
(ATTR_ENTITY_ID, entity_id),
|
||||
(ATTR_OPERATION_MODE, operation_mode)
|
||||
(ATTR_HVAC_MODE, hvac_mode)
|
||||
] if value is not None
|
||||
}
|
||||
_LOGGER.debug("set_temperature start data=%s", kwargs)
|
||||
|
@ -114,7 +87,7 @@ async def async_set_temperature(hass, temperature=None, entity_id=None,
|
|||
@bind_hass
|
||||
def set_temperature(hass, temperature=None, entity_id=None,
|
||||
target_temp_high=None, target_temp_low=None,
|
||||
operation_mode=None):
|
||||
hvac_mode=None):
|
||||
"""Set new target temperature."""
|
||||
kwargs = {
|
||||
key: value for key, value in [
|
||||
|
@ -122,7 +95,7 @@ def set_temperature(hass, temperature=None, entity_id=None,
|
|||
(ATTR_TARGET_TEMP_HIGH, target_temp_high),
|
||||
(ATTR_TARGET_TEMP_LOW, target_temp_low),
|
||||
(ATTR_ENTITY_ID, entity_id),
|
||||
(ATTR_OPERATION_MODE, operation_mode)
|
||||
(ATTR_HVAC_MODE, hvac_mode)
|
||||
] if value is not None
|
||||
}
|
||||
_LOGGER.debug("set_temperature start data=%s", kwargs)
|
||||
|
@ -173,26 +146,26 @@ def set_fan_mode(hass, fan, entity_id=None):
|
|||
hass.services.call(DOMAIN, SERVICE_SET_FAN_MODE, data)
|
||||
|
||||
|
||||
async def async_set_operation_mode(hass, operation_mode, entity_id=None):
|
||||
async def async_set_hvac_mode(hass, hvac_mode, entity_id=None):
|
||||
"""Set new target operation mode."""
|
||||
data = {ATTR_OPERATION_MODE: operation_mode}
|
||||
data = {ATTR_HVAC_MODE: hvac_mode}
|
||||
|
||||
if entity_id is not None:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN, SERVICE_SET_OPERATION_MODE, data, blocking=True)
|
||||
DOMAIN, SERVICE_SET_HVAC_MODE, data, blocking=True)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def set_operation_mode(hass, operation_mode, entity_id=None):
|
||||
def set_operation_mode(hass, hvac_mode, entity_id=None):
|
||||
"""Set new target operation mode."""
|
||||
data = {ATTR_OPERATION_MODE: operation_mode}
|
||||
data = {ATTR_HVAC_MODE: hvac_mode}
|
||||
|
||||
if entity_id is not None:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_OPERATION_MODE, data)
|
||||
hass.services.call(DOMAIN, SERVICE_SET_HVAC_MODE, data)
|
||||
|
||||
|
||||
async def async_set_swing_mode(hass, swing_mode, entity_id=None):
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
"""The tests for the climate component."""
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
@ -8,24 +7,22 @@ from homeassistant.components.climate import SET_TEMPERATURE_SCHEMA
|
|||
from tests.common import async_mock_service
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_set_temp_schema_no_req(hass, caplog):
|
||||
async def test_set_temp_schema_no_req(hass, caplog):
|
||||
"""Test the set temperature schema with missing required data."""
|
||||
domain = 'climate'
|
||||
service = 'test_set_temperature'
|
||||
schema = SET_TEMPERATURE_SCHEMA
|
||||
calls = async_mock_service(hass, domain, service, schema)
|
||||
|
||||
data = {'operation_mode': 'test', 'entity_id': ['climate.test_id']}
|
||||
data = {'hvac_mode': 'off', 'entity_id': ['climate.test_id']}
|
||||
with pytest.raises(vol.Invalid):
|
||||
yield from hass.services.async_call(domain, service, data)
|
||||
yield from hass.async_block_till_done()
|
||||
await hass.services.async_call(domain, service, data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 0
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_set_temp_schema(hass, caplog):
|
||||
async def test_set_temp_schema(hass, caplog):
|
||||
"""Test the set temperature schema with ok required data."""
|
||||
domain = 'climate'
|
||||
service = 'test_set_temperature'
|
||||
|
@ -33,10 +30,10 @@ def test_set_temp_schema(hass, caplog):
|
|||
calls = async_mock_service(hass, domain, service, schema)
|
||||
|
||||
data = {
|
||||
'temperature': 20.0, 'operation_mode': 'test',
|
||||
'temperature': 20.0, 'hvac_mode': 'heat',
|
||||
'entity_id': ['climate.test_id']}
|
||||
yield from hass.services.async_call(domain, service, data)
|
||||
yield from hass.async_block_till_done()
|
||||
await hass.services.async_call(domain, service, data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 1
|
||||
assert calls[-1].data == data
|
||||
|
|
|
@ -4,13 +4,12 @@ import pytest
|
|||
|
||||
from homeassistant.components.climate import async_reproduce_states
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_HOLD_MODE, ATTR_HUMIDITY,
|
||||
ATTR_OPERATION_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AUX_HEAT, SERVICE_SET_AWAY_MODE,
|
||||
SERVICE_SET_HOLD_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_OPERATION_MODE,
|
||||
SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, STATE_HEAT)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, STATE_ON)
|
||||
ATTR_AUX_HEAT, ATTR_HUMIDITY, ATTR_PRESET_MODE, ATTR_SWING_MODE,
|
||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, HVAC_MODE_AUTO,
|
||||
HVAC_MODE_HEAT, HVAC_MODE_OFF, SERVICE_SET_AUX_HEAT, SERVICE_SET_HUMIDITY,
|
||||
SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_SWING_MODE,
|
||||
SERVICE_SET_TEMPERATURE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE
|
||||
from homeassistant.core import Context, State
|
||||
|
||||
from tests.common import async_mock_service
|
||||
|
@ -20,13 +19,11 @@ ENTITY_2 = 'climate.test2'
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'service,state', [
|
||||
(SERVICE_TURN_ON, STATE_ON),
|
||||
(SERVICE_TURN_OFF, STATE_OFF),
|
||||
])
|
||||
async def test_state(hass, service, state):
|
||||
"""Test that we can turn a state into a service call."""
|
||||
calls_1 = async_mock_service(hass, DOMAIN, service)
|
||||
'state', [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||
)
|
||||
async def test_with_hvac_mode(hass, state):
|
||||
"""Test that state different hvac states."""
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_SET_HVAC_MODE)
|
||||
|
||||
await async_reproduce_states(hass, [
|
||||
State(ENTITY_1, state)
|
||||
|
@ -34,110 +31,66 @@ async def test_state(hass, service, state):
|
|||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls_1) == 1
|
||||
assert calls_1[0].data == {'entity_id': ENTITY_1}
|
||||
assert len(calls) == 1
|
||||
assert calls[0].data == {'entity_id': ENTITY_1, 'hvac_mode': state}
|
||||
|
||||
|
||||
async def test_turn_on_with_mode(hass):
|
||||
"""Test that state with additional attributes call multiple services."""
|
||||
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
|
||||
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_SET_OPERATION_MODE)
|
||||
async def test_multiple_state(hass):
|
||||
"""Test that multiple states gets calls."""
|
||||
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_SET_HVAC_MODE)
|
||||
|
||||
await async_reproduce_states(hass, [
|
||||
State(ENTITY_1, 'on',
|
||||
{ATTR_OPERATION_MODE: STATE_HEAT})
|
||||
])
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls_1) == 1
|
||||
assert calls_1[0].data == {'entity_id': ENTITY_1}
|
||||
|
||||
assert len(calls_2) == 1
|
||||
assert calls_2[0].data == {'entity_id': ENTITY_1,
|
||||
ATTR_OPERATION_MODE: STATE_HEAT}
|
||||
|
||||
|
||||
async def test_multiple_same_state(hass):
|
||||
"""Test that multiple states with same state gets calls."""
|
||||
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
|
||||
|
||||
await async_reproduce_states(hass, [
|
||||
State(ENTITY_1, 'on'),
|
||||
State(ENTITY_2, 'on'),
|
||||
State(ENTITY_1, HVAC_MODE_HEAT),
|
||||
State(ENTITY_2, HVAC_MODE_AUTO),
|
||||
])
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls_1) == 2
|
||||
# order is not guaranteed
|
||||
assert any(call.data == {'entity_id': ENTITY_1} for call in calls_1)
|
||||
assert any(call.data == {'entity_id': ENTITY_2} for call in calls_1)
|
||||
assert any(
|
||||
call.data == {'entity_id': ENTITY_1, 'hvac_mode': HVAC_MODE_HEAT}
|
||||
for call in calls_1)
|
||||
assert any(
|
||||
call.data == {'entity_id': ENTITY_2, 'hvac_mode': HVAC_MODE_AUTO}
|
||||
for call in calls_1)
|
||||
|
||||
|
||||
async def test_multiple_different_state(hass):
|
||||
"""Test that multiple states with different state gets calls."""
|
||||
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
|
||||
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF)
|
||||
async def test_state_with_none(hass):
|
||||
"""Test that none is not a hvac state."""
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_SET_HVAC_MODE)
|
||||
|
||||
await async_reproduce_states(hass, [
|
||||
State(ENTITY_1, 'on'),
|
||||
State(ENTITY_2, 'off'),
|
||||
State(ENTITY_1, None)
|
||||
])
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls_1) == 1
|
||||
assert calls_1[0].data == {'entity_id': ENTITY_1}
|
||||
assert len(calls_2) == 1
|
||||
assert calls_2[0].data == {'entity_id': ENTITY_2}
|
||||
assert len(calls) == 0
|
||||
|
||||
|
||||
async def test_state_with_context(hass):
|
||||
"""Test that context is forwarded."""
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_SET_HVAC_MODE)
|
||||
|
||||
context = Context()
|
||||
|
||||
await async_reproduce_states(hass, [
|
||||
State(ENTITY_1, 'on')
|
||||
State(ENTITY_1, HVAC_MODE_HEAT)
|
||||
], context)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 1
|
||||
assert calls[0].data == {'entity_id': ENTITY_1}
|
||||
assert calls[0].data == {'entity_id': ENTITY_1,
|
||||
'hvac_mode': HVAC_MODE_HEAT}
|
||||
assert calls[0].context == context
|
||||
|
||||
|
||||
async def test_attribute_no_state(hass):
|
||||
"""Test that no state service call is made with none state."""
|
||||
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
|
||||
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF)
|
||||
calls_3 = async_mock_service(hass, DOMAIN, SERVICE_SET_OPERATION_MODE)
|
||||
|
||||
value = "dummy"
|
||||
|
||||
await async_reproduce_states(hass, [
|
||||
State(ENTITY_1, None,
|
||||
{ATTR_OPERATION_MODE: value})
|
||||
])
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls_1) == 0
|
||||
assert len(calls_2) == 0
|
||||
assert len(calls_3) == 1
|
||||
assert calls_3[0].data == {'entity_id': ENTITY_1,
|
||||
ATTR_OPERATION_MODE: value}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'service,attribute', [
|
||||
(SERVICE_SET_OPERATION_MODE, ATTR_OPERATION_MODE),
|
||||
(SERVICE_SET_AUX_HEAT, ATTR_AUX_HEAT),
|
||||
(SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE),
|
||||
(SERVICE_SET_HOLD_MODE, ATTR_HOLD_MODE),
|
||||
(SERVICE_SET_PRESET_MODE, ATTR_PRESET_MODE),
|
||||
(SERVICE_SET_SWING_MODE, ATTR_SWING_MODE),
|
||||
(SERVICE_SET_HUMIDITY, ATTR_HUMIDITY),
|
||||
(SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE),
|
||||
|
|
|
@ -107,7 +107,8 @@ async def test_climate_devices(hass):
|
|||
{'state': {'on': False}})
|
||||
|
||||
await hass.services.async_call(
|
||||
'climate', 'turn_on', {'entity_id': 'climate.climate_1_name'},
|
||||
'climate', 'set_hvac_mode',
|
||||
{'entity_id': 'climate.climate_1_name', 'hvac_mode': 'heat'},
|
||||
blocking=True
|
||||
)
|
||||
gateway.api.session.put.assert_called_with(
|
||||
|
@ -116,7 +117,8 @@ async def test_climate_devices(hass):
|
|||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
'climate', 'turn_off', {'entity_id': 'climate.climate_1_name'},
|
||||
'climate', 'set_hvac_mode',
|
||||
{'entity_id': 'climate.climate_1_name', 'hvac_mode': 'off'},
|
||||
blocking=True
|
||||
)
|
||||
gateway.api.session.put.assert_called_with(
|
||||
|
@ -143,7 +145,7 @@ async def test_verify_state_update(hass):
|
|||
assert "climate.climate_1_name" in gateway.deconz_ids
|
||||
|
||||
thermostat = hass.states.get('climate.climate_1_name')
|
||||
assert thermostat.state == 'on'
|
||||
assert thermostat.state == 'off'
|
||||
|
||||
state_update = {
|
||||
"t": "event",
|
||||
|
|
|
@ -1,284 +1,281 @@
|
|||
"""The tests for the demo climate component."""
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.util.unit_system import (
|
||||
METRIC_SYSTEM
|
||||
)
|
||||
from homeassistant.setup import setup_component
|
||||
from homeassistant.components.climate import (
|
||||
DOMAIN, SERVICE_TURN_OFF, SERVICE_TURN_ON)
|
||||
from homeassistant.const import (ATTR_ENTITY_ID)
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_HVAC_ACTIONS,
|
||||
ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, ATTR_HUMIDITY, ATTR_HVAC_MODES,
|
||||
ATTR_MAX_HUMIDITY, ATTR_MAX_TEMP, ATTR_MIN_HUMIDITY, ATTR_MIN_TEMP,
|
||||
ATTR_PRESET_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, DOMAIN,
|
||||
HVAC_MODE_COOL, HVAC_MODE_HEAT, PRESET_AWAY, PRESET_ECO)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, STATE_ON
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
from tests.components.climate import common
|
||||
|
||||
|
||||
ENTITY_CLIMATE = 'climate.hvac'
|
||||
ENTITY_ECOBEE = 'climate.ecobee'
|
||||
ENTITY_HEATPUMP = 'climate.heatpump'
|
||||
|
||||
|
||||
class TestDemoClimate(unittest.TestCase):
|
||||
"""Test the demo climate hvac."""
|
||||
@pytest.fixture(autouse=True)
|
||||
async def setup_demo_climate(hass):
|
||||
"""Initialize setup demo climate."""
|
||||
hass.config.units = METRIC_SYSTEM
|
||||
assert await async_setup_component(hass, DOMAIN, {
|
||||
'climate': {
|
||||
'platform': 'demo',
|
||||
}
|
||||
})
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
"""Set up things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.hass.config.units = METRIC_SYSTEM
|
||||
assert setup_component(self.hass, DOMAIN, {
|
||||
'climate': {
|
||||
'platform': 'demo',
|
||||
}})
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
def test_setup_params(hass):
|
||||
"""Test the initial parameters."""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert state.state == HVAC_MODE_COOL
|
||||
assert 21 == state.attributes.get(ATTR_TEMPERATURE)
|
||||
assert 22 == state.attributes.get(ATTR_CURRENT_TEMPERATURE)
|
||||
assert "On High" == state.attributes.get(ATTR_FAN_MODE)
|
||||
assert 67 == state.attributes.get(ATTR_HUMIDITY)
|
||||
assert 54 == state.attributes.get(ATTR_CURRENT_HUMIDITY)
|
||||
assert "Off" == state.attributes.get(ATTR_SWING_MODE)
|
||||
assert STATE_OFF == state.attributes.get(ATTR_AUX_HEAT)
|
||||
assert state.attributes.get(ATTR_HVAC_MODES) == \
|
||||
['off', 'heat', 'cool', 'auto', 'dry', 'fan_only']
|
||||
|
||||
def test_setup_params(self):
|
||||
"""Test the initial parameters."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 21 == state.attributes.get('temperature')
|
||||
assert 'on' == state.attributes.get('away_mode')
|
||||
assert 22 == state.attributes.get('current_temperature')
|
||||
assert "On High" == state.attributes.get('fan_mode')
|
||||
assert 67 == state.attributes.get('humidity')
|
||||
assert 54 == state.attributes.get('current_humidity')
|
||||
assert "Off" == state.attributes.get('swing_mode')
|
||||
assert "cool" == state.attributes.get('operation_mode')
|
||||
assert 'off' == state.attributes.get('aux_heat')
|
||||
|
||||
def test_default_setup_params(self):
|
||||
"""Test the setup with default parameters."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 7 == state.attributes.get('min_temp')
|
||||
assert 35 == state.attributes.get('max_temp')
|
||||
assert 30 == state.attributes.get('min_humidity')
|
||||
assert 99 == state.attributes.get('max_humidity')
|
||||
def test_default_setup_params(hass):
|
||||
"""Test the setup with default parameters."""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert 7 == state.attributes.get(ATTR_MIN_TEMP)
|
||||
assert 35 == state.attributes.get(ATTR_MAX_TEMP)
|
||||
assert 30 == state.attributes.get(ATTR_MIN_HUMIDITY)
|
||||
assert 99 == state.attributes.get(ATTR_MAX_HUMIDITY)
|
||||
|
||||
def test_set_only_target_temp_bad_attr(self):
|
||||
"""Test setting the target temperature without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 21 == state.attributes.get('temperature')
|
||||
with pytest.raises(vol.Invalid):
|
||||
common.set_temperature(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
assert 21 == state.attributes.get('temperature')
|
||||
|
||||
def test_set_only_target_temp(self):
|
||||
"""Test the setting of the target temperature."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 21 == state.attributes.get('temperature')
|
||||
common.set_temperature(self.hass, 30, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 30.0 == state.attributes.get('temperature')
|
||||
async def test_set_only_target_temp_bad_attr(hass):
|
||||
"""Test setting the target temperature without required attribute."""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert 21 == state.attributes.get(ATTR_TEMPERATURE)
|
||||
|
||||
def test_set_only_target_temp_with_convert(self):
|
||||
"""Test the setting of the target temperature."""
|
||||
state = self.hass.states.get(ENTITY_HEATPUMP)
|
||||
assert 20 == state.attributes.get('temperature')
|
||||
common.set_temperature(self.hass, 21, ENTITY_HEATPUMP)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_HEATPUMP)
|
||||
assert 21.0 == state.attributes.get('temperature')
|
||||
with pytest.raises(vol.Invalid):
|
||||
await common.async_set_temperature(hass, None, ENTITY_CLIMATE)
|
||||
|
||||
def test_set_target_temp_range(self):
|
||||
"""Test the setting of the target temperature with range."""
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
assert state.attributes.get('temperature') is None
|
||||
assert 21.0 == state.attributes.get('target_temp_low')
|
||||
assert 24.0 == state.attributes.get('target_temp_high')
|
||||
common.set_temperature(self.hass, target_temp_high=25,
|
||||
target_temp_low=20, entity_id=ENTITY_ECOBEE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
assert state.attributes.get('temperature') is None
|
||||
assert 20.0 == state.attributes.get('target_temp_low')
|
||||
assert 25.0 == state.attributes.get('target_temp_high')
|
||||
await hass.async_block_till_done()
|
||||
assert 21 == state.attributes.get(ATTR_TEMPERATURE)
|
||||
|
||||
def test_set_target_temp_range_bad_attr(self):
|
||||
"""Test setting the target temperature range without attribute."""
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
assert state.attributes.get('temperature') is None
|
||||
assert 21.0 == state.attributes.get('target_temp_low')
|
||||
assert 24.0 == state.attributes.get('target_temp_high')
|
||||
with pytest.raises(vol.Invalid):
|
||||
common.set_temperature(self.hass, temperature=None,
|
||||
entity_id=ENTITY_ECOBEE,
|
||||
target_temp_low=None,
|
||||
target_temp_high=None)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
assert state.attributes.get('temperature') is None
|
||||
assert 21.0 == state.attributes.get('target_temp_low')
|
||||
assert 24.0 == state.attributes.get('target_temp_high')
|
||||
|
||||
def test_set_target_humidity_bad_attr(self):
|
||||
"""Test setting the target humidity without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 67 == state.attributes.get('humidity')
|
||||
with pytest.raises(vol.Invalid):
|
||||
common.set_humidity(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 67 == state.attributes.get('humidity')
|
||||
async def test_set_only_target_temp(hass):
|
||||
"""Test the setting of the target temperature."""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert 21 == state.attributes.get(ATTR_TEMPERATURE)
|
||||
|
||||
def test_set_target_humidity(self):
|
||||
"""Test the setting of the target humidity."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 67 == state.attributes.get('humidity')
|
||||
common.set_humidity(self.hass, 64, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 64.0 == state.attributes.get('humidity')
|
||||
await common.async_set_temperature(hass, 30, ENTITY_CLIMATE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
def test_set_fan_mode_bad_attr(self):
|
||||
"""Test setting fan mode without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert "On High" == state.attributes.get('fan_mode')
|
||||
with pytest.raises(vol.Invalid):
|
||||
common.set_fan_mode(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert "On High" == state.attributes.get('fan_mode')
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert 30.0 == state.attributes.get(ATTR_TEMPERATURE)
|
||||
|
||||
def test_set_fan_mode(self):
|
||||
"""Test setting of new fan mode."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert "On High" == state.attributes.get('fan_mode')
|
||||
common.set_fan_mode(self.hass, "On Low", ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert "On Low" == state.attributes.get('fan_mode')
|
||||
|
||||
def test_set_swing_mode_bad_attr(self):
|
||||
"""Test setting swing mode without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert "Off" == state.attributes.get('swing_mode')
|
||||
with pytest.raises(vol.Invalid):
|
||||
common.set_swing_mode(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert "Off" == state.attributes.get('swing_mode')
|
||||
async def test_set_only_target_temp_with_convert(hass):
|
||||
"""Test the setting of the target temperature."""
|
||||
state = hass.states.get(ENTITY_HEATPUMP)
|
||||
assert 20 == state.attributes.get(ATTR_TEMPERATURE)
|
||||
|
||||
def test_set_swing(self):
|
||||
"""Test setting of new swing mode."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert "Off" == state.attributes.get('swing_mode')
|
||||
common.set_swing_mode(self.hass, "Auto", ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert "Auto" == state.attributes.get('swing_mode')
|
||||
await common.async_set_temperature(hass, 21, ENTITY_HEATPUMP)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
def test_set_operation_bad_attr_and_state(self):
|
||||
"""Test setting operation mode without required attribute.
|
||||
state = hass.states.get(ENTITY_HEATPUMP)
|
||||
assert 21.0 == state.attributes.get(ATTR_TEMPERATURE)
|
||||
|
||||
Also check the state.
|
||||
"""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert "cool" == state.attributes.get('operation_mode')
|
||||
assert "cool" == state.state
|
||||
with pytest.raises(vol.Invalid):
|
||||
common.set_operation_mode(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert "cool" == state.attributes.get('operation_mode')
|
||||
assert "cool" == state.state
|
||||
|
||||
def test_set_operation(self):
|
||||
"""Test setting of new operation mode."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert "cool" == state.attributes.get('operation_mode')
|
||||
assert "cool" == state.state
|
||||
common.set_operation_mode(self.hass, "heat", ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert "heat" == state.attributes.get('operation_mode')
|
||||
assert "heat" == state.state
|
||||
async def test_set_target_temp_range(hass):
|
||||
"""Test the setting of the target temperature with range."""
|
||||
state = hass.states.get(ENTITY_ECOBEE)
|
||||
assert state.attributes.get(ATTR_TEMPERATURE) is None
|
||||
assert 21.0 == state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||
assert 24.0 == state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||
|
||||
def test_set_away_mode_bad_attr(self):
|
||||
"""Test setting the away mode without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 'on' == state.attributes.get('away_mode')
|
||||
with pytest.raises(vol.Invalid):
|
||||
common.set_away_mode(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
assert 'on' == state.attributes.get('away_mode')
|
||||
await common.async_set_temperature(
|
||||
hass, target_temp_high=25, target_temp_low=20, entity_id=ENTITY_ECOBEE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
def test_set_away_mode_on(self):
|
||||
"""Test setting the away mode on/true."""
|
||||
common.set_away_mode(self.hass, True, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 'on' == state.attributes.get('away_mode')
|
||||
state = hass.states.get(ENTITY_ECOBEE)
|
||||
assert state.attributes.get(ATTR_TEMPERATURE) is None
|
||||
assert 20.0 == state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||
assert 25.0 == state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||
|
||||
def test_set_away_mode_off(self):
|
||||
"""Test setting the away mode off/false."""
|
||||
common.set_away_mode(self.hass, False, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 'off' == state.attributes.get('away_mode')
|
||||
|
||||
def test_set_hold_mode_home(self):
|
||||
"""Test setting the hold mode home."""
|
||||
common.set_hold_mode(self.hass, 'home', ENTITY_ECOBEE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
assert 'home' == state.attributes.get('hold_mode')
|
||||
async def test_set_target_temp_range_bad_attr(hass):
|
||||
"""Test setting the target temperature range without attribute."""
|
||||
state = hass.states.get(ENTITY_ECOBEE)
|
||||
assert state.attributes.get(ATTR_TEMPERATURE) is None
|
||||
assert 21.0 == state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||
assert 24.0 == state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||
|
||||
def test_set_hold_mode_away(self):
|
||||
"""Test setting the hold mode away."""
|
||||
common.set_hold_mode(self.hass, 'away', ENTITY_ECOBEE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
assert 'away' == state.attributes.get('hold_mode')
|
||||
with pytest.raises(vol.Invalid):
|
||||
await common.async_set_temperature(
|
||||
hass, temperature=None, entity_id=ENTITY_ECOBEE,
|
||||
target_temp_low=None, target_temp_high=None)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
def test_set_hold_mode_none(self):
|
||||
"""Test setting the hold mode off/false."""
|
||||
common.set_hold_mode(self.hass, 'off', ENTITY_ECOBEE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
assert 'off' == state.attributes.get('hold_mode')
|
||||
state = hass.states.get(ENTITY_ECOBEE)
|
||||
assert state.attributes.get(ATTR_TEMPERATURE) is None
|
||||
assert 21.0 == state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||
assert 24.0 == state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||
|
||||
def test_set_aux_heat_bad_attr(self):
|
||||
"""Test setting the auxiliary heater without required attribute."""
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 'off' == state.attributes.get('aux_heat')
|
||||
with pytest.raises(vol.Invalid):
|
||||
common.set_aux_heat(self.hass, None, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
assert 'off' == state.attributes.get('aux_heat')
|
||||
|
||||
def test_set_aux_heat_on(self):
|
||||
"""Test setting the axillary heater on/true."""
|
||||
common.set_aux_heat(self.hass, True, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 'on' == state.attributes.get('aux_heat')
|
||||
async def test_set_target_humidity_bad_attr(hass):
|
||||
"""Test setting the target humidity without required attribute."""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert 67 == state.attributes.get(ATTR_HUMIDITY)
|
||||
|
||||
def test_set_aux_heat_off(self):
|
||||
"""Test setting the auxiliary heater off/false."""
|
||||
common.set_aux_heat(self.hass, False, ENTITY_CLIMATE)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||
assert 'off' == state.attributes.get('aux_heat')
|
||||
with pytest.raises(vol.Invalid):
|
||||
await common.async_set_humidity(hass, None, ENTITY_CLIMATE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
def test_set_on_off(self):
|
||||
"""Test on/off service."""
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
assert 'auto' == state.state
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert 67 == state.attributes.get(ATTR_HUMIDITY)
|
||||
|
||||
self.hass.services.call(DOMAIN, SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: ENTITY_ECOBEE})
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
assert 'off' == state.state
|
||||
|
||||
self.hass.services.call(DOMAIN, SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ENTITY_ECOBEE})
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
||||
assert 'auto' == state.state
|
||||
async def test_set_target_humidity(hass):
|
||||
"""Test the setting of the target humidity."""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert 67 == state.attributes.get(ATTR_HUMIDITY)
|
||||
|
||||
await common.async_set_humidity(hass, 64, ENTITY_CLIMATE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert 64.0 == state.attributes.get(ATTR_HUMIDITY)
|
||||
|
||||
|
||||
async def test_set_fan_mode_bad_attr(hass):
|
||||
"""Test setting fan mode without required attribute."""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert "On High" == state.attributes.get(ATTR_FAN_MODE)
|
||||
|
||||
with pytest.raises(vol.Invalid):
|
||||
await common.async_set_fan_mode(hass, None, ENTITY_CLIMATE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert "On High" == state.attributes.get(ATTR_FAN_MODE)
|
||||
|
||||
|
||||
async def test_set_fan_mode(hass):
|
||||
"""Test setting of new fan mode."""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert "On High" == state.attributes.get(ATTR_FAN_MODE)
|
||||
|
||||
await common.async_set_fan_mode(hass, "On Low", ENTITY_CLIMATE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert "On Low" == state.attributes.get(ATTR_FAN_MODE)
|
||||
|
||||
|
||||
async def test_set_swing_mode_bad_attr(hass):
|
||||
"""Test setting swing mode without required attribute."""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert "Off" == state.attributes.get(ATTR_SWING_MODE)
|
||||
|
||||
with pytest.raises(vol.Invalid):
|
||||
await common.async_set_swing_mode(hass, None, ENTITY_CLIMATE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert "Off" == state.attributes.get(ATTR_SWING_MODE)
|
||||
|
||||
|
||||
async def test_set_swing(hass):
|
||||
"""Test setting of new swing mode."""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert "Off" == state.attributes.get(ATTR_SWING_MODE)
|
||||
|
||||
await common.async_set_swing_mode(hass, "Auto", ENTITY_CLIMATE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert "Auto" == state.attributes.get(ATTR_SWING_MODE)
|
||||
|
||||
|
||||
async def test_set_hvac_bad_attr_and_state(hass):
|
||||
"""Test setting hvac mode without required attribute.
|
||||
|
||||
Also check the state.
|
||||
"""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert state.attributes.get(ATTR_HVAC_ACTIONS) == CURRENT_HVAC_COOL
|
||||
assert state.state == HVAC_MODE_COOL
|
||||
|
||||
with pytest.raises(vol.Invalid):
|
||||
await common.async_set_hvac_mode(hass, None, ENTITY_CLIMATE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert state.attributes.get(ATTR_HVAC_ACTIONS) == CURRENT_HVAC_COOL
|
||||
assert state.state == HVAC_MODE_COOL
|
||||
|
||||
|
||||
async def test_set_hvac(hass):
|
||||
"""Test setting of new hvac mode."""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert state.state == HVAC_MODE_COOL
|
||||
|
||||
await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT, ENTITY_CLIMATE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert state.state == HVAC_MODE_HEAT
|
||||
|
||||
|
||||
async def test_set_hold_mode_away(hass):
|
||||
"""Test setting the hold mode away."""
|
||||
await common.async_set_preset_mode(hass, PRESET_AWAY, ENTITY_ECOBEE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ECOBEE)
|
||||
assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY
|
||||
|
||||
|
||||
async def test_set_hold_mode_eco(hass):
|
||||
"""Test setting the hold mode eco."""
|
||||
await common.async_set_preset_mode(hass, PRESET_ECO, ENTITY_ECOBEE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ECOBEE)
|
||||
assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ECO
|
||||
|
||||
|
||||
async def test_set_aux_heat_bad_attr(hass):
|
||||
"""Test setting the auxiliary heater without required attribute."""
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF
|
||||
|
||||
with pytest.raises(vol.Invalid):
|
||||
await common.async_set_aux_heat(hass, None, ENTITY_CLIMATE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF
|
||||
|
||||
|
||||
async def test_set_aux_heat_on(hass):
|
||||
"""Test setting the axillary heater on/true."""
|
||||
await common.async_set_aux_heat(hass, True, ENTITY_CLIMATE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert state.attributes.get(ATTR_AUX_HEAT) == STATE_ON
|
||||
|
||||
|
||||
async def test_set_aux_heat_off(hass):
|
||||
"""Test setting the auxiliary heater off/false."""
|
||||
await common.async_set_aux_heat(hass, False, ENTITY_CLIMATE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_CLIMATE)
|
||||
assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF
|
||||
|
|
|
@ -230,45 +230,45 @@ class DysonTest(unittest.TestCase):
|
|||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||
assert not entity.should_poll
|
||||
|
||||
entity.set_fan_mode(dyson.STATE_FOCUS)
|
||||
entity.set_fan_mode(dyson.FAN_FOCUS)
|
||||
set_config = device.set_configuration
|
||||
set_config.assert_called_with(focus_mode=FocusMode.FOCUS_ON)
|
||||
|
||||
entity.set_fan_mode(dyson.STATE_DIFFUSE)
|
||||
entity.set_fan_mode(dyson.FAN_DIFFUSE)
|
||||
set_config = device.set_configuration
|
||||
set_config.assert_called_with(focus_mode=FocusMode.FOCUS_OFF)
|
||||
|
||||
def test_dyson_fan_list(self):
|
||||
def test_dyson_fan_modes(self):
|
||||
"""Test get fan list."""
|
||||
device = _get_device_heat_on()
|
||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||
assert len(entity.fan_list) == 2
|
||||
assert dyson.STATE_FOCUS in entity.fan_list
|
||||
assert dyson.STATE_DIFFUSE in entity.fan_list
|
||||
assert len(entity.fan_modes) == 2
|
||||
assert dyson.FAN_FOCUS in entity.fan_modes
|
||||
assert dyson.FAN_DIFFUSE in entity.fan_modes
|
||||
|
||||
def test_dyson_fan_mode_focus(self):
|
||||
"""Test fan focus mode."""
|
||||
device = _get_device_focus()
|
||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||
assert entity.current_fan_mode == dyson.STATE_FOCUS
|
||||
assert entity.fan_mode == dyson.FAN_FOCUS
|
||||
|
||||
def test_dyson_fan_mode_diffuse(self):
|
||||
"""Test fan diffuse mode."""
|
||||
device = _get_device_diffuse()
|
||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||
assert entity.current_fan_mode == dyson.STATE_DIFFUSE
|
||||
assert entity.fan_mode == dyson.FAN_DIFFUSE
|
||||
|
||||
def test_dyson_set_operation_mode(self):
|
||||
def test_dyson_set_hvac_mode(self):
|
||||
"""Test set operation mode."""
|
||||
device = _get_device_heat_on()
|
||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||
assert not entity.should_poll
|
||||
|
||||
entity.set_operation_mode(dyson.STATE_HEAT)
|
||||
entity.set_hvac_mode(dyson.HVAC_MODE_HEAT)
|
||||
set_config = device.set_configuration
|
||||
set_config.assert_called_with(heat_mode=HeatMode.HEAT_ON)
|
||||
|
||||
entity.set_operation_mode(dyson.STATE_COOL)
|
||||
entity.set_hvac_mode(dyson.HVAC_MODE_COOL)
|
||||
set_config = device.set_configuration
|
||||
set_config.assert_called_with(heat_mode=HeatMode.HEAT_OFF)
|
||||
|
||||
|
@ -276,15 +276,15 @@ class DysonTest(unittest.TestCase):
|
|||
"""Test get operation list."""
|
||||
device = _get_device_heat_on()
|
||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||
assert len(entity.operation_list) == 2
|
||||
assert dyson.STATE_HEAT in entity.operation_list
|
||||
assert dyson.STATE_COOL in entity.operation_list
|
||||
assert len(entity.hvac_modes) == 2
|
||||
assert dyson.HVAC_MODE_HEAT in entity.hvac_modes
|
||||
assert dyson.HVAC_MODE_COOL in entity.hvac_modes
|
||||
|
||||
def test_dyson_heat_off(self):
|
||||
"""Test turn off heat."""
|
||||
device = _get_device_heat_off()
|
||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||
entity.set_operation_mode(dyson.STATE_COOL)
|
||||
entity.set_hvac_mode(dyson.HVAC_MODE_COOL)
|
||||
set_config = device.set_configuration
|
||||
set_config.assert_called_with(heat_mode=HeatMode.HEAT_OFF)
|
||||
|
||||
|
@ -292,7 +292,7 @@ class DysonTest(unittest.TestCase):
|
|||
"""Test turn on heat."""
|
||||
device = _get_device_heat_on()
|
||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||
entity.set_operation_mode(dyson.STATE_HEAT)
|
||||
entity.set_hvac_mode(dyson.HVAC_MODE_HEAT)
|
||||
set_config = device.set_configuration
|
||||
set_config.assert_called_with(heat_mode=HeatMode.HEAT_ON)
|
||||
|
||||
|
@ -300,19 +300,20 @@ class DysonTest(unittest.TestCase):
|
|||
"""Test get heat value on."""
|
||||
device = _get_device_heat_on()
|
||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||
assert entity.current_operation == dyson.STATE_HEAT
|
||||
assert entity.hvac_mode == dyson.HVAC_MODE_HEAT
|
||||
|
||||
def test_dyson_heat_value_off(self):
|
||||
"""Test get heat value off."""
|
||||
device = _get_device_cool()
|
||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||
assert entity.current_operation == dyson.STATE_COOL
|
||||
assert entity.hvac_mode == dyson.HVAC_MODE_COOL
|
||||
|
||||
def test_dyson_heat_value_idle(self):
|
||||
"""Test get heat value idle."""
|
||||
device = _get_device_heat_off()
|
||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||
assert entity.current_operation == dyson.STATE_IDLE
|
||||
assert entity.hvac_mode == dyson.HVAC_MODE_HEAT
|
||||
assert entity.hvac_action == dyson.CURRENT_HVAC_IDLE
|
||||
|
||||
def test_on_message(self):
|
||||
"""Test when message is received."""
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue