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:
@@ -18,6 +18,7 @@
|
|||||||
"python.linting.pylintEnabled": true,
|
"python.linting.pylintEnabled": true,
|
||||||
"python.linting.enabled": true,
|
"python.linting.enabled": true,
|
||||||
"files.trimTrailingWhitespace": 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
|
virtualization/vagrant/config
|
||||||
|
|
||||||
# Visual Studio Code
|
# Visual Studio Code
|
||||||
.vscode
|
.vscode/*
|
||||||
|
!.vscode/cSpell.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
|
||||||
# Built docs
|
# Built docs
|
||||||
docs/build
|
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 (
|
from .const import (
|
||||||
API_TEMP_UNITS,
|
API_TEMP_UNITS,
|
||||||
API_THERMOSTAT_MODES,
|
API_THERMOSTAT_MODES,
|
||||||
|
API_THERMOSTAT_PRESETS,
|
||||||
DATE_FORMAT,
|
DATE_FORMAT,
|
||||||
PERCENTAGE_FAN_MAP,
|
PERCENTAGE_FAN_MAP,
|
||||||
)
|
)
|
||||||
@@ -180,9 +181,13 @@ class AlexaPowerController(AlexaCapibility):
|
|||||||
if name != 'powerState':
|
if name != 'powerState':
|
||||||
raise UnsupportedProperty(name)
|
raise UnsupportedProperty(name)
|
||||||
|
|
||||||
if self.entity.state == STATE_OFF:
|
if self.entity.domain == climate.DOMAIN:
|
||||||
return 'OFF'
|
is_on = self.entity.state != climate.HVAC_MODE_OFF
|
||||||
return 'ON'
|
|
||||||
|
else:
|
||||||
|
is_on = self.entity.state != STATE_OFF
|
||||||
|
|
||||||
|
return 'ON' if is_on else 'OFF'
|
||||||
|
|
||||||
|
|
||||||
class AlexaLockController(AlexaCapibility):
|
class AlexaLockController(AlexaCapibility):
|
||||||
@@ -546,16 +551,13 @@ class AlexaThermostatController(AlexaCapibility):
|
|||||||
|
|
||||||
def properties_supported(self):
|
def properties_supported(self):
|
||||||
"""Return what properties this entity supports."""
|
"""Return what properties this entity supports."""
|
||||||
properties = []
|
properties = [{'name': 'thermostatMode'}]
|
||||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
if supported & climate.SUPPORT_TARGET_TEMPERATURE:
|
if supported & climate.SUPPORT_TARGET_TEMPERATURE:
|
||||||
properties.append({'name': 'targetSetpoint'})
|
properties.append({'name': 'targetSetpoint'})
|
||||||
if supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW:
|
if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE:
|
||||||
properties.append({'name': 'lowerSetpoint'})
|
properties.append({'name': 'lowerSetpoint'})
|
||||||
if supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH:
|
|
||||||
properties.append({'name': 'upperSetpoint'})
|
properties.append({'name': 'upperSetpoint'})
|
||||||
if supported & climate.SUPPORT_OPERATION_MODE:
|
|
||||||
properties.append({'name': 'thermostatMode'})
|
|
||||||
return properties
|
return properties
|
||||||
|
|
||||||
def properties_proactively_reported(self):
|
def properties_proactively_reported(self):
|
||||||
@@ -569,13 +571,18 @@ class AlexaThermostatController(AlexaCapibility):
|
|||||||
def get_property(self, name):
|
def get_property(self, name):
|
||||||
"""Read and return a property."""
|
"""Read and return a property."""
|
||||||
if name == 'thermostatMode':
|
if name == 'thermostatMode':
|
||||||
ha_mode = self.entity.attributes.get(climate.ATTR_OPERATION_MODE)
|
preset = self.entity.attributes.get(climate.ATTR_PRESET_MODE)
|
||||||
mode = API_THERMOSTAT_MODES.get(ha_mode)
|
|
||||||
if mode is None:
|
if preset in API_THERMOSTAT_PRESETS:
|
||||||
_LOGGER.error("%s (%s) has unsupported %s value '%s'",
|
mode = API_THERMOSTAT_PRESETS[preset]
|
||||||
self.entity.entity_id, type(self.entity),
|
else:
|
||||||
climate.ATTR_OPERATION_MODE, ha_mode)
|
mode = API_THERMOSTAT_MODES.get(self.entity.state)
|
||||||
raise UnsupportedProperty(name)
|
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
|
return mode
|
||||||
|
|
||||||
unit = self.hass.config.units.temperature_unit
|
unit = self.hass.config.units.temperature_unit
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_OFF,
|
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
TEMP_FAHRENHEIT,
|
TEMP_FAHRENHEIT,
|
||||||
)
|
)
|
||||||
@@ -57,16 +56,17 @@ API_TEMP_UNITS = {
|
|||||||
# reverse mapping of this dict and we want to map the first occurrance of OFF
|
# reverse mapping of this dict and we want to map the first occurrance of OFF
|
||||||
# back to HA state.
|
# back to HA state.
|
||||||
API_THERMOSTAT_MODES = OrderedDict([
|
API_THERMOSTAT_MODES = OrderedDict([
|
||||||
(climate.STATE_HEAT, 'HEAT'),
|
(climate.HVAC_MODE_HEAT, 'HEAT'),
|
||||||
(climate.STATE_COOL, 'COOL'),
|
(climate.HVAC_MODE_COOL, 'COOL'),
|
||||||
(climate.STATE_AUTO, 'AUTO'),
|
(climate.HVAC_MODE_HEAT_COOL, 'AUTO'),
|
||||||
(climate.STATE_ECO, 'ECO'),
|
(climate.HVAC_MODE_AUTO, 'AUTO'),
|
||||||
(climate.STATE_MANUAL, 'AUTO'),
|
(climate.HVAC_MODE_OFF, 'OFF'),
|
||||||
(STATE_OFF, 'OFF'),
|
(climate.HVAC_MODE_FAN_ONLY, 'OFF'),
|
||||||
(climate.STATE_IDLE, 'OFF'),
|
(climate.HVAC_MODE_DRY, 'OFF'),
|
||||||
(climate.STATE_FAN_ONLY, 'OFF'),
|
|
||||||
(climate.STATE_DRY, 'OFF'),
|
|
||||||
])
|
])
|
||||||
|
API_THERMOSTAT_PRESETS = {
|
||||||
|
climate.PRESET_ECO: 'ECO'
|
||||||
|
}
|
||||||
|
|
||||||
PERCENTAGE_FAN_MAP = {
|
PERCENTAGE_FAN_MAP = {
|
||||||
fan.SPEED_LOW: 33,
|
fan.SPEED_LOW: 33,
|
||||||
|
|||||||
@@ -248,9 +248,11 @@ class ClimateCapabilities(AlexaEntity):
|
|||||||
|
|
||||||
def interfaces(self):
|
def interfaces(self):
|
||||||
"""Yield the supported interfaces."""
|
"""Yield the supported interfaces."""
|
||||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
# If we support two modes, one being off, we allow turning on too.
|
||||||
if supported & climate.SUPPORT_ON_OFF:
|
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 AlexaPowerController(self.entity)
|
||||||
|
|
||||||
yield AlexaThermostatController(self.hass, self.entity)
|
yield AlexaThermostatController(self.hass, self.entity)
|
||||||
yield AlexaTemperatureSensor(self.hass, self.entity)
|
yield AlexaTemperatureSensor(self.hass, self.entity)
|
||||||
yield AlexaEndpointHealth(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 (
|
from .const import (
|
||||||
API_TEMP_UNITS,
|
API_TEMP_UNITS,
|
||||||
API_THERMOSTAT_MODES,
|
API_THERMOSTAT_MODES,
|
||||||
|
API_THERMOSTAT_PRESETS,
|
||||||
Cause,
|
Cause,
|
||||||
)
|
)
|
||||||
from .entities import async_get_entities
|
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 = directive.payload['thermostatMode']
|
||||||
mode = mode if isinstance(mode, str) else mode['value']
|
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 = {
|
data = {
|
||||||
ATTR_ENTITY_ID: entity.entity_id,
|
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()
|
response = directive.response()
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
entity.domain, climate.SERVICE_SET_OPERATION_MODE, data,
|
climate.DOMAIN, service, data,
|
||||||
blocking=False, context=context)
|
blocking=False, context=context)
|
||||||
response.add_context_property({
|
response.add_context_property({
|
||||||
'name': 'thermostatMode',
|
'name': 'thermostatMode',
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, HVAC_MODE_HEAT)
|
||||||
SUPPORT_ON_OFF, STATE_HEAT)
|
from homeassistant.const import ATTR_NAME, ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
from homeassistant.const import ATTR_NAME
|
|
||||||
from homeassistant.const import (ATTR_TEMPERATURE,
|
|
||||||
STATE_OFF, TEMP_CELSIUS)
|
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from .const import (ATTR_VALUE, CONF_CLIENT_ID, CONF_CLIENT_SECRET,
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
|
||||||
SUPPORT_ON_OFF)
|
|
||||||
|
|
||||||
SEND_COMFORT_FEEDBACK_SCHEMA = vol.Schema({
|
SEND_COMFORT_FEEDBACK_SCHEMA = vol.Schema({
|
||||||
vol.Required(ATTR_NAME): cv.string,
|
vol.Required(ATTR_NAME): cv.string,
|
||||||
@@ -177,11 +173,6 @@ class AmbiclimateEntity(ClimateDevice):
|
|||||||
"""Return the current humidity."""
|
"""Return the current humidity."""
|
||||||
return self._data.get('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
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
"""Return the minimum temperature."""
|
"""Return the minimum temperature."""
|
||||||
@@ -198,9 +189,12 @@ class AmbiclimateEntity(ClimateDevice):
|
|||||||
return SUPPORT_FLAGS
|
return SUPPORT_FLAGS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation."""
|
"""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):
|
async def async_set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
@@ -209,13 +203,13 @@ class AmbiclimateEntity(ClimateDevice):
|
|||||||
return
|
return
|
||||||
await self._heater.set_target_temperature(temperature)
|
await self._heater.set_target_temperature(temperature)
|
||||||
|
|
||||||
async def async_turn_on(self):
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
"""Turn device on."""
|
"""Set new target hvac mode."""
|
||||||
await self._heater.turn_on()
|
if hvac_mode == HVAC_MODE_HEAT:
|
||||||
|
await self._heater.turn_on()
|
||||||
async def async_turn_off(self):
|
return
|
||||||
"""Turn device off."""
|
if hvac_mode == HVAC_MODE_OFF:
|
||||||
await self._heater.turn_off()
|
await self._heater.turn_off()
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Retrieve latest state."""
|
"""Retrieve latest state."""
|
||||||
|
|||||||
@@ -1,68 +1,41 @@
|
|||||||
"""Provides functionality to interact with climate devices."""
|
"""Provides functionality to interact with climate devices."""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
|
||||||
import functools as ft
|
import functools as ft
|
||||||
|
import logging
|
||||||
|
from typing import Any, Awaitable, Dict, List, Optional
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.helpers.temperature import display_temp as show_temp
|
from homeassistant.const import (
|
||||||
from homeassistant.util.temperature import convert as convert_temperature
|
ATTR_ENTITY_ID, ATTR_TEMPERATURE, PRECISION_TENTHS, PRECISION_WHOLE,
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
STATE_OFF, STATE_ON, TEMP_CELSIUS)
|
||||||
from homeassistant.helpers.entity import Entity
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.config_validation import ( # noqa
|
from homeassistant.helpers.config_validation import ( # noqa
|
||||||
PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE)
|
PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE)
|
||||||
import homeassistant.helpers.config_validation as cv
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.const import (
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
from homeassistant.helpers.temperature import display_temp as show_temp
|
||||||
STATE_ON, STATE_OFF, TEMP_CELSIUS, PRECISION_WHOLE,
|
from homeassistant.helpers.typing import (
|
||||||
PRECISION_TENTHS)
|
ConfigType, HomeAssistantType, ServiceDataType)
|
||||||
|
from homeassistant.util.temperature import convert as convert_temperature
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_AUX_HEAT,
|
ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE,
|
||||||
ATTR_AWAY_MODE,
|
ATTR_FAN_MODE, ATTR_FAN_MODES, ATTR_HUMIDITY, ATTR_HVAC_ACTIONS,
|
||||||
ATTR_CURRENT_HUMIDITY,
|
ATTR_HVAC_MODE, ATTR_HVAC_MODES, ATTR_MAX_HUMIDITY, ATTR_MAX_TEMP,
|
||||||
ATTR_CURRENT_TEMPERATURE,
|
ATTR_MIN_HUMIDITY, ATTR_MIN_TEMP, ATTR_PRESET_MODE, ATTR_PRESET_MODES,
|
||||||
ATTR_FAN_LIST,
|
ATTR_SWING_MODE, ATTR_SWING_MODES, ATTR_TARGET_TEMP_HIGH,
|
||||||
ATTR_FAN_MODE,
|
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_STEP, DOMAIN, HVAC_MODES,
|
||||||
ATTR_HOLD_MODE,
|
SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY,
|
||||||
ATTR_HUMIDITY,
|
SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_SWING_MODE,
|
||||||
ATTR_MAX_HUMIDITY,
|
SERVICE_SET_TEMPERATURE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE,
|
||||||
ATTR_MAX_TEMP,
|
SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_HUMIDITY,
|
||||||
ATTR_MIN_HUMIDITY,
|
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||||
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,
|
|
||||||
)
|
|
||||||
from .reproduce_state import async_reproduce_states # noqa
|
from .reproduce_state import async_reproduce_states # noqa
|
||||||
|
|
||||||
DEFAULT_MIN_TEMP = 7
|
DEFAULT_MIN_TEMP = 7
|
||||||
DEFAULT_MAX_TEMP = 35
|
DEFAULT_MAX_TEMP = 35
|
||||||
DEFAULT_MIN_HUMITIDY = 30
|
DEFAULT_MIN_HUMIDITY = 30
|
||||||
DEFAULT_MAX_HUMIDITY = 99
|
DEFAULT_MAX_HUMIDITY = 99
|
||||||
|
|
||||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
@@ -76,14 +49,6 @@ CONVERTIBLE_ATTRIBUTE = [
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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({
|
SET_AUX_HEAT_SCHEMA = vol.Schema({
|
||||||
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
||||||
vol.Required(ATTR_AUX_HEAT): cv.boolean,
|
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_HIGH, 'temperature'): vol.Coerce(float),
|
||||||
vol.Inclusive(ATTR_TARGET_TEMP_LOW, '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_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({
|
SET_FAN_MODE_SCHEMA = vol.Schema({
|
||||||
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
||||||
vol.Required(ATTR_FAN_MODE): cv.string,
|
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.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.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({
|
SET_HUMIDITY_SCHEMA = vol.Schema({
|
||||||
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
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."""
|
"""Set up climate devices."""
|
||||||
component = hass.data[DOMAIN] = \
|
component = hass.data[DOMAIN] = \
|
||||||
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
||||||
await component.async_setup(config)
|
await component.async_setup(config)
|
||||||
|
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_SET_AWAY_MODE, SET_AWAY_MODE_SCHEMA,
|
SERVICE_SET_HVAC_MODE, SET_HVAC_MODE_SCHEMA,
|
||||||
async_service_away_mode
|
'async_set_hvac_mode'
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_SET_HOLD_MODE, SET_HOLD_MODE_SCHEMA,
|
SERVICE_SET_PRESET_MODE, SET_PRESET_MODE_SCHEMA,
|
||||||
'async_set_hold_mode'
|
'async_set_preset_mode'
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_SET_AUX_HEAT, SET_AUX_HEAT_SCHEMA,
|
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,
|
SERVICE_SET_FAN_MODE, SET_FAN_MODE_SCHEMA,
|
||||||
'async_set_fan_mode'
|
'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(
|
component.async_register_entity_service(
|
||||||
SERVICE_SET_SWING_MODE, SET_SWING_MODE_SCHEMA,
|
SERVICE_SET_SWING_MODE, SET_SWING_MODE_SCHEMA,
|
||||||
'async_set_swing_mode'
|
'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
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, entry):
|
async def async_setup_entry(hass: HomeAssistantType, entry):
|
||||||
"""Set up a config entry."""
|
"""Set up a config entry."""
|
||||||
return await hass.data[DOMAIN].async_setup_entry(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."""
|
"""Unload a config entry."""
|
||||||
return await hass.data[DOMAIN].async_unload_entry(entry)
|
return await hass.data[DOMAIN].async_unload_entry(entry)
|
||||||
|
|
||||||
@@ -185,27 +138,23 @@ class ClimateDevice(Entity):
|
|||||||
"""Representation of a climate device."""
|
"""Representation of a climate device."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self) -> str:
|
||||||
"""Return the current state."""
|
"""Return the current state."""
|
||||||
if self.is_on is False:
|
return self.hvac_mode
|
||||||
return STATE_OFF
|
|
||||||
if self.current_operation:
|
|
||||||
return self.current_operation
|
|
||||||
if self.is_on:
|
|
||||||
return STATE_ON
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def precision(self):
|
def precision(self) -> float:
|
||||||
"""Return the precision of the system."""
|
"""Return the precision of the system."""
|
||||||
if self.hass.config.units.temperature_unit == TEMP_CELSIUS:
|
if self.hass.config.units.temperature_unit == TEMP_CELSIUS:
|
||||||
return PRECISION_TENTHS
|
return PRECISION_TENTHS
|
||||||
return PRECISION_WHOLE
|
return PRECISION_WHOLE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self) -> Dict[str, Any]:
|
||||||
"""Return the optional state attributes."""
|
"""Return the optional state attributes."""
|
||||||
|
supported_features = self.supported_features
|
||||||
data = {
|
data = {
|
||||||
|
ATTR_HVAC_MODES: self.hvac_modes,
|
||||||
ATTR_CURRENT_TEMPERATURE: show_temp(
|
ATTR_CURRENT_TEMPERATURE: show_temp(
|
||||||
self.hass, self.current_temperature, self.temperature_unit,
|
self.hass, self.current_temperature, self.temperature_unit,
|
||||||
self.precision),
|
self.precision),
|
||||||
@@ -220,16 +169,13 @@ class ClimateDevice(Entity):
|
|||||||
self.precision),
|
self.precision),
|
||||||
}
|
}
|
||||||
|
|
||||||
supported_features = self.supported_features
|
if self.target_temperature_step:
|
||||||
if self.target_temperature_step is not None:
|
|
||||||
data[ATTR_TARGET_TEMP_STEP] = 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(
|
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
|
||||||
self.hass, self.target_temperature_high, self.temperature_unit,
|
self.hass, self.target_temperature_high, self.temperature_unit,
|
||||||
self.precision)
|
self.precision)
|
||||||
|
|
||||||
if supported_features & SUPPORT_TARGET_TEMPERATURE_LOW:
|
|
||||||
data[ATTR_TARGET_TEMP_LOW] = show_temp(
|
data[ATTR_TARGET_TEMP_LOW] = show_temp(
|
||||||
self.hass, self.target_temperature_low, self.temperature_unit,
|
self.hass, self.target_temperature_low, self.temperature_unit,
|
||||||
self.precision)
|
self.precision)
|
||||||
@@ -239,136 +185,160 @@ class ClimateDevice(Entity):
|
|||||||
|
|
||||||
if supported_features & SUPPORT_TARGET_HUMIDITY:
|
if supported_features & SUPPORT_TARGET_HUMIDITY:
|
||||||
data[ATTR_HUMIDITY] = self.target_humidity
|
data[ATTR_HUMIDITY] = self.target_humidity
|
||||||
|
data[ATTR_MIN_HUMIDITY] = self.min_humidity
|
||||||
if supported_features & SUPPORT_TARGET_HUMIDITY_LOW:
|
data[ATTR_MAX_HUMIDITY] = self.max_humidity
|
||||||
data[ATTR_MIN_HUMIDITY] = self.min_humidity
|
|
||||||
|
|
||||||
if supported_features & SUPPORT_TARGET_HUMIDITY_HIGH:
|
|
||||||
data[ATTR_MAX_HUMIDITY] = self.max_humidity
|
|
||||||
|
|
||||||
if supported_features & SUPPORT_FAN_MODE:
|
if supported_features & SUPPORT_FAN_MODE:
|
||||||
data[ATTR_FAN_MODE] = self.current_fan_mode
|
data[ATTR_FAN_MODE] = self.fan_mode
|
||||||
if self.fan_list:
|
data[ATTR_FAN_MODES] = self.fan_modes
|
||||||
data[ATTR_FAN_LIST] = self.fan_list
|
|
||||||
|
|
||||||
if supported_features & SUPPORT_OPERATION_MODE:
|
if self.hvac_action:
|
||||||
data[ATTR_OPERATION_MODE] = self.current_operation
|
data[ATTR_HVAC_ACTIONS] = self.hvac_action
|
||||||
if self.operation_list:
|
|
||||||
data[ATTR_OPERATION_LIST] = self.operation_list
|
|
||||||
|
|
||||||
if supported_features & SUPPORT_HOLD_MODE:
|
if supported_features & SUPPORT_PRESET_MODE:
|
||||||
data[ATTR_HOLD_MODE] = self.current_hold_mode
|
data[ATTR_PRESET_MODE] = self.preset_mode
|
||||||
|
data[ATTR_PRESET_MODES] = self.preset_modes
|
||||||
|
|
||||||
if supported_features & SUPPORT_SWING_MODE:
|
if supported_features & SUPPORT_SWING_MODE:
|
||||||
data[ATTR_SWING_MODE] = self.current_swing_mode
|
data[ATTR_SWING_MODE] = self.swing_mode
|
||||||
if self.swing_list:
|
data[ATTR_SWING_MODES] = self.swing_modes
|
||||||
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
|
|
||||||
|
|
||||||
if supported_features & SUPPORT_AUX_HEAT:
|
if supported_features & SUPPORT_AUX_HEAT:
|
||||||
is_aux_heat = self.is_aux_heat_on
|
data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF
|
||||||
data[ATTR_AUX_HEAT] = STATE_ON if is_aux_heat else STATE_OFF
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self) -> str:
|
||||||
"""Return the unit of measurement used by the platform."""
|
"""Return the unit of measurement used by the platform."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_humidity(self):
|
def current_humidity(self) -> Optional[int]:
|
||||||
"""Return the current humidity."""
|
"""Return the current humidity."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_humidity(self):
|
def target_humidity(self) -> Optional[int]:
|
||||||
"""Return the humidity we try to reach."""
|
"""Return the humidity we try to reach."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self) -> str:
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""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
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def current_temperature(self) -> Optional[float]:
|
||||||
"""Return the list of available operation modes."""
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_temperature(self):
|
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self) -> Optional[float]:
|
||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_step(self):
|
def target_temperature_step(self) -> Optional[float]:
|
||||||
"""Return the supported step of target temperature."""
|
"""Return the supported step of target temperature."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_high(self):
|
def target_temperature_high(self) -> Optional[float]:
|
||||||
"""Return the highbound target temperature we try to reach."""
|
"""Return the highbound target temperature we try to reach.
|
||||||
return None
|
|
||||||
|
Requires SUPPORT_TARGET_TEMPERATURE_RANGE.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_low(self):
|
def target_temperature_low(self) -> Optional[float]:
|
||||||
"""Return the lowbound target temperature we try to reach."""
|
"""Return the lowbound target temperature we try to reach.
|
||||||
return None
|
|
||||||
|
Requires SUPPORT_TARGET_TEMPERATURE_RANGE.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_away_mode_on(self):
|
def preset_mode(self) -> Optional[str]:
|
||||||
"""Return true if away mode is on."""
|
"""Return the current preset mode, e.g., home, away, temp.
|
||||||
return None
|
|
||||||
|
Requires SUPPORT_PRESET_MODE.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_hold_mode(self):
|
def preset_modes(self) -> Optional[List[str]]:
|
||||||
"""Return the current hold mode, e.g., home, away, temp."""
|
"""Return a list of available preset modes.
|
||||||
return None
|
|
||||||
|
Requires SUPPORT_PRESET_MODE.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_aux_heat(self) -> Optional[str]:
|
||||||
"""Return true if on."""
|
"""Return true if aux heater.
|
||||||
return None
|
|
||||||
|
Requires SUPPORT_AUX_HEAT.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_aux_heat_on(self):
|
def fan_mode(self) -> Optional[str]:
|
||||||
"""Return true if aux heater."""
|
"""Return the fan setting.
|
||||||
return None
|
|
||||||
|
Requires SUPPORT_FAN_MODE.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_modes(self) -> Optional[List[str]]:
|
||||||
"""Return the fan setting."""
|
"""Return the list of available fan modes.
|
||||||
return None
|
|
||||||
|
Requires SUPPORT_FAN_MODE.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def swing_mode(self) -> Optional[str]:
|
||||||
"""Return the list of available fan modes."""
|
"""Return the swing setting.
|
||||||
return None
|
|
||||||
|
Requires SUPPORT_SWING_MODE.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_swing_mode(self):
|
def swing_modes(self) -> Optional[List[str]]:
|
||||||
"""Return the fan setting."""
|
"""Return the list of available swing modes.
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
Requires SUPPORT_SWING_MODE.
|
||||||
def swing_list(self):
|
"""
|
||||||
"""Return the list of available swing modes."""
|
raise NotImplementedError
|
||||||
return None
|
|
||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def async_set_temperature(self, **kwargs):
|
def async_set_temperature(self, **kwargs) -> Awaitable[None]:
|
||||||
"""Set new target temperature.
|
"""Set new target temperature.
|
||||||
|
|
||||||
This method must be run in the event loop and returns a coroutine.
|
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(
|
return self.hass.async_add_job(
|
||||||
ft.partial(self.set_temperature, **kwargs))
|
ft.partial(self.set_temperature, **kwargs))
|
||||||
|
|
||||||
def set_humidity(self, humidity):
|
def set_humidity(self, humidity: int) -> None:
|
||||||
"""Set new target humidity."""
|
"""Set new target humidity."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def async_set_humidity(self, humidity):
|
def async_set_humidity(self, humidity: int) -> Awaitable[None]:
|
||||||
"""Set new target humidity.
|
"""Set new target humidity.
|
||||||
|
|
||||||
This method must be run in the event loop and returns a coroutine.
|
This method must be run in the event loop and returns a coroutine.
|
||||||
"""
|
"""
|
||||||
return self.hass.async_add_job(self.set_humidity, humidity)
|
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."""
|
"""Set new target fan mode."""
|
||||||
raise NotImplementedError()
|
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.
|
"""Set new target fan mode.
|
||||||
|
|
||||||
This method must be run in the event loop and returns a coroutine.
|
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)
|
return self.hass.async_add_job(self.set_fan_mode, fan_mode)
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode: str) -> None:
|
||||||
"""Set new target operation mode."""
|
"""Set new target hvac mode."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def async_set_operation_mode(self, operation_mode):
|
def async_set_hvac_mode(self, hvac_mode: str) -> Awaitable[None]:
|
||||||
"""Set new target operation mode.
|
"""Set new target hvac mode.
|
||||||
|
|
||||||
This method must be run in the event loop and returns a coroutine.
|
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."""
|
"""Set new target swing operation."""
|
||||||
raise NotImplementedError()
|
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.
|
"""Set new target swing operation.
|
||||||
|
|
||||||
This method must be run in the event loop and returns a coroutine.
|
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)
|
return self.hass.async_add_job(self.set_swing_mode, swing_mode)
|
||||||
|
|
||||||
def turn_away_mode_on(self):
|
def set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Turn away mode on."""
|
"""Set new preset mode."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def async_turn_away_mode_on(self):
|
def async_set_preset_mode(self, preset_mode: str) -> Awaitable[None]:
|
||||||
"""Turn away mode on.
|
"""Set new preset mode.
|
||||||
|
|
||||||
This method must be run in the event loop and returns a coroutine.
|
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):
|
def turn_aux_heat_on(self) -> None:
|
||||||
"""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):
|
|
||||||
"""Turn auxiliary heater on."""
|
"""Turn auxiliary heater on."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def async_turn_aux_heat_on(self):
|
def async_turn_aux_heat_on(self) -> Awaitable[None]:
|
||||||
"""Turn auxiliary heater on.
|
"""Turn auxiliary heater on.
|
||||||
|
|
||||||
This method must be run in the event loop and returns a coroutine.
|
This method must be run in the event loop and returns a coroutine.
|
||||||
"""
|
"""
|
||||||
return self.hass.async_add_job(self.turn_aux_heat_on)
|
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."""
|
"""Turn auxiliary heater off."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def async_turn_aux_heat_off(self):
|
def async_turn_aux_heat_off(self) -> Awaitable[None]:
|
||||||
"""Turn auxiliary heater off.
|
"""Turn auxiliary heater off.
|
||||||
|
|
||||||
This method must be run in the event loop and returns a coroutine.
|
This method must be run in the event loop and returns a coroutine.
|
||||||
"""
|
"""
|
||||||
return self.hass.async_add_job(self.turn_aux_heat_off)
|
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
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self) -> int:
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self) -> float:
|
||||||
"""Return the minimum temperature."""
|
"""Return the minimum temperature."""
|
||||||
return convert_temperature(DEFAULT_MIN_TEMP, TEMP_CELSIUS,
|
return convert_temperature(DEFAULT_MIN_TEMP, TEMP_CELSIUS,
|
||||||
self.temperature_unit)
|
self.temperature_unit)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self):
|
def max_temp(self) -> float:
|
||||||
"""Return the maximum temperature."""
|
"""Return the maximum temperature."""
|
||||||
return convert_temperature(DEFAULT_MAX_TEMP, TEMP_CELSIUS,
|
return convert_temperature(DEFAULT_MAX_TEMP, TEMP_CELSIUS,
|
||||||
self.temperature_unit)
|
self.temperature_unit)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_humidity(self):
|
def min_humidity(self) -> int:
|
||||||
"""Return the minimum humidity."""
|
"""Return the minimum humidity."""
|
||||||
return DEFAULT_MIN_HUMITIDY
|
return DEFAULT_MIN_HUMIDITY
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_humidity(self):
|
def max_humidity(self) -> int:
|
||||||
"""Return the maximum humidity."""
|
"""Return the maximum humidity."""
|
||||||
return DEFAULT_MAX_HUMIDITY
|
return DEFAULT_MAX_HUMIDITY
|
||||||
|
|
||||||
|
|
||||||
async def async_service_away_mode(entity, service):
|
async def async_service_aux_heat(
|
||||||
"""Handle away mode service."""
|
entity: ClimateDevice, service: ServiceDataType
|
||||||
if service.data[ATTR_AWAY_MODE]:
|
) -> None:
|
||||||
await entity.async_turn_away_mode_on()
|
|
||||||
else:
|
|
||||||
await entity.async_turn_away_mode_off()
|
|
||||||
|
|
||||||
|
|
||||||
async def async_service_aux_heat(entity, service):
|
|
||||||
"""Handle aux heat service."""
|
"""Handle aux heat service."""
|
||||||
if service.data[ATTR_AUX_HEAT]:
|
if service.data[ATTR_AUX_HEAT]:
|
||||||
await entity.async_turn_aux_heat_on()
|
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()
|
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."""
|
"""Handle set temperature service."""
|
||||||
hass = entity.hass
|
hass = entity.hass
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|||||||
@@ -1,20 +1,103 @@
|
|||||||
"""Provides the constants needed for component."""
|
"""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_AUX_HEAT = 'aux_heat'
|
||||||
ATTR_AWAY_MODE = 'away_mode'
|
|
||||||
ATTR_CURRENT_HUMIDITY = 'current_humidity'
|
ATTR_CURRENT_HUMIDITY = 'current_humidity'
|
||||||
ATTR_CURRENT_TEMPERATURE = 'current_temperature'
|
ATTR_CURRENT_TEMPERATURE = 'current_temperature'
|
||||||
ATTR_FAN_LIST = 'fan_list'
|
ATTR_FAN_MODES = 'fan_modes'
|
||||||
ATTR_FAN_MODE = 'fan_mode'
|
ATTR_FAN_MODE = 'fan_mode'
|
||||||
ATTR_HOLD_MODE = 'hold_mode'
|
ATTR_PRESET_MODE = 'preset_mode'
|
||||||
|
ATTR_PRESET_MODES = 'preset_modes'
|
||||||
ATTR_HUMIDITY = 'humidity'
|
ATTR_HUMIDITY = 'humidity'
|
||||||
ATTR_MAX_HUMIDITY = 'max_humidity'
|
ATTR_MAX_HUMIDITY = 'max_humidity'
|
||||||
ATTR_MAX_TEMP = 'max_temp'
|
|
||||||
ATTR_MIN_HUMIDITY = 'min_humidity'
|
ATTR_MIN_HUMIDITY = 'min_humidity'
|
||||||
|
ATTR_MAX_TEMP = 'max_temp'
|
||||||
ATTR_MIN_TEMP = 'min_temp'
|
ATTR_MIN_TEMP = 'min_temp'
|
||||||
ATTR_OPERATION_LIST = 'operation_list'
|
ATTR_HVAC_ACTIONS = 'hvac_action'
|
||||||
ATTR_OPERATION_MODE = 'operation_mode'
|
ATTR_HVAC_MODES = 'hvac_modes'
|
||||||
ATTR_SWING_LIST = 'swing_list'
|
ATTR_HVAC_MODE = 'hvac_mode'
|
||||||
|
ATTR_SWING_MODES = 'swing_modes'
|
||||||
ATTR_SWING_MODE = 'swing_mode'
|
ATTR_SWING_MODE = 'swing_mode'
|
||||||
ATTR_TARGET_TEMP_HIGH = 'target_temp_high'
|
ATTR_TARGET_TEMP_HIGH = 'target_temp_high'
|
||||||
ATTR_TARGET_TEMP_LOW = 'target_temp_low'
|
ATTR_TARGET_TEMP_LOW = 'target_temp_low'
|
||||||
@@ -28,33 +111,17 @@ DEFAULT_MAX_HUMIDITY = 99
|
|||||||
DOMAIN = 'climate'
|
DOMAIN = 'climate'
|
||||||
|
|
||||||
SERVICE_SET_AUX_HEAT = 'set_aux_heat'
|
SERVICE_SET_AUX_HEAT = 'set_aux_heat'
|
||||||
SERVICE_SET_AWAY_MODE = 'set_away_mode'
|
|
||||||
SERVICE_SET_FAN_MODE = 'set_fan_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_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_SWING_MODE = 'set_swing_mode'
|
||||||
SERVICE_SET_TEMPERATURE = 'set_temperature'
|
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 = 1
|
||||||
SUPPORT_TARGET_TEMPERATURE_HIGH = 2
|
SUPPORT_TARGET_TEMPERATURE_RANGE = 2
|
||||||
SUPPORT_TARGET_TEMPERATURE_LOW = 4
|
SUPPORT_TARGET_HUMIDITY = 4
|
||||||
SUPPORT_TARGET_HUMIDITY = 8
|
SUPPORT_FAN_MODE = 8
|
||||||
SUPPORT_TARGET_HUMIDITY_HIGH = 16
|
SUPPORT_PRESET_MODE = 16
|
||||||
SUPPORT_TARGET_HUMIDITY_LOW = 32
|
SUPPORT_SWING_MODE = 32
|
||||||
SUPPORT_FAN_MODE = 64
|
SUPPORT_AUX_HEAT = 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
|
|
||||||
|
|||||||
@@ -2,27 +2,24 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Iterable, Optional
|
from typing import Iterable, Optional
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_TEMPERATURE
|
||||||
ATTR_TEMPERATURE, SERVICE_TURN_OFF,
|
|
||||||
SERVICE_TURN_ON, STATE_OFF, STATE_ON)
|
|
||||||
from homeassistant.core import Context, State
|
from homeassistant.core import Context, State
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_AUX_HEAT,
|
ATTR_AUX_HEAT,
|
||||||
ATTR_AWAY_MODE,
|
|
||||||
ATTR_TARGET_TEMP_HIGH,
|
ATTR_TARGET_TEMP_HIGH,
|
||||||
ATTR_TARGET_TEMP_LOW,
|
ATTR_TARGET_TEMP_LOW,
|
||||||
ATTR_HOLD_MODE,
|
ATTR_PRESET_MODE,
|
||||||
ATTR_OPERATION_MODE,
|
ATTR_HVAC_MODE,
|
||||||
ATTR_SWING_MODE,
|
ATTR_SWING_MODE,
|
||||||
ATTR_HUMIDITY,
|
ATTR_HUMIDITY,
|
||||||
SERVICE_SET_AWAY_MODE,
|
HVAC_MODES,
|
||||||
SERVICE_SET_AUX_HEAT,
|
SERVICE_SET_AUX_HEAT,
|
||||||
SERVICE_SET_TEMPERATURE,
|
SERVICE_SET_TEMPERATURE,
|
||||||
SERVICE_SET_HOLD_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
SERVICE_SET_OPERATION_MODE,
|
SERVICE_SET_HVAC_MODE,
|
||||||
SERVICE_SET_SWING_MODE,
|
SERVICE_SET_SWING_MODE,
|
||||||
SERVICE_SET_HUMIDITY,
|
SERVICE_SET_HUMIDITY,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@@ -33,9 +30,9 @@ async def _async_reproduce_states(hass: HomeAssistantType,
|
|||||||
state: State,
|
state: State,
|
||||||
context: Optional[Context] = None) -> None:
|
context: Optional[Context] = None) -> None:
|
||||||
"""Reproduce component states."""
|
"""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."""
|
"""Call service with set of attributes given."""
|
||||||
data = {}
|
data = data or {}
|
||||||
data['entity_id'] = state.entity_id
|
data['entity_id'] = state.entity_id
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if key in state.attributes:
|
if key in state.attributes:
|
||||||
@@ -45,17 +42,13 @@ async def _async_reproduce_states(hass: HomeAssistantType,
|
|||||||
DOMAIN, service, data,
|
DOMAIN, service, data,
|
||||||
blocking=True, context=context)
|
blocking=True, context=context)
|
||||||
|
|
||||||
if state.state == STATE_ON:
|
if state.state in HVAC_MODES:
|
||||||
await call_service(SERVICE_TURN_ON, [])
|
await call_service(
|
||||||
elif state.state == STATE_OFF:
|
SERVICE_SET_HVAC_MODE, [], {ATTR_HVAC_MODE: state.state})
|
||||||
await call_service(SERVICE_TURN_OFF, [])
|
|
||||||
|
|
||||||
if ATTR_AUX_HEAT in state.attributes:
|
if ATTR_AUX_HEAT in state.attributes:
|
||||||
await call_service(SERVICE_SET_AUX_HEAT, [ATTR_AUX_HEAT])
|
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 \
|
if (ATTR_TEMPERATURE in state.attributes) or \
|
||||||
(ATTR_TARGET_TEMP_HIGH in state.attributes) or \
|
(ATTR_TARGET_TEMP_HIGH in state.attributes) or \
|
||||||
(ATTR_TARGET_TEMP_LOW in state.attributes):
|
(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_HIGH,
|
||||||
ATTR_TARGET_TEMP_LOW])
|
ATTR_TARGET_TEMP_LOW])
|
||||||
|
|
||||||
if ATTR_HOLD_MODE in state.attributes:
|
if ATTR_PRESET_MODE in state.attributes:
|
||||||
await call_service(SERVICE_SET_HOLD_MODE,
|
await call_service(SERVICE_SET_PRESET_MODE, [ATTR_PRESET_MODE])
|
||||||
[ATTR_HOLD_MODE])
|
|
||||||
|
|
||||||
if ATTR_OPERATION_MODE in state.attributes:
|
|
||||||
await call_service(SERVICE_SET_OPERATION_MODE,
|
|
||||||
[ATTR_OPERATION_MODE])
|
|
||||||
|
|
||||||
if ATTR_SWING_MODE in state.attributes:
|
if ATTR_SWING_MODE in state.attributes:
|
||||||
await call_service(SERVICE_SET_SWING_MODE,
|
await call_service(SERVICE_SET_SWING_MODE, [ATTR_SWING_MODE])
|
||||||
[ATTR_SWING_MODE])
|
|
||||||
|
|
||||||
if ATTR_HUMIDITY in state.attributes:
|
if ATTR_HUMIDITY in state.attributes:
|
||||||
await call_service(SERVICE_SET_HUMIDITY,
|
await call_service(SERVICE_SET_HUMIDITY, [ATTR_HUMIDITY])
|
||||||
[ATTR_HUMIDITY])
|
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
|
|||||||
@@ -9,23 +9,14 @@ set_aux_heat:
|
|||||||
aux_heat:
|
aux_heat:
|
||||||
description: New value of axillary heater.
|
description: New value of axillary heater.
|
||||||
example: true
|
example: true
|
||||||
set_away_mode:
|
set_preset_mode:
|
||||||
description: Turn away mode on/off for climate device.
|
description: Set preset mode for climate device.
|
||||||
fields:
|
fields:
|
||||||
entity_id:
|
entity_id:
|
||||||
description: Name(s) of entities to change.
|
description: Name(s) of entities to change.
|
||||||
example: 'climate.kitchen'
|
example: 'climate.kitchen'
|
||||||
away_mode:
|
preset_mode:
|
||||||
description: New value of away mode.
|
description: New value of preset 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
|
|
||||||
example: 'away'
|
example: 'away'
|
||||||
set_temperature:
|
set_temperature:
|
||||||
description: Set target temperature of climate device.
|
description: Set target temperature of climate device.
|
||||||
@@ -42,9 +33,9 @@ set_temperature:
|
|||||||
target_temp_low:
|
target_temp_low:
|
||||||
description: New target low temperature for HVAC.
|
description: New target low temperature for HVAC.
|
||||||
example: 20
|
example: 20
|
||||||
operation_mode:
|
hvac_mode:
|
||||||
description: Operation mode to set temperature to. This defaults to current_operation mode if not set, or set incorrectly.
|
description: HVAC operation mode to set temperature to.
|
||||||
example: 'Heat'
|
example: 'heat'
|
||||||
set_humidity:
|
set_humidity:
|
||||||
description: Set target humidity of climate device.
|
description: Set target humidity of climate device.
|
||||||
fields:
|
fields:
|
||||||
@@ -63,15 +54,15 @@ set_fan_mode:
|
|||||||
fan_mode:
|
fan_mode:
|
||||||
description: New value of fan mode.
|
description: New value of fan mode.
|
||||||
example: On Low
|
example: On Low
|
||||||
set_operation_mode:
|
set_hvac_mode:
|
||||||
description: Set operation mode for climate device.
|
description: Set HVAC operation mode for climate device.
|
||||||
fields:
|
fields:
|
||||||
entity_id:
|
entity_id:
|
||||||
description: Name(s) of entities to change.
|
description: Name(s) of entities to change.
|
||||||
example: 'climate.nest'
|
example: 'climate.nest'
|
||||||
operation_mode:
|
hvac_mode:
|
||||||
description: New value of operation mode.
|
description: New value of operation mode.
|
||||||
example: Heat
|
example: heat
|
||||||
set_swing_mode:
|
set_swing_mode:
|
||||||
description: Set swing operation for climate device.
|
description: Set swing operation for climate device.
|
||||||
fields:
|
fields:
|
||||||
@@ -81,20 +72,6 @@ set_swing_mode:
|
|||||||
swing_mode:
|
swing_mode:
|
||||||
description: New value of 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:
|
ecobee_set_fan_min_on_time:
|
||||||
description: Set the minimum fan on time.
|
description: Set the minimum fan on time.
|
||||||
fields:
|
fields:
|
||||||
@@ -137,13 +114,3 @@ nuheat_resume_program:
|
|||||||
entity_id:
|
entity_id:
|
||||||
description: Name(s) of entities to change.
|
description: Name(s) of entities to change.
|
||||||
example: 'climate.kitchen'
|
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 import ClimateDevice, PLATFORM_SCHEMA
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY,
|
HVAC_MODE_OFF, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY,
|
||||||
STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
|
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
|
||||||
SUPPORT_TARGET_TEMPERATURE)
|
SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE |
|
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE)
|
||||||
SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF)
|
|
||||||
|
|
||||||
DEFAULT_PORT = 10102
|
DEFAULT_PORT = 10102
|
||||||
|
|
||||||
AVAILABLE_MODES = [STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_DRY,
|
AVAILABLE_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL,
|
||||||
STATE_FAN_ONLY]
|
HVAC_MODE_DRY, HVAC_MODE_AUTO, HVAC_MODE_FAN_ONLY]
|
||||||
|
|
||||||
CM_TO_HA_STATE = {
|
CM_TO_HA_STATE = {
|
||||||
'heat': STATE_HEAT,
|
'heat': HVAC_MODE_HEAT,
|
||||||
'cool': STATE_COOL,
|
'cool': HVAC_MODE_COOL,
|
||||||
'auto': STATE_AUTO,
|
'auto': HVAC_MODE_AUTO,
|
||||||
'dry': STATE_DRY,
|
'dry': HVAC_MODE_DRY,
|
||||||
'fan': STATE_FAN_ONLY,
|
'fan': HVAC_MODE_FAN_ONLY,
|
||||||
}
|
}
|
||||||
|
|
||||||
HA_STATE_TO_CM = {value: key for key, value in CM_TO_HA_STATE.items()}
|
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."""
|
"""Initialize the climate device."""
|
||||||
self._device = device
|
self._device = device
|
||||||
self._uid = device.uid
|
self._uid = device.uid
|
||||||
self._operation_list = supported_modes
|
self._hvac_modes = supported_modes
|
||||||
|
self._hvac_mode = None
|
||||||
self._target_temperature = None
|
self._target_temperature = None
|
||||||
self._current_temperature = None
|
self._current_temperature = None
|
||||||
self._current_fan_mode = None
|
self._current_fan_mode = None
|
||||||
@@ -89,7 +89,10 @@ class CoolmasterClimate(ClimateDevice):
|
|||||||
self._on = status['is_on']
|
self._on = status['is_on']
|
||||||
|
|
||||||
device_mode = status['mode']
|
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':
|
if status['unit'] == 'celsius':
|
||||||
self._unit = TEMP_CELSIUS
|
self._unit = TEMP_CELSIUS
|
||||||
@@ -127,27 +130,22 @@ class CoolmasterClimate(ClimateDevice):
|
|||||||
return self._target_temperature
|
return self._target_temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return hvac target hvac state."""
|
||||||
return self._current_operation
|
return self._hvac_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return self._operation_list
|
return self._hvac_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def fan_mode(self):
|
||||||
"""Return true if the device is on."""
|
|
||||||
return self._on
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_fan_mode(self):
|
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self._current_fan_mode
|
return self._current_fan_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""Return the list of available fan modes."""
|
||||||
return FAN_MODES
|
return FAN_MODES
|
||||||
|
|
||||||
@@ -165,18 +163,13 @@ class CoolmasterClimate(ClimateDevice):
|
|||||||
fan_mode)
|
fan_mode)
|
||||||
self._device.set_fan_speed(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."""
|
"""Set new operation mode."""
|
||||||
_LOGGER.debug("Setting operation mode of %s to %s", self.unique_id,
|
_LOGGER.debug("Setting operation mode of %s to %s", self.unique_id,
|
||||||
operation_mode)
|
hvac_mode)
|
||||||
self._device.set_mode(HA_STATE_TO_CM[operation_mode])
|
|
||||||
|
|
||||||
def turn_on(self):
|
if hvac_mode == HVAC_MODE_OFF:
|
||||||
"""Turn on."""
|
self._device.turn_off()
|
||||||
_LOGGER.debug("Turning %s on", self.unique_id)
|
else:
|
||||||
self._device.turn_on()
|
self._device.set_mode(HA_STATE_TO_CM[hvac_mode])
|
||||||
|
self._device.turn_on()
|
||||||
def turn_off(self):
|
|
||||||
"""Turn off."""
|
|
||||||
_LOGGER.debug("Turning %s off", self.unique_id)
|
|
||||||
self._device.turn_off()
|
|
||||||
|
|||||||
@@ -5,14 +5,17 @@ import re
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
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 (
|
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
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from . import DOMAIN as DAIKIN_DOMAIN
|
from . import DOMAIN as DAIKIN_DOMAIN
|
||||||
@@ -27,26 +30,31 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
HA_STATE_TO_DAIKIN = {
|
HA_STATE_TO_DAIKIN = {
|
||||||
STATE_FAN_ONLY: 'fan',
|
HVAC_MODE_FAN_ONLY: 'fan',
|
||||||
STATE_DRY: 'dry',
|
HVAC_MODE_DRY: 'dry',
|
||||||
STATE_COOL: 'cool',
|
HVAC_MODE_COOL: 'cool',
|
||||||
STATE_HEAT: 'hot',
|
HVAC_MODE_HEAT: 'hot',
|
||||||
STATE_AUTO: 'auto',
|
HVAC_MODE_HEAT_COOL: 'auto',
|
||||||
STATE_OFF: 'off',
|
HVAC_MODE_OFF: 'off',
|
||||||
}
|
}
|
||||||
|
|
||||||
DAIKIN_TO_HA_STATE = {
|
DAIKIN_TO_HA_STATE = {
|
||||||
'fan': STATE_FAN_ONLY,
|
'fan': HVAC_MODE_FAN_ONLY,
|
||||||
'dry': STATE_DRY,
|
'dry': HVAC_MODE_DRY,
|
||||||
'cool': STATE_COOL,
|
'cool': HVAC_MODE_COOL,
|
||||||
'hot': STATE_HEAT,
|
'hot': HVAC_MODE_HEAT,
|
||||||
'auto': STATE_AUTO,
|
'auto': HVAC_MODE_HEAT_COOL,
|
||||||
'off': STATE_OFF,
|
'off': HVAC_MODE_OFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
HA_PRESET_TO_DAIKIN = {
|
||||||
|
PRESET_AWAY: 'on',
|
||||||
|
PRESET_HOME: 'off'
|
||||||
}
|
}
|
||||||
|
|
||||||
HA_ATTR_TO_DAIKIN = {
|
HA_ATTR_TO_DAIKIN = {
|
||||||
ATTR_AWAY_MODE: 'en_hol',
|
ATTR_PRESET_MODE: 'en_hol',
|
||||||
ATTR_OPERATION_MODE: 'mode',
|
ATTR_HVAC_MODE: 'mode',
|
||||||
ATTR_FAN_MODE: 'f_rate',
|
ATTR_FAN_MODE: 'f_rate',
|
||||||
ATTR_SWING_MODE: 'f_dir',
|
ATTR_SWING_MODE: 'f_dir',
|
||||||
ATTR_INSIDE_TEMPERATURE: 'htemp',
|
ATTR_INSIDE_TEMPERATURE: 'htemp',
|
||||||
@@ -80,7 +88,7 @@ class DaikinClimate(ClimateDevice):
|
|||||||
|
|
||||||
self._api = api
|
self._api = api
|
||||||
self._list = {
|
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_FAN_MODE: self._api.device.fan_rate,
|
||||||
ATTR_SWING_MODE: list(
|
ATTR_SWING_MODE: list(
|
||||||
map(
|
map(
|
||||||
@@ -90,12 +98,10 @@ class DaikinClimate(ClimateDevice):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
self._supported_features = (SUPPORT_ON_OFF
|
self._supported_features = SUPPORT_TARGET_TEMPERATURE
|
||||||
| SUPPORT_OPERATION_MODE
|
|
||||||
| SUPPORT_TARGET_TEMPERATURE)
|
|
||||||
|
|
||||||
if self._api.device.support_away_mode:
|
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:
|
if self._api.device.support_fan_rate:
|
||||||
self._supported_features |= SUPPORT_FAN_MODE
|
self._supported_features |= SUPPORT_FAN_MODE
|
||||||
@@ -127,7 +133,7 @@ class DaikinClimate(ClimateDevice):
|
|||||||
value = self._api.device.represent(daikin_attr)[1].title()
|
value = self._api.device.represent(daikin_attr)[1].title()
|
||||||
elif key == ATTR_SWING_MODE:
|
elif key == ATTR_SWING_MODE:
|
||||||
value = self._api.device.represent(daikin_attr)[1].title()
|
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
|
# Daikin can return also internal states auto-1 or auto-7
|
||||||
# and we need to translate them as AUTO
|
# and we need to translate them as AUTO
|
||||||
daikin_mode = re.sub(
|
daikin_mode = re.sub(
|
||||||
@@ -135,6 +141,10 @@ class DaikinClimate(ClimateDevice):
|
|||||||
self._api.device.represent(daikin_attr)[1])
|
self._api.device.represent(daikin_attr)[1])
|
||||||
ha_mode = DAIKIN_TO_HA_STATE.get(daikin_mode)
|
ha_mode = DAIKIN_TO_HA_STATE.get(daikin_mode)
|
||||||
value = ha_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:
|
if value is None:
|
||||||
_LOGGER.error("Invalid value requested for key %s", key)
|
_LOGGER.error("Invalid value requested for key %s", key)
|
||||||
@@ -154,15 +164,17 @@ class DaikinClimate(ClimateDevice):
|
|||||||
values = {}
|
values = {}
|
||||||
|
|
||||||
for attr in [ATTR_TEMPERATURE, ATTR_FAN_MODE, ATTR_SWING_MODE,
|
for attr in [ATTR_TEMPERATURE, ATTR_FAN_MODE, ATTR_SWING_MODE,
|
||||||
ATTR_OPERATION_MODE]:
|
ATTR_HVAC_MODE, ATTR_PRESET_MODE]:
|
||||||
value = settings.get(attr)
|
value = settings.get(attr)
|
||||||
if value is None:
|
if value is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
daikin_attr = HA_ATTR_TO_DAIKIN.get(attr)
|
daikin_attr = HA_ATTR_TO_DAIKIN.get(attr)
|
||||||
if daikin_attr is not None:
|
if daikin_attr is not None:
|
||||||
if attr == ATTR_OPERATION_MODE:
|
if attr == ATTR_HVAC_MODE:
|
||||||
values[daikin_attr] = HA_STATE_TO_DAIKIN[value]
|
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]:
|
elif value in self._list[attr]:
|
||||||
values[daikin_attr] = value.lower()
|
values[daikin_attr] = value.lower()
|
||||||
else:
|
else:
|
||||||
@@ -218,21 +230,21 @@ class DaikinClimate(ClimateDevice):
|
|||||||
await self._set(kwargs)
|
await self._set(kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return self.get(ATTR_OPERATION_MODE)
|
return self.get(ATTR_HVAC_MODE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""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."""
|
"""Set HVAC mode."""
|
||||||
await self._set({ATTR_OPERATION_MODE: operation_mode})
|
await self._set({ATTR_HVAC_MODE: hvac_mode})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self.get(ATTR_FAN_MODE)
|
return self.get(ATTR_FAN_MODE)
|
||||||
|
|
||||||
@@ -241,12 +253,12 @@ class DaikinClimate(ClimateDevice):
|
|||||||
await self._set({ATTR_FAN_MODE: fan_mode})
|
await self._set({ATTR_FAN_MODE: fan_mode})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""List of available fan modes."""
|
"""List of available fan modes."""
|
||||||
return self._list.get(ATTR_FAN_MODE)
|
return self._list.get(ATTR_FAN_MODE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_swing_mode(self):
|
def swing_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self.get(ATTR_SWING_MODE)
|
return self.get(ATTR_SWING_MODE)
|
||||||
|
|
||||||
@@ -255,10 +267,24 @@ class DaikinClimate(ClimateDevice):
|
|||||||
await self._set({ATTR_SWING_MODE: swing_mode})
|
await self._set({ATTR_SWING_MODE: swing_mode})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def swing_list(self):
|
def swing_modes(self):
|
||||||
"""List of available swing modes."""
|
"""List of available swing modes."""
|
||||||
return self._list.get(ATTR_SWING_MODE)
|
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):
|
async def async_update(self):
|
||||||
"""Retrieve latest state."""
|
"""Retrieve latest state."""
|
||||||
await self._api.async_update()
|
await self._api.async_update()
|
||||||
@@ -267,36 +293,3 @@ class DaikinClimate(ClimateDevice):
|
|||||||
def device_info(self):
|
def device_info(self):
|
||||||
"""Return a device description for device registry."""
|
"""Return a device description for device registry."""
|
||||||
return self._api.device_info
|
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 import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
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 (
|
from homeassistant.const import (
|
||||||
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, TEMP_CELSIUS)
|
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, TEMP_CELSIUS)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
@@ -13,6 +13,8 @@ from .const import ATTR_OFFSET, ATTR_VALVE, NEW_SENSOR
|
|||||||
from .deconz_device import DeconzDevice
|
from .deconz_device import DeconzDevice
|
||||||
from .gateway import get_gateway_from_config_entry
|
from .gateway import get_gateway_from_config_entry
|
||||||
|
|
||||||
|
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass, config, async_add_entities, discovery_info=None):
|
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):
|
class DeconzThermostat(DeconzDevice, ClimateDevice):
|
||||||
"""Representation of a deCONZ thermostat."""
|
"""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
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
return self._features
|
return SUPPORT_TARGET_TEMPERATURE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def hvac_mode(self):
|
||||||
"""Return true if on."""
|
"""Return hvac operation ie. heat, cool mode.
|
||||||
return self._device.state_on
|
|
||||||
|
|
||||||
async def async_turn_on(self):
|
Need to be one of HVAC_MODE_*.
|
||||||
"""Turn on switch."""
|
"""
|
||||||
data = {'mode': 'auto'}
|
if self._device.on:
|
||||||
await self._device.async_set_config(data)
|
return HVAC_MODE_HEAT
|
||||||
|
return HVAC_MODE_OFF
|
||||||
|
|
||||||
async def async_turn_off(self):
|
@property
|
||||||
"""Turn off switch."""
|
def hvac_modes(self):
|
||||||
data = {'mode': 'off'}
|
"""Return the list of available hvac operation modes.
|
||||||
await self._device.async_set_config(data)
|
|
||||||
|
Need to be a subset of HVAC_MODES.
|
||||||
|
"""
|
||||||
|
return SUPPORT_HVAC
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
@@ -97,6 +95,15 @@ class DeconzThermostat(DeconzDevice, ClimateDevice):
|
|||||||
|
|
||||||
await self._device.async_set_config(data)
|
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
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
|
|||||||
@@ -1,85 +1,138 @@
|
|||||||
"""Demo platform that offers a fake climate device."""
|
"""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 import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, SUPPORT_AUX_HEAT,
|
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL,
|
||||||
SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, SUPPORT_HOLD_MODE, SUPPORT_ON_OFF,
|
CURRENT_HVAC_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_HUMIDITY,
|
HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, HVAC_MODES, SUPPORT_AUX_HEAT,
|
||||||
SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW,
|
SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE,
|
||||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH,
|
SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE,
|
||||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
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):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up the Demo climate devices."""
|
"""Set up the Demo climate devices."""
|
||||||
add_entities([
|
add_entities([
|
||||||
DemoClimate('HeatPump', 68, TEMP_FAHRENHEIT, None, None, 77,
|
DemoClimate(
|
||||||
None, None, None, None, 'heat', None, None,
|
name='HeatPump',
|
||||||
None, True),
|
target_temperature=68,
|
||||||
DemoClimate('Hvac', 21, TEMP_CELSIUS, True, None, 22, 'On High',
|
unit_of_measurement=TEMP_FAHRENHEIT,
|
||||||
67, 54, 'Off', 'cool', False, None, None, None),
|
preset=None,
|
||||||
DemoClimate('Ecobee', None, TEMP_CELSIUS, None, 'home', 23, 'Auto Low',
|
current_temperature=77,
|
||||||
None, None, 'Auto', 'auto', None, 24, 21, None)
|
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):
|
class DemoClimate(ClimateDevice):
|
||||||
"""Representation of a demo climate device."""
|
"""Representation of a demo climate device."""
|
||||||
|
|
||||||
def __init__(self, name, target_temperature, unit_of_measurement,
|
def __init__(
|
||||||
away, hold, current_temperature, current_fan_mode,
|
self,
|
||||||
target_humidity, current_humidity, current_swing_mode,
|
name,
|
||||||
current_operation, aux, target_temp_high, target_temp_low,
|
target_temperature,
|
||||||
is_on):
|
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."""
|
"""Initialize the climate device."""
|
||||||
self._name = name
|
self._name = name
|
||||||
self._support_flags = SUPPORT_FLAGS
|
self._support_flags = SUPPORT_FLAGS
|
||||||
if target_temperature is not None:
|
if target_temperature is not None:
|
||||||
self._support_flags = \
|
self._support_flags = \
|
||||||
self._support_flags | SUPPORT_TARGET_TEMPERATURE
|
self._support_flags | SUPPORT_TARGET_TEMPERATURE
|
||||||
if away is not None:
|
if preset is not None:
|
||||||
self._support_flags = self._support_flags | SUPPORT_AWAY_MODE
|
self._support_flags = self._support_flags | SUPPORT_PRESET_MODE
|
||||||
if hold is not None:
|
if fan_mode is not None:
|
||||||
self._support_flags = self._support_flags | SUPPORT_HOLD_MODE
|
|
||||||
if current_fan_mode is not None:
|
|
||||||
self._support_flags = self._support_flags | SUPPORT_FAN_MODE
|
self._support_flags = self._support_flags | SUPPORT_FAN_MODE
|
||||||
if target_humidity is not None:
|
if target_humidity is not None:
|
||||||
self._support_flags = \
|
self._support_flags = \
|
||||||
self._support_flags | SUPPORT_TARGET_HUMIDITY
|
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
|
self._support_flags = self._support_flags | SUPPORT_SWING_MODE
|
||||||
if current_operation is not None:
|
if hvac_action is not None:
|
||||||
self._support_flags = self._support_flags | SUPPORT_OPERATION_MODE
|
self._support_flags = self._support_flags
|
||||||
if aux is not None:
|
if aux is not None:
|
||||||
self._support_flags = self._support_flags | SUPPORT_AUX_HEAT
|
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 = \
|
||||||
self._support_flags | SUPPORT_TARGET_TEMPERATURE_HIGH
|
self._support_flags | SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||||
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._target_temperature = target_temperature
|
self._target_temperature = target_temperature
|
||||||
self._target_humidity = target_humidity
|
self._target_humidity = target_humidity
|
||||||
self._unit_of_measurement = unit_of_measurement
|
self._unit_of_measurement = unit_of_measurement
|
||||||
self._away = away
|
self._preset = preset
|
||||||
self._hold = hold
|
self._preset_modes = preset_modes
|
||||||
self._current_temperature = current_temperature
|
self._current_temperature = current_temperature
|
||||||
self._current_humidity = current_humidity
|
self._current_humidity = current_humidity
|
||||||
self._current_fan_mode = current_fan_mode
|
self._current_fan_mode = fan_mode
|
||||||
self._current_operation = current_operation
|
self._hvac_action = hvac_action
|
||||||
|
self._hvac_mode = hvac_mode
|
||||||
self._aux = aux
|
self._aux = aux
|
||||||
self._current_swing_mode = current_swing_mode
|
self._current_swing_mode = swing_mode
|
||||||
self._fan_list = ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off']
|
self._fan_modes = ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off']
|
||||||
self._operation_list = ['heat', 'cool', 'auto', 'off']
|
self._hvac_modes = hvac_modes
|
||||||
self._swing_list = ['Auto', '1', '2', '3', 'Off']
|
self._swing_modes = ['Auto', '1', '2', '3', 'Off']
|
||||||
self._target_temperature_high = target_temp_high
|
self._target_temperature_high = target_temp_high
|
||||||
self._target_temperature_low = target_temp_low
|
self._target_temperature_low = target_temp_low
|
||||||
self._on = is_on
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
@@ -132,46 +185,56 @@ class DemoClimate(ClimateDevice):
|
|||||||
return self._target_humidity
|
return self._target_humidity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_action(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return self._current_operation
|
return self._hvac_action
|
||||||
|
|
||||||
@property
|
@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 the list of available operation modes."""
|
||||||
return self._operation_list
|
return self._hvac_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_away_mode_on(self):
|
def preset_mode(self):
|
||||||
"""Return if away mode is on."""
|
"""Return preset mode."""
|
||||||
return self._away
|
return self._preset
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_hold_mode(self):
|
def preset_modes(self):
|
||||||
"""Return hold mode setting."""
|
"""Return preset modes."""
|
||||||
return self._hold
|
return self._preset_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_aux_heat_on(self):
|
def is_aux_heat(self):
|
||||||
"""Return true if aux heat is on."""
|
"""Return true if aux heat is on."""
|
||||||
return self._aux
|
return self._aux
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def fan_mode(self):
|
||||||
"""Return true if the device is on."""
|
|
||||||
return self._on
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_fan_mode(self):
|
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self._current_fan_mode
|
return self._current_fan_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""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."""
|
"""Set new target temperatures."""
|
||||||
if kwargs.get(ATTR_TEMPERATURE) is not None:
|
if kwargs.get(ATTR_TEMPERATURE) is not None:
|
||||||
self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
|
self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
@@ -179,69 +242,39 @@ class DemoClimate(ClimateDevice):
|
|||||||
kwargs.get(ATTR_TARGET_TEMP_LOW) is not None:
|
kwargs.get(ATTR_TARGET_TEMP_LOW) is not None:
|
||||||
self._target_temperature_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
self._target_temperature_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||||
self._target_temperature_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
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."""
|
"""Set new humidity level."""
|
||||||
self._target_humidity = humidity
|
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."""
|
"""Set new swing mode."""
|
||||||
self._current_swing_mode = 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."""
|
"""Set new fan mode."""
|
||||||
self._current_fan_mode = 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."""
|
"""Set new operation mode."""
|
||||||
self._current_operation = operation_mode
|
self._hvac_mode = hvac_mode
|
||||||
self.schedule_update_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
async def async_set_preset_mode(self, preset_mode):
|
||||||
def current_swing_mode(self):
|
"""Update preset_mode on."""
|
||||||
"""Return the swing setting."""
|
self._preset = preset_mode
|
||||||
return self._current_swing_mode
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@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()
|
|
||||||
|
|
||||||
def turn_aux_heat_on(self):
|
def turn_aux_heat_on(self):
|
||||||
"""Turn auxiliary heater on."""
|
"""Turn auxiliary heater on."""
|
||||||
self._aux = True
|
self._aux = True
|
||||||
self.schedule_update_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
def turn_aux_heat_off(self):
|
def turn_aux_heat_off(self):
|
||||||
"""Turn auxiliary heater off."""
|
"""Turn auxiliary heater off."""
|
||||||
self._aux = False
|
self._aux = False
|
||||||
self.schedule_update_ha_state()
|
self.async_write_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()
|
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
"""Support for Dyson Pure Hot+Cool link fan."""
|
"""Support for Dyson Pure Hot+Cool link fan."""
|
||||||
import logging
|
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 import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_FAN_MODE,
|
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, HVAC_MODE_COOL,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
HVAC_MODE_HEAT, SUPPORT_FAN_MODE, FAN_FOCUS,
|
||||||
|
FAN_DIFFUSE, SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
|
|
||||||
from . import DYSON_DEVICES
|
from . import DYSON_DEVICES
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
STATE_DIFFUSE = "Diffuse Mode"
|
SUPPORT_FAN = [FAN_FOCUS, FAN_DIFFUSE]
|
||||||
STATE_FOCUS = "Focus Mode"
|
SUPPORT_HVAG = [HVAC_MODE_COOL, HVAC_MODE_HEAT]
|
||||||
FAN_LIST = [STATE_FOCUS, STATE_DIFFUSE]
|
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||||
OPERATION_LIST = [STATE_HEAT, STATE_COOL]
|
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
|
||||||
| SUPPORT_OPERATION_MODE)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
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:
|
if discovery_info is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink
|
|
||||||
# Get Dyson Devices from parent component.
|
# Get Dyson Devices from parent component.
|
||||||
add_devices(
|
add_devices(
|
||||||
[DysonPureHotCoolLinkDevice(device)
|
[DysonPureHotCoolLinkDevice(device)
|
||||||
@@ -43,17 +44,17 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
|
|||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Call when entity is added to hass."""
|
"""Call when entity is added to hass."""
|
||||||
self.hass.async_add_job(self._device.add_message_listener,
|
self.hass.async_add_job(
|
||||||
self.on_message)
|
self._device.add_message_listener, self.on_message)
|
||||||
|
|
||||||
def on_message(self, message):
|
def on_message(self, message):
|
||||||
"""Call when new messages received from the climate."""
|
"""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(
|
||||||
_LOGGER.debug("Message received for climate device %s : %s",
|
"Message received for climate device %s : %s", self.name, message)
|
||||||
self.name, message)
|
self.schedule_update_ha_state()
|
||||||
self.schedule_update_ha_state()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
@@ -101,32 +102,46 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return hvac operation ie. heat, cool mode.
|
||||||
from libpurecool.const import HeatMode, HeatState
|
|
||||||
|
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_mode == HeatMode.HEAT_ON.value:
|
||||||
if self._device.state.heat_state == HeatState.HEAT_STATE_ON.value:
|
if self._device.state.heat_state == HeatState.HEAT_STATE_ON.value:
|
||||||
return STATE_HEAT
|
return CURRENT_HVAC_HEAT
|
||||||
return STATE_IDLE
|
return CURRENT_HVAC_IDLE
|
||||||
return STATE_COOL
|
return CURRENT_HVAC_COOL
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def fan_mode(self):
|
||||||
"""Return the list of available operation modes."""
|
|
||||||
return OPERATION_LIST
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_fan_mode(self):
|
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
from libpurecool.const import FocusMode
|
|
||||||
if self._device.state.focus_mode == FocusMode.FOCUS_ON.value:
|
if self._device.state.focus_mode == FocusMode.FOCUS_ON.value:
|
||||||
return STATE_FOCUS
|
return FAN_FOCUS
|
||||||
return STATE_DIFFUSE
|
return FAN_DIFFUSE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""Return the list of available fan modes."""
|
||||||
return FAN_LIST
|
return SUPPORT_FAN
|
||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
@@ -138,7 +153,6 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
|
|||||||
# Limit the target temperature into acceptable range.
|
# Limit the target temperature into acceptable range.
|
||||||
target_temp = min(self.max_temp, target_temp)
|
target_temp = min(self.max_temp, target_temp)
|
||||||
target_temp = max(self.min_temp, target_temp)
|
target_temp = max(self.min_temp, target_temp)
|
||||||
from libpurecool.const import HeatTarget, HeatMode
|
|
||||||
self._device.set_configuration(
|
self._device.set_configuration(
|
||||||
heat_target=HeatTarget.celsius(target_temp),
|
heat_target=HeatTarget.celsius(target_temp),
|
||||||
heat_mode=HeatMode.HEAT_ON)
|
heat_mode=HeatMode.HEAT_ON)
|
||||||
@@ -146,19 +160,17 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
|
|||||||
def set_fan_mode(self, fan_mode):
|
def set_fan_mode(self, fan_mode):
|
||||||
"""Set new fan mode."""
|
"""Set new fan mode."""
|
||||||
_LOGGER.debug("Set %s focus mode %s", self.name, fan_mode)
|
_LOGGER.debug("Set %s focus mode %s", self.name, fan_mode)
|
||||||
from libpurecool.const import FocusMode
|
if fan_mode == FAN_FOCUS:
|
||||||
if fan_mode == STATE_FOCUS:
|
|
||||||
self._device.set_configuration(focus_mode=FocusMode.FOCUS_ON)
|
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)
|
self._device.set_configuration(focus_mode=FocusMode.FOCUS_OFF)
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set operation mode."""
|
"""Set new target hvac mode."""
|
||||||
_LOGGER.debug("Set %s heat mode %s", self.name, operation_mode)
|
_LOGGER.debug("Set %s heat mode %s", self.name, hvac_mode)
|
||||||
from libpurecool.const import HeatMode
|
if hvac_mode == HVAC_MODE_HEAT:
|
||||||
if operation_mode == STATE_HEAT:
|
|
||||||
self._device.set_configuration(heat_mode=HeatMode.HEAT_ON)
|
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)
|
self._device.set_configuration(heat_mode=HeatMode.HEAT_OFF)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
"""Support for Ecobee Thermostats."""
|
"""Support for Ecobee Thermostats."""
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import ecobee
|
from homeassistant.components import ecobee
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
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,
|
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE,
|
||||||
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE,
|
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_RANGE, SUPPORT_FAN_MODE,
|
||||||
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
|
PRESET_AWAY, FAN_AUTO, FAN_ON, CURRENT_HVAC_OFF, CURRENT_HVAC_HEAT,
|
||||||
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_FAN_MODE,
|
CURRENT_HVAC_COOL
|
||||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
)
|
||||||
from homeassistant.const import (
|
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
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_CONFIGURING = {}
|
_CONFIGURING = {}
|
||||||
@@ -23,10 +24,33 @@ ATTR_FAN_MIN_ON_TIME = 'fan_min_on_time'
|
|||||||
ATTR_RESUME_ALL = 'resume_all'
|
ATTR_RESUME_ALL = 'resume_all'
|
||||||
|
|
||||||
DEFAULT_RESUME_ALL = False
|
DEFAULT_RESUME_ALL = False
|
||||||
TEMPERATURE_HOLD = 'temp'
|
PRESET_TEMPERATURE = 'temp'
|
||||||
VACATION_HOLD = 'vacation'
|
PRESET_VACATION = 'vacation'
|
||||||
|
PRESET_AUX_HEAT_ONLY = 'aux_heat_only'
|
||||||
|
PRESET_HOLD_NEXT_TRANSITION = 'next_transition'
|
||||||
|
PRESET_HOLD_INDEFINITE = 'indefinite'
|
||||||
AWAY_MODE = 'awayMode'
|
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_SET_FAN_MIN_ON_TIME = 'ecobee_set_fan_min_on_time'
|
||||||
SERVICE_RESUME_PROGRAM = 'ecobee_resume_program'
|
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,
|
vol.Optional(ATTR_RESUME_ALL, default=DEFAULT_RESUME_ALL): cv.boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
|
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
||||||
SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE |
|
SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_RANGE |
|
||||||
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH |
|
SUPPORT_FAN_MODE)
|
||||||
SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_HIGH |
|
|
||||||
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_FAN_MODE)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
@@ -114,9 +136,10 @@ class Thermostat(ClimateDevice):
|
|||||||
self.hold_temp = hold_temp
|
self.hold_temp = hold_temp
|
||||||
self.vacation = None
|
self.vacation = None
|
||||||
self._climate_list = self.climate_list
|
self._climate_list = self.climate_list
|
||||||
self._operation_list = ['auto', 'auxHeatOnly', 'cool',
|
self._operation_list = [
|
||||||
'heat', 'off']
|
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF
|
||||||
self._fan_list = ['auto', 'on']
|
]
|
||||||
|
self._fan_modes = [FAN_AUTO, FAN_ON]
|
||||||
self.update_without_throttle = False
|
self.update_without_throttle = False
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
@@ -143,6 +166,9 @@ class Thermostat(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
|
if self.thermostat['settings']['useCelsius']:
|
||||||
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
return TEMP_FAHRENHEIT
|
return TEMP_FAHRENHEIT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -153,25 +179,25 @@ class Thermostat(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def target_temperature_low(self):
|
def target_temperature_low(self):
|
||||||
"""Return the lower bound temperature we try to reach."""
|
"""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 self.thermostat['runtime']['desiredHeat'] / 10.0
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_high(self):
|
def target_temperature_high(self):
|
||||||
"""Return the upper bound temperature we try to reach."""
|
"""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 self.thermostat['runtime']['desiredCool'] / 10.0
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
if self.current_operation == STATE_AUTO:
|
if self.hvac_mode == HVAC_MODE_AUTO:
|
||||||
return None
|
return None
|
||||||
if self.current_operation == STATE_HEAT:
|
if self.hvac_mode == HVAC_MODE_HEAT:
|
||||||
return self.thermostat['runtime']['desiredHeat'] / 10.0
|
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 self.thermostat['runtime']['desiredCool'] / 10.0
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -180,70 +206,63 @@ class Thermostat(ClimateDevice):
|
|||||||
"""Return the current fan status."""
|
"""Return the current fan status."""
|
||||||
if 'fan' in self.thermostat['equipmentStatus']:
|
if 'fan' in self.thermostat['equipmentStatus']:
|
||||||
return STATE_ON
|
return STATE_ON
|
||||||
return STATE_OFF
|
return HVAC_MODE_OFF
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self.thermostat['runtime']['desiredFanMode']
|
return self.thermostat['runtime']['desiredFanMode']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_hold_mode(self):
|
def fan_modes(self):
|
||||||
"""Return current hold mode."""
|
|
||||||
mode = self._current_hold_mode
|
|
||||||
return None if mode == AWAY_MODE else mode
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fan_list(self):
|
|
||||||
"""Return the available fan modes."""
|
"""Return the available fan modes."""
|
||||||
return self._fan_list
|
return self._fan_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _current_hold_mode(self):
|
def preset_mode(self):
|
||||||
|
"""Return current preset mode."""
|
||||||
events = self.thermostat['events']
|
events = self.thermostat['events']
|
||||||
for event in events:
|
for event in events:
|
||||||
if event['running']:
|
if not event['running']:
|
||||||
if event['type'] == 'hold':
|
continue
|
||||||
if event['holdClimateRef'] == 'away':
|
|
||||||
if int(event['endDate'][0:4]) - \
|
if event['type'] == 'hold':
|
||||||
int(event['startDate'][0:4]) <= 1:
|
if event['holdClimateRef'] == 'away':
|
||||||
# A temporary hold from away climate is a hold
|
if int(event['endDate'][0:4]) - \
|
||||||
return 'away'
|
int(event['startDate'][0:4]) <= 1:
|
||||||
# A permanent hold from away climate
|
# A temporary hold from away climate is a hold
|
||||||
return AWAY_MODE
|
return PRESET_AWAY
|
||||||
if event['holdClimateRef'] != "":
|
# A permanent hold from away climate
|
||||||
# Any other hold based on climate
|
return PRESET_AWAY
|
||||||
return event['holdClimateRef']
|
if event['holdClimateRef'] != "":
|
||||||
# Any hold not based on a climate is a temp hold
|
# Any other hold based on climate
|
||||||
return TEMPERATURE_HOLD
|
return event['holdClimateRef']
|
||||||
if event['type'].startswith('auto'):
|
# Any hold not based on a climate is a temp hold
|
||||||
# All auto modes are treated as holds
|
return PRESET_TEMPERATURE
|
||||||
return event['type'][4:].lower()
|
if event['type'].startswith('auto'):
|
||||||
if event['type'] == 'vacation':
|
# All auto modes are treated as holds
|
||||||
self.vacation = event['name']
|
return event['type'][4:].lower()
|
||||||
return VACATION_HOLD
|
if event['type'] == 'vacation':
|
||||||
|
self.vacation = event['name']
|
||||||
|
return PRESET_VACATION
|
||||||
|
|
||||||
|
if self.is_aux_heat:
|
||||||
|
return PRESET_AUX_HEAT_ONLY
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation."""
|
"""Return current operation."""
|
||||||
if self.operation_mode == 'auxHeatOnly' or \
|
return ECOBEE_HVAC_TO_HASS[self.thermostat['settings']['hvacMode']]
|
||||||
self.operation_mode == 'heatPump':
|
|
||||||
return STATE_HEAT
|
|
||||||
return self.operation_mode
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the operation modes list."""
|
"""Return the operation modes list."""
|
||||||
return self._operation_list
|
return self._operation_list
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_mode(self):
|
def climate_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
|
||||||
return self.thermostat['settings']['hvacMode']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def mode(self):
|
|
||||||
"""Return current mode, as the user-visible name."""
|
"""Return current mode, as the user-visible name."""
|
||||||
cur = self.thermostat['program']['currentClimateRef']
|
cur = self.thermostat['program']['currentClimateRef']
|
||||||
climates = self.thermostat['program']['climates']
|
climates = self.thermostat['program']['climates']
|
||||||
@@ -251,80 +270,76 @@ class Thermostat(ClimateDevice):
|
|||||||
return current[0]['name']
|
return current[0]['name']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_min_on_time(self):
|
def current_humidity(self) -> Optional[int]:
|
||||||
"""Return current fan minimum on time."""
|
"""Return the current humidity."""
|
||||||
return self.thermostat['settings']['fanMinOnTime']
|
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
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return device specific state attributes."""
|
"""Return device specific state attributes."""
|
||||||
# Move these to Thermostat Device and make them global
|
|
||||||
status = self.thermostat['equipmentStatus']
|
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 {
|
return {
|
||||||
"actual_humidity": self.thermostat['runtime']['actualHumidity'],
|
|
||||||
"fan": self.fan,
|
"fan": self.fan,
|
||||||
"climate_mode": self.mode,
|
"climate_mode": self.climate_mode,
|
||||||
"operation": operation,
|
|
||||||
"equipment_running": status,
|
"equipment_running": status,
|
||||||
"climate_list": self.climate_list,
|
"climate_list": self.climate_list,
|
||||||
"fan_min_on_time": self.fan_min_on_time
|
"fan_min_on_time": self.thermostat['settings']['fanMinOnTime']
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_away_mode_on(self):
|
def is_aux_heat(self):
|
||||||
"""Return true if away mode is on."""
|
|
||||||
return self._current_hold_mode == AWAY_MODE
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_aux_heat_on(self):
|
|
||||||
"""Return true if aux heater."""
|
"""Return true if aux heater."""
|
||||||
return 'auxHeat' in self.thermostat['equipmentStatus']
|
return 'auxHeat' in self.thermostat['equipmentStatus']
|
||||||
|
|
||||||
def turn_away_mode_on(self):
|
def set_preset(self, preset):
|
||||||
"""Turn away mode on by setting it on away hold indefinitely."""
|
"""Activate a preset."""
|
||||||
if self._current_hold_mode != AWAY_MODE:
|
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',
|
self.data.ecobee.set_climate_hold(self.thermostat_index, 'away',
|
||||||
'indefinite')
|
'indefinite')
|
||||||
self.update_without_throttle = True
|
|
||||||
|
|
||||||
def turn_away_mode_off(self):
|
elif preset == PRESET_TEMPERATURE:
|
||||||
"""Turn away off."""
|
self.set_temp_hold(self.current_temperature)
|
||||||
if self._current_hold_mode == AWAY_MODE:
|
|
||||||
|
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.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:
|
else:
|
||||||
if hold_mode == TEMPERATURE_HOLD:
|
_LOGGER.warning("Received invalid preset: %s", preset)
|
||||||
self.set_temp_hold(self.current_temperature)
|
|
||||||
else:
|
@property
|
||||||
self.data.ecobee.set_climate_hold(
|
def preset_modes(self):
|
||||||
self.thermostat_index, hold_mode, self.hold_preference())
|
"""Return available preset modes."""
|
||||||
self.update_without_throttle = True
|
return PRESET_MODES
|
||||||
|
|
||||||
def set_auto_temp_hold(self, heat_temp, cool_temp):
|
def set_auto_temp_hold(self, heat_temp, cool_temp):
|
||||||
"""Set temperature hold in auto mode."""
|
"""Set temperature hold in auto mode."""
|
||||||
@@ -352,7 +367,8 @@ class Thermostat(ClimateDevice):
|
|||||||
|
|
||||||
def set_fan_mode(self, fan_mode):
|
def set_fan_mode(self, fan_mode):
|
||||||
"""Set the fan mode. Valid values are "on" or "auto"."""
|
"""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'"
|
error = "Invalid fan_mode value: Valid values are 'on' or 'auto'"
|
||||||
_LOGGER.error(error)
|
_LOGGER.error(error)
|
||||||
return
|
return
|
||||||
@@ -376,8 +392,8 @@ class Thermostat(ClimateDevice):
|
|||||||
heatCoolMinDelta property.
|
heatCoolMinDelta property.
|
||||||
https://www.ecobee.com/home/developer/api/examples/ex5.shtml
|
https://www.ecobee.com/home/developer/api/examples/ex5.shtml
|
||||||
"""
|
"""
|
||||||
if self.current_operation == STATE_HEAT or self.current_operation == \
|
if self.hvac_mode == HVAC_MODE_HEAT or \
|
||||||
STATE_COOL:
|
self.hvac_mode == HVAC_MODE_COOL:
|
||||||
heat_temp = temp
|
heat_temp = temp
|
||||||
cool_temp = temp
|
cool_temp = temp
|
||||||
else:
|
else:
|
||||||
@@ -392,7 +408,7 @@ class Thermostat(ClimateDevice):
|
|||||||
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
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):
|
(low_temp is not None or high_temp is not None):
|
||||||
self.set_auto_temp_hold(low_temp, high_temp)
|
self.set_auto_temp_hold(low_temp, high_temp)
|
||||||
elif temp is not None:
|
elif temp is not None:
|
||||||
@@ -405,9 +421,14 @@ class Thermostat(ClimateDevice):
|
|||||||
"""Set the humidity level."""
|
"""Set the humidity level."""
|
||||||
self.data.ecobee.set_humidity(self.thermostat_index, humidity)
|
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)."""
|
"""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
|
self.update_without_throttle = True
|
||||||
|
|
||||||
def set_fan_min_on_time(self, fan_min_on_time):
|
def set_fan_min_on_time(self, fan_min_on_time):
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
"""Support for control of Elk-M1 connected thermostats."""
|
"""Support for control of Elk-M1 connected thermostats."""
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, STATE_AUTO, STATE_COOL,
|
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, HVAC_MODE_AUTO,
|
||||||
STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE,
|
HVAC_MODE_COOL,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE_HIGH,
|
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_AUX_HEAT,
|
||||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
SUPPORT_FAN_MODE,
|
||||||
|
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||||
from homeassistant.const import PRECISION_WHOLE, STATE_ON
|
from homeassistant.const import PRECISION_WHOLE, STATE_ON
|
||||||
|
|
||||||
from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities
|
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,
|
async def async_setup_platform(hass, config, async_add_entities,
|
||||||
discovery_info=None):
|
discovery_info=None):
|
||||||
"""Create the Elk-M1 thermostat platform."""
|
"""Create the Elk-M1 thermostat platform."""
|
||||||
@@ -32,9 +37,8 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
return (SUPPORT_OPERATION_MODE | SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT
|
return (SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT
|
||||||
| SUPPORT_TARGET_TEMPERATURE_HIGH
|
| SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||||
| SUPPORT_TARGET_TEMPERATURE_LOW)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
@@ -78,14 +82,14 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||||||
return self._element.humidity
|
return self._element.humidity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return [STATE_IDLE, STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_FAN_ONLY]
|
return SUPPORT_HVAC
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def precision(self):
|
def precision(self):
|
||||||
@@ -93,7 +97,7 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||||||
return PRECISION_WHOLE
|
return PRECISION_WHOLE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_aux_heat_on(self):
|
def is_aux_heat(self):
|
||||||
"""Return if aux heater is on."""
|
"""Return if aux heater is on."""
|
||||||
from elkm1_lib.const import ThermostatMode
|
from elkm1_lib.const import ThermostatMode
|
||||||
return self._element.mode == ThermostatMode.EMERGENCY_HEAT.value
|
return self._element.mode == ThermostatMode.EMERGENCY_HEAT.value
|
||||||
@@ -109,11 +113,11 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||||||
return 99
|
return 99
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
from elkm1_lib.const import ThermostatFan
|
from elkm1_lib.const import ThermostatFan
|
||||||
if self._element.fan == ThermostatFan.AUTO.value:
|
if self._element.fan == ThermostatFan.AUTO.value:
|
||||||
return STATE_AUTO
|
return HVAC_MODE_AUTO
|
||||||
if self._element.fan == ThermostatFan.ON.value:
|
if self._element.fan == ThermostatFan.ON.value:
|
||||||
return STATE_ON
|
return STATE_ON
|
||||||
return None
|
return None
|
||||||
@@ -125,17 +129,19 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||||||
if fan is not None:
|
if fan is not None:
|
||||||
self._element.set(ThermostatSetting.FAN.value, fan)
|
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."""
|
"""Set thermostat operation mode."""
|
||||||
from elkm1_lib.const import ThermostatFan, ThermostatMode
|
from elkm1_lib.const import ThermostatFan, ThermostatMode
|
||||||
settings = {
|
settings = {
|
||||||
STATE_IDLE: (ThermostatMode.OFF.value, ThermostatFan.AUTO.value),
|
HVAC_MODE_OFF:
|
||||||
STATE_HEAT: (ThermostatMode.HEAT.value, None),
|
(ThermostatMode.OFF.value, ThermostatFan.AUTO.value),
|
||||||
STATE_COOL: (ThermostatMode.COOL.value, None),
|
HVAC_MODE_HEAT: (ThermostatMode.HEAT.value, None),
|
||||||
STATE_AUTO: (ThermostatMode.AUTO.value, None),
|
HVAC_MODE_COOL: (ThermostatMode.COOL.value, None),
|
||||||
STATE_FAN_ONLY: (ThermostatMode.OFF.value, ThermostatFan.ON.value)
|
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):
|
async def async_turn_aux_heat_on(self):
|
||||||
"""Turn auxiliary heater on."""
|
"""Turn auxiliary heater on."""
|
||||||
@@ -148,14 +154,14 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||||||
self._elk_set(ThermostatMode.HEAT.value, None)
|
self._elk_set(ThermostatMode.HEAT.value, None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""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):
|
async def async_set_fan_mode(self, fan_mode):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
from elkm1_lib.const import ThermostatFan
|
from elkm1_lib.const import ThermostatFan
|
||||||
if fan_mode == STATE_AUTO:
|
if fan_mode == HVAC_MODE_AUTO:
|
||||||
self._elk_set(None, ThermostatFan.AUTO.value)
|
self._elk_set(None, ThermostatFan.AUTO.value)
|
||||||
elif fan_mode == STATE_ON:
|
elif fan_mode == STATE_ON:
|
||||||
self._elk_set(None, ThermostatFan.ON.value)
|
self._elk_set(None, ThermostatFan.ON.value)
|
||||||
@@ -175,13 +181,13 @@ class ElkThermostat(ElkEntity, ClimateDevice):
|
|||||||
def _element_changed(self, element, changeset):
|
def _element_changed(self, element, changeset):
|
||||||
from elkm1_lib.const import ThermostatFan, ThermostatMode
|
from elkm1_lib.const import ThermostatFan, ThermostatMode
|
||||||
mode_to_state = {
|
mode_to_state = {
|
||||||
ThermostatMode.OFF.value: STATE_IDLE,
|
ThermostatMode.OFF.value: HVAC_MODE_OFF,
|
||||||
ThermostatMode.COOL.value: STATE_COOL,
|
ThermostatMode.COOL.value: HVAC_MODE_COOL,
|
||||||
ThermostatMode.HEAT.value: STATE_HEAT,
|
ThermostatMode.HEAT.value: HVAC_MODE_HEAT,
|
||||||
ThermostatMode.EMERGENCY_HEAT.value: STATE_HEAT,
|
ThermostatMode.EMERGENCY_HEAT.value: HVAC_MODE_HEAT,
|
||||||
ThermostatMode.AUTO.value: STATE_AUTO,
|
ThermostatMode.AUTO.value: HVAC_MODE_AUTO,
|
||||||
}
|
}
|
||||||
self._state = mode_to_state.get(self._element.mode)
|
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._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 import ClimateDevice, PLATFORM_SCHEMA
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_HEAT, STATE_AUTO, SUPPORT_AUX_HEAT, SUPPORT_OPERATION_MODE,
|
HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, SUPPORT_AUX_HEAT,
|
||||||
SUPPORT_TARGET_TEMPERATURE)
|
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, CURRENT_HVAC_HEAT,
|
||||||
|
CURRENT_HVAC_IDLE)
|
||||||
from homeassistant.const import (
|
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
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -16,7 +17,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
# Return cached results if last scan was less then this time ago
|
# Return cached results if last scan was less then this time ago
|
||||||
SCAN_INTERVAL = timedelta(seconds=120)
|
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({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_USERNAME): cv.string,
|
vol.Required(CONF_USERNAME): cv.string,
|
||||||
@@ -24,9 +25,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
EPH_TO_HA_STATE = {
|
EPH_TO_HA_STATE = {
|
||||||
'AUTO': STATE_AUTO,
|
'AUTO': HVAC_MODE_HEAT_COOL,
|
||||||
'ON': STATE_HEAT,
|
'ON': HVAC_MODE_HEAT,
|
||||||
'OFF': STATE_OFF
|
'OFF': HVAC_MODE_OFF
|
||||||
}
|
}
|
||||||
|
|
||||||
HA_STATE_TO_EPH = {value: key for key, value in EPH_TO_HA_STATE.items()}
|
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):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
if self._hot_water:
|
if self._hot_water:
|
||||||
return SUPPORT_AUX_HEAT | SUPPORT_OPERATION_MODE
|
return SUPPORT_AUX_HEAT
|
||||||
|
|
||||||
return (SUPPORT_TARGET_TEMPERATURE |
|
return (SUPPORT_TARGET_TEMPERATURE |
|
||||||
SUPPORT_AUX_HEAT |
|
SUPPORT_AUX_HEAT)
|
||||||
SUPPORT_OPERATION_MODE)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@@ -100,43 +100,35 @@ class EphEmberThermostat(ClimateDevice):
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def hvac_action(self):
|
||||||
"""Show Device Attributes."""
|
"""Return current HVAC action."""
|
||||||
attributes = {
|
if self._zone['isCurrentlyActive']:
|
||||||
'currently_active': self._zone['isCurrentlyActive']
|
return CURRENT_HVAC_HEAT
|
||||||
}
|
|
||||||
return attributes
|
return CURRENT_HVAC_IDLE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
from pyephember.pyephember import ZoneMode
|
from pyephember.pyephember import ZoneMode
|
||||||
mode = ZoneMode(self._zone['mode'])
|
mode = ZoneMode(self._zone['mode'])
|
||||||
return self.map_mode_eph_hass(mode)
|
return self.map_mode_eph_hass(mode)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the supported operations."""
|
"""Return the supported operations."""
|
||||||
return OPERATION_LIST
|
return OPERATION_LIST
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set the operation 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:
|
if mode is not None:
|
||||||
self._ember.set_mode_by_name(self._zone_name, mode)
|
self._ember.set_mode_by_name(self._zone_name, mode)
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("Invalid operation mode provided %s", operation_mode)
|
_LOGGER.error("Invalid operation mode provided %s", hvac_mode)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_aux_heat(self):
|
||||||
"""Return current state."""
|
|
||||||
if self._zone['isCurrentlyActive']:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_aux_heat_on(self):
|
|
||||||
"""Return true if aux heater."""
|
"""Return true if aux heater."""
|
||||||
return self._zone['isBoostActive']
|
return self._zone['isBoostActive']
|
||||||
|
|
||||||
@@ -197,4 +189,4 @@ class EphEmberThermostat(ClimateDevice):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def map_mode_eph_hass(operation_mode):
|
def map_mode_eph_hass(operation_mode):
|
||||||
"""Map from eph mode to home assistant 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."""
|
"""Support for eQ-3 Bluetooth Smart thermostats."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import eq3bt as eq3 # pylint: disable=import-error
|
||||||
import voluptuous as vol
|
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 (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_HEAT, STATE_MANUAL, STATE_ECO,
|
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST,
|
||||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE,
|
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||||
SUPPORT_ON_OFF)
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, CONF_MAC, CONF_DEVICES, STATE_ON, STATE_OFF,
|
ATTR_TEMPERATURE, CONF_DEVICES, CONF_MAC, PRECISION_HALVES, TEMP_CELSIUS)
|
||||||
TEMP_CELSIUS, PRECISION_HALVES)
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -23,6 +22,32 @@ ATTR_STATE_LOCKED = 'is_locked'
|
|||||||
ATTR_STATE_LOW_BAT = 'low_battery'
|
ATTR_STATE_LOW_BAT = 'low_battery'
|
||||||
ATTR_STATE_AWAY_END = 'away_end'
|
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({
|
DEVICE_SCHEMA = vol.Schema({
|
||||||
vol.Required(CONF_MAC): cv.string,
|
vol.Required(CONF_MAC): cv.string,
|
||||||
})
|
})
|
||||||
@@ -32,8 +57,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
vol.Schema({cv.string: DEVICE_SCHEMA}),
|
vol.Schema({cv.string: DEVICE_SCHEMA}),
|
||||||
})
|
})
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
|
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||||
SUPPORT_AWAY_MODE | SUPPORT_ON_OFF)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
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():
|
for name, device_cfg in config[CONF_DEVICES].items():
|
||||||
mac = device_cfg[CONF_MAC]
|
mac = device_cfg[CONF_MAC]
|
||||||
devices.append(EQ3BTSmartThermostat(mac, name))
|
devices.append(EQ3BTSmartThermostat(mac, name), True)
|
||||||
|
|
||||||
add_entities(devices)
|
add_entities(devices)
|
||||||
|
|
||||||
@@ -53,23 +77,8 @@ class EQ3BTSmartThermostat(ClimateDevice):
|
|||||||
def __init__(self, _mac, _name):
|
def __init__(self, _mac, _name):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
# We want to avoid name clash with this module.
|
# 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._name = _name
|
||||||
self._thermostat = eq3.Thermostat(_mac)
|
self._thermostat = eq3.Thermostat(_mac)
|
||||||
self._target_temperature = None
|
|
||||||
self._target_mode = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
@@ -79,7 +88,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return if thermostat is available."""
|
"""Return if thermostat is available."""
|
||||||
return self.current_operation is not None
|
return self._thermostat.mode > 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@@ -111,46 +120,25 @@ class EQ3BTSmartThermostat(ClimateDevice):
|
|||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
if temperature is None:
|
if temperature is None:
|
||||||
return
|
return
|
||||||
self._target_temperature = temperature
|
|
||||||
self._thermostat.target_temperature = temperature
|
self._thermostat.target_temperature = temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return the current operation mode."""
|
"""Return the current operation mode."""
|
||||||
if self._thermostat.mode < 0:
|
if self._thermostat.mode < 0:
|
||||||
return None
|
return HVAC_MODE_OFF
|
||||||
return self.modes[self._thermostat.mode]
|
return EQ_TO_HA_HVAC[self._thermostat.mode]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""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."""
|
"""Set operation mode."""
|
||||||
self._target_mode = operation_mode
|
if self.preset_mode:
|
||||||
self._thermostat.mode = self.reverse_modes[operation_mode]
|
return
|
||||||
|
self._thermostat.mode = HA_TO_EQ_HVAC[hvac_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)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
@@ -175,6 +163,28 @@ class EQ3BTSmartThermostat(ClimateDevice):
|
|||||||
|
|
||||||
return dev_specific
|
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):
|
def update(self):
|
||||||
"""Update the data from the thermostat."""
|
"""Update the data from the thermostat."""
|
||||||
# pylint: disable=import-error,no-name-in-module
|
# pylint: disable=import-error,no-name-in-module
|
||||||
@@ -183,15 +193,3 @@ class EQ3BTSmartThermostat(ClimateDevice):
|
|||||||
self._thermostat.update()
|
self._thermostat.update()
|
||||||
except BTLEException as ex:
|
except BTLEException as ex:
|
||||||
_LOGGER.warning("Updating the state failed: %s", 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 import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||||
STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_AWAY_MODE,
|
HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE,
|
||||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
|
SUPPORT_TARGET_TEMPERATURE_RANGE, PRESET_AWAY,
|
||||||
|
HVAC_MODE_OFF)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE,
|
ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE,
|
||||||
STATE_OFF, TEMP_CELSIUS)
|
TEMP_CELSIUS)
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
EsphomeEntity, esphome_map_enum, esphome_state_property,
|
EsphomeEntity, esphome_map_enum, esphome_state_property,
|
||||||
@@ -34,10 +35,10 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
|||||||
@esphome_map_enum
|
@esphome_map_enum
|
||||||
def _climate_modes():
|
def _climate_modes():
|
||||||
return {
|
return {
|
||||||
ClimateMode.OFF: STATE_OFF,
|
ClimateMode.OFF: HVAC_MODE_OFF,
|
||||||
ClimateMode.AUTO: STATE_AUTO,
|
ClimateMode.AUTO: HVAC_MODE_HEAT_COOL,
|
||||||
ClimateMode.COOL: STATE_COOL,
|
ClimateMode.COOL: HVAC_MODE_COOL,
|
||||||
ClimateMode.HEAT: STATE_HEAT,
|
ClimateMode.HEAT: HVAC_MODE_HEAT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
|
|||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self) -> List[str]:
|
def hvac_modes(self) -> List[str]:
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return [
|
return [
|
||||||
_climate_modes.from_esphome(mode)
|
_climate_modes.from_esphome(mode)
|
||||||
@@ -94,18 +95,17 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
features = SUPPORT_OPERATION_MODE
|
features = 0
|
||||||
if self._static_info.supports_two_point_target_temperature:
|
if self._static_info.supports_two_point_target_temperature:
|
||||||
features |= (SUPPORT_TARGET_TEMPERATURE_LOW |
|
features |= (SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||||
SUPPORT_TARGET_TEMPERATURE_HIGH)
|
|
||||||
else:
|
else:
|
||||||
features |= SUPPORT_TARGET_TEMPERATURE
|
features |= SUPPORT_TARGET_TEMPERATURE
|
||||||
if self._static_info.supports_away:
|
if self._static_info.supports_away:
|
||||||
features |= SUPPORT_AWAY_MODE
|
features |= SUPPORT_PRESET_MODE
|
||||||
return features
|
return features
|
||||||
|
|
||||||
@esphome_state_property
|
@esphome_state_property
|
||||||
def current_operation(self) -> Optional[str]:
|
def hvac_mode(self) -> Optional[str]:
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return _climate_modes.from_esphome(self._state.mode)
|
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 the highbound target temperature we try to reach."""
|
||||||
return self._state.target_temperature_high
|
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:
|
async def async_set_temperature(self, **kwargs) -> None:
|
||||||
"""Set new target temperature (and operation mode if set)."""
|
"""Set new target temperature (and operation mode if set)."""
|
||||||
data = {'key': self._static_info.key}
|
data = {'key': self._static_info.key}
|
||||||
if ATTR_OPERATION_MODE in kwargs:
|
if ATTR_HVAC_MODE in kwargs:
|
||||||
data['mode'] = _climate_modes.from_hass(
|
data['mode'] = _climate_modes.from_hass(
|
||||||
kwargs[ATTR_OPERATION_MODE])
|
kwargs[ATTR_HVAC_MODE])
|
||||||
if ATTR_TEMPERATURE in kwargs:
|
if ATTR_TEMPERATURE in kwargs:
|
||||||
data['target_temperature'] = kwargs[ATTR_TEMPERATURE]
|
data['target_temperature'] = kwargs[ATTR_TEMPERATURE]
|
||||||
if ATTR_TARGET_TEMP_LOW in kwargs:
|
if ATTR_TARGET_TEMP_LOW in kwargs:
|
||||||
@@ -155,12 +150,24 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
|
|||||||
mode=_climate_modes.from_hass(operation_mode),
|
mode=_climate_modes.from_hass(operation_mode),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_turn_away_mode_on(self) -> None:
|
@property
|
||||||
"""Turn away mode on."""
|
def preset_mode(self):
|
||||||
await self._client.climate_command(key=self._static_info.key,
|
"""Return current preset mode."""
|
||||||
away=True)
|
if self._state and self._state.away:
|
||||||
|
return PRESET_AWAY
|
||||||
|
|
||||||
async def async_turn_away_mode_off(self) -> None:
|
return None
|
||||||
"""Turn away mode off."""
|
|
||||||
|
@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,
|
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."""
|
"""Support for (EMEA/EU-based) Honeywell TCC climate systems.
|
||||||
# Glossary:
|
|
||||||
# TCS - temperature control system (a.k.a. Controller, Parent), which can
|
Such systems include evohome (multi-zone), and Round Thermostat (single zone).
|
||||||
# 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
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any, Dict, Tuple
|
||||||
|
|
||||||
|
from dateutil.tz import tzlocal
|
||||||
import requests.exceptions
|
import requests.exceptions
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
import evohomeclient2
|
import evohomeclient2
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_SCAN_INTERVAL, CONF_USERNAME, CONF_PASSWORD,
|
CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME,
|
||||||
EVENT_HOMEASSISTANT_START,
|
HTTP_SERVICE_UNAVAILABLE, HTTP_TOO_MANY_REQUESTS, TEMP_CELSIUS)
|
||||||
HTTP_SERVICE_UNAVAILABLE, HTTP_TOO_MANY_REQUESTS,
|
|
||||||
PRECISION_HALVES, TEMP_CELSIUS)
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.discovery import load_platform
|
from homeassistant.helpers.discovery import load_platform
|
||||||
from homeassistant.helpers.dispatcher import (
|
from homeassistant.helpers.dispatcher import (
|
||||||
async_dispatcher_connect, async_dispatcher_send)
|
async_dispatcher_connect, async_dispatcher_send)
|
||||||
from homeassistant.helpers.entity import Entity
|
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 (
|
from .const import DOMAIN, EVO_STRFTIME, STORAGE_VERSION, STORAGE_KEY, GWS, TCS
|
||||||
DOMAIN, DATA_EVOHOME, DISPATCHER_EVOHOME, GWS, TCS)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CONF_ACCESS_TOKEN_EXPIRES = 'access_token_expires'
|
||||||
|
CONF_REFRESH_TOKEN = 'refresh_token'
|
||||||
|
|
||||||
CONF_LOCATION_IDX = 'location_idx'
|
CONF_LOCATION_IDX = 'location_idx'
|
||||||
SCAN_INTERVAL_DEFAULT = timedelta(seconds=300)
|
SCAN_INTERVAL_DEFAULT = timedelta(seconds=300)
|
||||||
SCAN_INTERVAL_MINIMUM = timedelta(seconds=180)
|
SCAN_INTERVAL_MINIMUM = timedelta(seconds=60)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
DOMAIN: vol.Schema({
|
DOMAIN: vol.Schema({
|
||||||
@@ -44,229 +45,314 @@ CONFIG_SCHEMA = vol.Schema({
|
|||||||
}),
|
}),
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
CONF_SECRETS = [
|
|
||||||
CONF_USERNAME, CONF_PASSWORD,
|
|
||||||
]
|
|
||||||
|
|
||||||
# bit masks for dispatcher packets
|
def _local_dt_to_utc(dt_naive: datetime) -> datetime:
|
||||||
EVO_PARENT = 0x01
|
dt_aware = as_utc(dt_naive.replace(microsecond=0, tzinfo=tzlocal()))
|
||||||
EVO_CHILD = 0x02
|
return dt_aware.replace(tzinfo=None)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, hass_config):
|
def _handle_exception(err):
|
||||||
"""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)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = evo_data['client'] = evohomeclient2.EvohomeClient(
|
raise err
|
||||||
evo_data['params'][CONF_USERNAME],
|
|
||||||
evo_data['params'][CONF_PASSWORD],
|
|
||||||
debug=False
|
|
||||||
)
|
|
||||||
|
|
||||||
except evohomeclient2.AuthenticationError as err:
|
except evohomeclient2.AuthenticationError:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"setup(): Failed to authenticate with the vendor's server. "
|
"Failed to (re)authenticate with the vendor's server. "
|
||||||
"Check your username and password are correct. "
|
"Check that your username and password are correct. "
|
||||||
"Resolve any errors and restart HA. Message is: %s",
|
"Message is: %s",
|
||||||
err
|
err
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
_LOGGER.error(
|
# this appears to be common with Honeywell's servers
|
||||||
"setup(): Unable to connect with the vendor's server. "
|
_LOGGER.warning(
|
||||||
"Check your network and the vendor's status page. "
|
"Unable to connect with the vendor's server. "
|
||||||
"Resolve any errors and restart HA."
|
"Check your network and the vendor's status page."
|
||||||
|
"Message is: %s",
|
||||||
|
err
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
finally: # Redact any config data that's no longer needed
|
except requests.exceptions.HTTPError:
|
||||||
for parameter in CONF_SECRETS:
|
if err.response.status_code == HTTP_SERVICE_UNAVAILABLE:
|
||||||
evo_data['params'][parameter] = 'REDACTED' \
|
_LOGGER.warning(
|
||||||
if evo_data['params'][parameter] else None
|
"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
|
raise # we don't expect/handle any other HTTPErrors
|
||||||
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'
|
|
||||||
|
|
||||||
# Pull down the installation configuration
|
|
||||||
loc_idx = evo_data['params'][CONF_LOCATION_IDX]
|
|
||||||
try:
|
|
||||||
evo_data['config'] = client.installation_info[loc_idx]
|
|
||||||
|
|
||||||
except IndexError:
|
async def async_setup(hass, hass_config):
|
||||||
_LOGGER.error(
|
"""Create a (EMEA/EU-based) Honeywell evohome system."""
|
||||||
"setup(): config error, '%s' = %s, but its valid range is 0-%s. "
|
broker = EvoBroker(hass, hass_config[DOMAIN])
|
||||||
"Unable to continue. Fix any configuration errors and restart HA.",
|
if not await broker.init_client():
|
||||||
CONF_LOCATION_IDX, loc_idx, len(client.installation_info) - 1
|
|
||||||
)
|
|
||||||
return False
|
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)
|
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]:
|
async_track_time_interval(
|
||||||
_LOGGER.warning(
|
hass, broker.update, hass_config[DOMAIN][CONF_SCAN_INTERVAL]
|
||||||
"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)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class EvoDevice(Entity):
|
class EvoBroker:
|
||||||
"""Base for any Honeywell evohome device.
|
"""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.
|
(optionally) a DHW controller.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, evo_data, client, obj_ref):
|
def __init__(self, evo_broker, evo_device) -> None:
|
||||||
"""Initialize the evohome entity."""
|
"""Initialize the evohome entity."""
|
||||||
self._client = client
|
self._evo_device = evo_device
|
||||||
self._obj = obj_ref
|
self._evo_tcs = evo_broker.tcs
|
||||||
|
|
||||||
self._name = None
|
self._name = self._icon = self._precision = None
|
||||||
self._icon = None
|
self._state_attributes = []
|
||||||
self._type = None
|
|
||||||
|
|
||||||
self._supported_features = None
|
self._supported_features = None
|
||||||
self._operation_list = None
|
self._setpoints = None
|
||||||
|
|
||||||
self._params = evo_data['params']
|
|
||||||
self._timers = evo_data['timers']
|
|
||||||
self._status = {}
|
|
||||||
|
|
||||||
self._available = False # should become True after first update()
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _connect(self, packet):
|
def _refresh(self, packet):
|
||||||
if packet['to'] & self._type and packet['signal'] == 'refresh':
|
if packet['signal'] == 'refresh':
|
||||||
self.async_schedule_update_ha_state(force_refresh=True)
|
self.async_schedule_update_ha_state(force_refresh=True)
|
||||||
|
|
||||||
def _handle_exception(self, err):
|
def get_setpoints(self) -> Dict[str, Any]:
|
||||||
try:
|
"""Return the current/next scheduled switchpoints.
|
||||||
raise err
|
|
||||||
|
|
||||||
except evohomeclient2.AuthenticationError:
|
Only Zones & DHW controllers (but not the TCS) have schedules.
|
||||||
_LOGGER.error(
|
"""
|
||||||
"Failed to (re)authenticate with the vendor's server. "
|
switchpoints = {}
|
||||||
"This may be a temporary error. Message is: %s",
|
schedule = self._evo_device.schedule()
|
||||||
err
|
|
||||||
)
|
|
||||||
|
|
||||||
except requests.exceptions.ConnectionError:
|
day_time = datetime.now()
|
||||||
# this appears to be common with Honeywell's servers
|
day_of_week = int(day_time.strftime('%w')) # 0 is Sunday
|
||||||
_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
|
|
||||||
|
|
||||||
|
# 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:
|
else:
|
||||||
raise # we don't expect/handle any other HTTPErrors
|
break
|
||||||
|
|
||||||
# These properties, methods are from the Entity class
|
# Did the current SP start yesterday? Does the next start SP tomorrow?
|
||||||
async def async_added_to_hass(self):
|
current_sp_day = -1 if sp_idx == -1 else 0
|
||||||
"""Run when entity about to be added."""
|
next_sp_day = 1 if sp_idx + 1 == len(day['Switchpoints']) else 0
|
||||||
async_dispatcher_connect(self.hass, DISPATCHER_EVOHOME, self._connect)
|
|
||||||
|
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
|
@property
|
||||||
def should_poll(self) -> bool:
|
def should_poll(self) -> bool:
|
||||||
"""Most evohome devices push their state to HA.
|
"""Evohome entities should not be polled."""
|
||||||
|
|
||||||
Only the Controller should be polled.
|
|
||||||
"""
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the name to use in the frontend UI."""
|
"""Return the name of the Evohome entity."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self) -> Dict[str, Any]:
|
||||||
"""Return the device state attributes of the evohome device.
|
"""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
|
if 'setpoints' in self._state_attributes:
|
||||||
restrictions placed upon ClimateDevice properties, etc. by HA.
|
status['setpoints'] = self._setpoints
|
||||||
"""
|
|
||||||
return {'status': self._status}
|
return {'status': status}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self) -> str:
|
||||||
"""Return the icon to use in the frontend UI."""
|
"""Return the icon to use in the frontend UI."""
|
||||||
return self._icon
|
return self._icon
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def supported_features(self) -> int:
|
||||||
"""Return True if the device is currently available."""
|
"""Get the flag of supported features of the device."""
|
||||||
return self._available
|
|
||||||
|
|
||||||
@property
|
|
||||||
def supported_features(self):
|
|
||||||
"""Get the list of supported features of the device."""
|
|
||||||
return self._supported_features
|
return self._supported_features
|
||||||
|
|
||||||
# These properties are common to ClimateDevice, WaterHeaterDevice classes
|
async def async_added_to_hass(self) -> None:
|
||||||
@property
|
"""Run when entity about to be added to hass."""
|
||||||
def precision(self):
|
async_dispatcher_connect(self.hass, DOMAIN, self._refresh)
|
||||||
"""Return the temperature precision to use in the frontend UI."""
|
|
||||||
return PRECISION_HALVES
|
|
||||||
|
|
||||||
@property
|
@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 the temperature unit to use in the frontend UI."""
|
||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
@property
|
def update(self) -> None:
|
||||||
def operation_list(self):
|
"""Get the latest state data."""
|
||||||
"""Return the list of available operations."""
|
self._setpoints = self.get_setpoints()
|
||||||
return self._operation_list
|
|
||||||
|
|||||||
@@ -1,457 +1,331 @@
|
|||||||
"""Support for Climate devices of (EMEA/EU-based) Honeywell evohome systems."""
|
"""Support for Climate devices of (EMEA/EU-based) Honeywell TCC systems."""
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
import requests.exceptions
|
import requests.exceptions
|
||||||
|
|
||||||
import evohomeclient2
|
import evohomeclient2
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_ECO, STATE_MANUAL, SUPPORT_AWAY_MODE, SUPPORT_ON_OFF,
|
HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
PRESET_AWAY, PRESET_ECO, PRESET_HOME,
|
||||||
from homeassistant.const import (
|
SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE)
|
||||||
CONF_SCAN_INTERVAL, STATE_OFF,)
|
|
||||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
|
||||||
|
|
||||||
from . import (
|
from . import CONF_LOCATION_IDX, _handle_exception, EvoDevice
|
||||||
EvoDevice,
|
|
||||||
CONF_LOCATION_IDX, EVO_CHILD, EVO_PARENT)
|
|
||||||
from .const import (
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# The Controller's opmode/state and the zone's (inherited) state
|
PRESET_RESET = 'Reset' # reset all child zones to EVO_FOLLOW
|
||||||
EVO_RESET = 'AutoWithReset'
|
PRESET_CUSTOM = 'Custom'
|
||||||
EVO_AUTO = 'Auto'
|
|
||||||
EVO_AUTOECO = 'AutoWithEco'
|
|
||||||
EVO_AWAY = 'Away'
|
|
||||||
EVO_DAYOFF = 'DayOff'
|
|
||||||
EVO_CUSTOM = 'Custom'
|
|
||||||
EVO_HEATOFF = 'HeatingOff'
|
|
||||||
|
|
||||||
# These are for Zones' opmode, and state
|
HA_HVAC_TO_TCS = {
|
||||||
EVO_FOLLOW = 'FollowSchedule'
|
HVAC_MODE_OFF: EVO_HEATOFF,
|
||||||
EVO_TEMPOVER = 'TemporaryOverride'
|
HVAC_MODE_HEAT: EVO_AUTO,
|
||||||
EVO_PERMOVER = 'PermanentOverride'
|
}
|
||||||
|
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,
|
HA_PRESET_TO_EVO = {
|
||||||
# where HA considers it to 'override' the exising operating mode
|
'temporary': EVO_TEMPOVER,
|
||||||
TCS_STATE_TO_HA = {
|
'permanent': EVO_PERMOVER,
|
||||||
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_STATE_TO_TCS = {
|
EVO_PRESET_TO_HA = {v: k for k, v in HA_PRESET_TO_EVO.items()}
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, hass_config, async_add_entities,
|
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."""
|
"""Create the evohome Controller, and its Zones, if any."""
|
||||||
evo_data = hass.data[DATA_EVOHOME]
|
broker = hass.data[DOMAIN]['broker']
|
||||||
|
loc_idx = broker.params[CONF_LOCATION_IDX]
|
||||||
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
|
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Found Controller, id=%s [%s], name=%s (location_idx=%s)",
|
"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)
|
loc_idx)
|
||||||
|
|
||||||
controller = EvoController(evo_data, client, tcs_obj_ref)
|
controller = EvoController(broker, broker.tcs)
|
||||||
zones = []
|
|
||||||
|
|
||||||
for zone_idx in tcs_obj_ref.zones:
|
zones = []
|
||||||
zone_obj_ref = tcs_obj_ref.zones[zone_idx]
|
for zone_idx in broker.tcs.zones:
|
||||||
|
evo_zone = broker.tcs.zones[zone_idx]
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Found Zone, id=%s [%s], name=%s",
|
"Found Zone, id=%s [%s], name=%s",
|
||||||
zone_obj_ref.zoneId, zone_obj_ref.zone_type, zone_obj_ref.name)
|
evo_zone.zoneId, evo_zone.zone_type, evo_zone.name)
|
||||||
zones.append(EvoZone(evo_data, client, zone_obj_ref))
|
zones.append(EvoZone(broker, evo_zone))
|
||||||
|
|
||||||
entities = [controller] + zones
|
entities = [controller] + zones
|
||||||
|
|
||||||
async_add_entities(entities, update_before_add=False)
|
async_add_entities(entities, update_before_add=True)
|
||||||
|
|
||||||
|
|
||||||
class EvoZone(EvoDevice, ClimateDevice):
|
class EvoClimateDevice(EvoDevice, ClimateDevice):
|
||||||
"""Base for a Honeywell evohome Zone device."""
|
"""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."""
|
"""Initialize the evohome Zone."""
|
||||||
super().__init__(evo_data, client, obj_ref)
|
super().__init__(evo_broker, evo_device)
|
||||||
|
|
||||||
self._id = obj_ref.zoneId
|
self._id = evo_device.zoneId
|
||||||
self._name = obj_ref.name
|
self._name = evo_device.name
|
||||||
self._icon = "mdi:radiator"
|
self._icon = 'mdi:radiator'
|
||||||
self._type = EVO_CHILD
|
|
||||||
|
|
||||||
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:
|
if _zone['zoneId'] == self._id:
|
||||||
self._config = _zone
|
self._config = _zone
|
||||||
break
|
break
|
||||||
self._status = {}
|
|
||||||
|
|
||||||
self._operation_list = ZONE_OP_LIST
|
|
||||||
self._supported_features = \
|
|
||||||
SUPPORT_OPERATION_MODE | \
|
|
||||||
SUPPORT_TARGET_TEMPERATURE | \
|
|
||||||
SUPPORT_ON_OFF
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self) -> str:
|
||||||
"""Return the current operating mode of the evohome Zone.
|
"""Return the current operating mode of the evohome Zone.
|
||||||
|
|
||||||
The evohome Zones that are in 'FollowSchedule' mode inherit their
|
NB: evohome Zones 'inherit' their operating mode from the controller.
|
||||||
actual operating mode from the Controller.
|
|
||||||
"""
|
|
||||||
evo_data = self.hass.data[DATA_EVOHOME]
|
|
||||||
|
|
||||||
system_mode = evo_data['status']['systemModeStatus']['mode']
|
Usually, Zones are in 'FollowSchedule' mode, where their setpoints are
|
||||||
setpoint_mode = self._status['setpointStatus']['setpointMode']
|
a function of their schedule, and the Controller's operating_mode, e.g.
|
||||||
|
Economy mode is their scheduled setpoint less (usually) 3C.
|
||||||
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.
|
|
||||||
|
|
||||||
However, Zones can override these setpoints, either for a specified
|
However, Zones can override these setpoints, either for a specified
|
||||||
period of time, 'TemporaryOverride', after which they will revert back
|
period of time, 'TemporaryOverride', after which they will revert back
|
||||||
to 'FollowSchedule' mode, or indefinitely, 'PermanentOverride'.
|
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):
|
@property
|
||||||
"""Process the evohome Zone's state data."""
|
def current_temperature(self) -> Optional[float]:
|
||||||
evo_data = self.hass.data[DATA_EVOHOME]
|
"""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']:
|
@property
|
||||||
if _zone['zoneId'] == self._id:
|
def target_temperature(self) -> Optional[float]:
|
||||||
self._status = _zone
|
"""Return the target temperature of the evohome Zone."""
|
||||||
break
|
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):
|
class EvoController(EvoClimateDevice):
|
||||||
"""Base for a Honeywell evohome hub/Controller device.
|
"""Base for a Honeywell evohome Controller (hub).
|
||||||
|
|
||||||
The Controller (aka TCS, temperature control system) is the parent of all
|
The Controller (aka TCS, temperature control system) is the parent of all
|
||||||
the child (CH/DHW) devices. It is also a Climate device.
|
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)."""
|
"""Initialize the evohome Controller (hub)."""
|
||||||
super().__init__(evo_data, client, obj_ref)
|
super().__init__(evo_broker, evo_device)
|
||||||
|
|
||||||
self._id = obj_ref.systemId
|
self._id = evo_device.systemId
|
||||||
self._name = '_{}'.format(obj_ref.location.name)
|
self._name = evo_device.location.name
|
||||||
self._icon = "mdi:thermostat"
|
self._icon = 'mdi:thermostat'
|
||||||
self._type = EVO_PARENT
|
|
||||||
|
|
||||||
self._config = evo_data['config'][GWS][0][TCS][0]
|
self._precision = None
|
||||||
self._status = evo_data['status']
|
self._state_attributes = [
|
||||||
self._timers['statusUpdated'] = datetime.min
|
'activeFaults', 'systemModeStatus']
|
||||||
|
|
||||||
self._operation_list = TCS_OP_LIST
|
self._supported_features = SUPPORT_PRESET_MODE
|
||||||
self._supported_features = \
|
self._hvac_modes = list(HA_HVAC_TO_TCS)
|
||||||
SUPPORT_OPERATION_MODE | \
|
self._preset_modes = list(HA_PRESET_TO_TCS)
|
||||||
SUPPORT_AWAY_MODE
|
|
||||||
|
self._config = dict(evo_broker.config)
|
||||||
|
self._config['zones'] = '...'
|
||||||
|
if 'dhw' in self._config:
|
||||||
|
self._config['dhw'] = '...'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def hvac_mode(self) -> str:
|
||||||
"""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):
|
|
||||||
"""Return the current operating mode of the evohome Controller."""
|
"""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
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self) -> Optional[float]:
|
||||||
"""Return the average current temperature of the Heating/DHW zones.
|
"""Return the average current temperature of the heating Zones.
|
||||||
|
|
||||||
Although evohome Controllers do not have a target temp, one is
|
Controllers do not have a current temp, but one is expected by HA.
|
||||||
expected by the HA schema.
|
|
||||||
"""
|
"""
|
||||||
tmp_list = [x for x in self._status['zones']
|
temps = [z.temperatureStatus['temperature'] for z in
|
||||||
if x['temperatureStatus']['isAvailable']]
|
self._evo_device._zones if z.temperatureStatus['isAvailable']] # noqa: E501; pylint: disable=protected-access
|
||||||
temps = [zone['temperatureStatus']['temperature'] for zone in tmp_list]
|
return round(sum(temps) / len(temps), 1) if temps else None
|
||||||
|
|
||||||
avg_temp = round(sum(temps) / len(temps), 1) if temps else None
|
|
||||||
return avg_temp
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self) -> Optional[float]:
|
||||||
"""Return the average target temperature of the Heating/DHW zones.
|
"""Return the average target temperature of the heating Zones.
|
||||||
|
|
||||||
Although evohome Controllers do not have a target temp, one is
|
Controllers do not have a target temp, but one is expected by HA.
|
||||||
expected by the HA schema.
|
|
||||||
"""
|
"""
|
||||||
temps = [zone['setpointStatus']['targetHeatTemperature']
|
temps = [z.setpointStatus['targetHeatTemperature']
|
||||||
for zone in self._status['zones']]
|
for z in self._evo_device._zones] # noqa: E501; pylint: disable=protected-access
|
||||||
|
return round(sum(temps) / len(temps), 1) if temps else None
|
||||||
avg_temp = round(sum(temps) / len(temps), 1) if temps else None
|
|
||||||
return avg_temp
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_away_mode_on(self) -> bool:
|
def preset_mode(self) -> Optional[str]:
|
||||||
"""Return True if away mode is on."""
|
"""Return the current preset mode, e.g., home, away, temp."""
|
||||||
return self._status['systemModeStatus']['mode'] == EVO_AWAY
|
return TCS_PRESET_TO_HA.get(self._evo_device.systemModeStatus['mode'])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def min_temp(self) -> float:
|
||||||
"""Return True as evohome Controllers are always on.
|
"""Return the minimum target temperature of the heating Zones.
|
||||||
|
|
||||||
For example, evohome Controllers have a 'HeatingOff' mode, but even
|
Controllers do not have a min target temp, but one is required by HA.
|
||||||
then the DHW would remain on.
|
|
||||||
"""
|
"""
|
||||||
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
|
@property
|
||||||
def min_temp(self):
|
def max_temp(self) -> float:
|
||||||
"""Return the minimum target temperature of a evohome Controller.
|
"""Return the maximum target temperature of the heating Zones.
|
||||||
|
|
||||||
Although evohome Controllers do not have a minimum target temp, one is
|
Controllers do not have a max target temp, but one is required by HA.
|
||||||
expected by the HA schema; the default for an evohome HR92 is used.
|
|
||||||
"""
|
"""
|
||||||
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 _set_operation_mode(self, op_mode) -> None:
|
||||||
def max_temp(self):
|
"""Set the Controller to any of its native EVO_* operating modes."""
|
||||||
"""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):
|
|
||||||
try:
|
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,
|
except (requests.exceptions.RequestException,
|
||||||
evohomeclient2.AuthenticationError) as err:
|
evohomeclient2.AuthenticationError) as err:
|
||||||
self._handle_exception(err)
|
_handle_exception(err)
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode: str) -> None:
|
||||||
"""Set new target operation mode for the TCS.
|
"""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'
|
def set_preset_mode(self, preset_mode: str) -> None:
|
||||||
mode is needed, it can be enabled via turn_away_mode_on method.
|
"""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):
|
def update(self) -> None:
|
||||||
"""Turn away mode on.
|
"""Get the latest state data."""
|
||||||
|
pass
|
||||||
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)
|
|
||||||
|
|||||||
@@ -1,9 +1,25 @@
|
|||||||
"""Provides the constants needed for evohome."""
|
"""Support for (EMEA/EU-based) Honeywell TCC climate systems."""
|
||||||
|
|
||||||
DOMAIN = 'evohome'
|
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'
|
GWS = 'gateways'
|
||||||
TCS = 'temperatureControlSystems'
|
TCS = 'temperatureControlSystems'
|
||||||
|
|
||||||
|
EVO_STRFTIME = '%Y-%m-%dT%H:%M:%SZ'
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"name": "Evohome",
|
"name": "Evohome",
|
||||||
"documentation": "https://www.home-assistant.io/components/evohome",
|
"documentation": "https://www.home-assistant.io/components/evohome",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"evohomeclient==0.3.2"
|
"evohomeclient==0.3.3"
|
||||||
],
|
],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@zxdavb"]
|
"codeowners": ["@zxdavb"]
|
||||||
|
|||||||
@@ -1,90 +1,87 @@
|
|||||||
"""Support for Fibaro thermostats."""
|
"""Support for Fibaro thermostats."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_COOL, STATE_DRY,
|
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
|
||||||
STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
|
HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, SUPPORT_FAN_MODE,
|
||||||
STATE_MANUAL, SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE)
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
|
|
||||||
from homeassistant.components.climate import (
|
from . import FIBARO_DEVICES, FibaroDevice
|
||||||
ClimateDevice)
|
|
||||||
|
|
||||||
from homeassistant.const import (
|
PRESET_RESUME = 'resume'
|
||||||
ATTR_TEMPERATURE,
|
PRESET_MOIST = 'moist'
|
||||||
STATE_OFF,
|
PRESET_FURNACE = 'furnace'
|
||||||
TEMP_CELSIUS,
|
PRESET_CHANGEOVER = 'changeover'
|
||||||
TEMP_FAHRENHEIT)
|
PRESET_ECO_HEAT = 'eco_heat'
|
||||||
|
PRESET_ECO_COOL = 'eco_cool'
|
||||||
from . import (
|
PRESET_FORCE_OPEN = 'force_open'
|
||||||
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'
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
|
# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
|
||||||
# Table 128, Thermostat Fan Mode Set version 4::Fan Mode encoding
|
# Table 128, Thermostat Fan Mode Set version 4::Fan Mode encoding
|
||||||
FANMODES = {
|
FANMODES = {
|
||||||
0: STATE_OFF,
|
0: 'off',
|
||||||
1: SPEED_LOW,
|
1: 'low',
|
||||||
2: FAN_AUTO_HIGH,
|
2: 'auto_high',
|
||||||
3: SPEED_HIGH,
|
3: 'medium',
|
||||||
4: FAN_AUTO_MEDIUM,
|
4: 'auto_medium',
|
||||||
5: SPEED_MEDIUM,
|
5: 'high',
|
||||||
6: FAN_CIRCULATION,
|
6: 'circulation',
|
||||||
7: FAN_HUMIDITY_CIRCULATION,
|
7: 'humidity_circulation',
|
||||||
8: FAN_LEFT_RIGHT,
|
8: 'left_right',
|
||||||
9: FAN_UP_DOWN,
|
9: 'up_down',
|
||||||
10: FAN_QUIET,
|
10: 'quiet',
|
||||||
128: STATE_AUTO
|
128: 'auto'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HA_FANMODES = {v: k for k, v in FANMODES.items()}
|
||||||
|
|
||||||
# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
|
# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
|
||||||
# Table 130, Thermostat Mode Set version 3::Mode encoding.
|
# Table 130, Thermostat Mode Set version 3::Mode encoding.
|
||||||
OPMODES = {
|
# 4 AUXILARY
|
||||||
0: STATE_OFF,
|
OPMODES_PRESET = {
|
||||||
1: STATE_HEAT,
|
5: PRESET_RESUME,
|
||||||
2: STATE_COOL,
|
7: PRESET_FURNACE,
|
||||||
3: STATE_AUTO,
|
9: PRESET_MOIST,
|
||||||
4: STATE_AUXILIARY,
|
10: PRESET_CHANGEOVER,
|
||||||
5: STATE_RESUME,
|
11: PRESET_ECO_HEAT,
|
||||||
6: STATE_FAN_ONLY,
|
12: PRESET_ECO_COOL,
|
||||||
7: STATE_FURNACE,
|
13: PRESET_AWAY,
|
||||||
8: STATE_DRY,
|
15: PRESET_BOOST,
|
||||||
9: STATE_MOIST,
|
31: PRESET_FORCE_OPEN,
|
||||||
10: STATE_AUTO_CHANGEOVER,
|
|
||||||
11: STATE_ENERGY_HEAT,
|
|
||||||
12: STATE_ENERGY_COOL,
|
|
||||||
13: STATE_AWAY,
|
|
||||||
15: STATE_FULL_POWER,
|
|
||||||
31: STATE_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):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
@@ -109,10 +106,9 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||||||
self._fan_mode_device = None
|
self._fan_mode_device = None
|
||||||
self._support_flags = 0
|
self._support_flags = 0
|
||||||
self.entity_id = 'climate.{}'.format(self.ha_id)
|
self.entity_id = 'climate.{}'.format(self.ha_id)
|
||||||
self._fan_mode_to_state = {}
|
self._hvac_support = []
|
||||||
self._fan_state_to_mode = {}
|
self._preset_support = []
|
||||||
self._op_mode_to_state = {}
|
self._fan_support = []
|
||||||
self._op_state_to_mode = {}
|
|
||||||
|
|
||||||
siblings = fibaro_device.fibaro_controller.get_siblings(
|
siblings = fibaro_device.fibaro_controller.get_siblings(
|
||||||
fibaro_device.id)
|
fibaro_device.id)
|
||||||
@@ -129,7 +125,7 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||||||
if 'setMode' in device.actions or \
|
if 'setMode' in device.actions or \
|
||||||
'setOperatingMode' in device.actions:
|
'setOperatingMode' in device.actions:
|
||||||
self._op_mode_device = FibaroDevice(device)
|
self._op_mode_device = FibaroDevice(device)
|
||||||
self._support_flags |= SUPPORT_OPERATION_MODE
|
self._support_flags |= SUPPORT_PRESET_MODE
|
||||||
if 'setFanMode' in device.actions:
|
if 'setFanMode' in device.actions:
|
||||||
self._fan_mode_device = FibaroDevice(device)
|
self._fan_mode_device = FibaroDevice(device)
|
||||||
self._support_flags |= SUPPORT_FAN_MODE
|
self._support_flags |= SUPPORT_FAN_MODE
|
||||||
@@ -143,11 +139,11 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||||||
fan_modes = self._fan_mode_device.fibaro_device.\
|
fan_modes = self._fan_mode_device.fibaro_device.\
|
||||||
properties.supportedModes.split(",")
|
properties.supportedModes.split(",")
|
||||||
for mode in fan_modes:
|
for mode in fan_modes:
|
||||||
try:
|
mode = int(mode)
|
||||||
self._fan_mode_to_state[int(mode)] = FANMODES[int(mode)]
|
if mode not in FANMODES:
|
||||||
self._fan_state_to_mode[FANMODES[int(mode)]] = int(mode)
|
_LOGGER.warning("%d unknown fan mode", mode)
|
||||||
except KeyError:
|
continue
|
||||||
self._fan_mode_to_state[int(mode)] = 'unknown'
|
self._fan_support.append(FANMODES[int(mode)])
|
||||||
|
|
||||||
if self._op_mode_device:
|
if self._op_mode_device:
|
||||||
prop = self._op_mode_device.fibaro_device.properties
|
prop = self._op_mode_device.fibaro_device.properties
|
||||||
@@ -156,11 +152,13 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||||||
elif "supportedModes" in prop:
|
elif "supportedModes" in prop:
|
||||||
op_modes = prop.supportedModes.split(",")
|
op_modes = prop.supportedModes.split(",")
|
||||||
for mode in op_modes:
|
for mode in op_modes:
|
||||||
try:
|
mode = int(mode)
|
||||||
self._op_mode_to_state[int(mode)] = OPMODES[int(mode)]
|
if mode in OPMODES_HVAC:
|
||||||
self._op_state_to_mode[OPMODES[int(mode)]] = int(mode)
|
mode_ha = OPMODES_HVAC[mode]
|
||||||
except KeyError:
|
if mode_ha not in self._hvac_support:
|
||||||
self._op_mode_to_state[int(mode)] = 'unknown'
|
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):
|
async def async_added_to_hass(self):
|
||||||
"""Call when entity is added to hass."""
|
"""Call when entity is added to hass."""
|
||||||
@@ -194,32 +192,70 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||||||
return self._support_flags
|
return self._support_flags
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""Return the list of available fan modes."""
|
||||||
if self._fan_mode_device is None:
|
if not self._fan_mode_device:
|
||||||
return None
|
return None
|
||||||
return list(self._fan_state_to_mode)
|
return self._fan_support
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
if self._fan_mode_device is None:
|
if not self._fan_mode_device:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mode = int(self._fan_mode_device.fibaro_device.properties.mode)
|
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):
|
def set_fan_mode(self, fan_mode):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
if self._fan_mode_device is None:
|
if not self._fan_mode_device:
|
||||||
return
|
return
|
||||||
self._fan_mode_device.action(
|
self._fan_mode_device.action("setFanMode", HA_FANMODES[fan_mode])
|
||||||
"setFanMode", self._fan_state_to_mode[fan_mode])
|
|
||||||
|
|
||||||
@property
|
@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."""
|
"""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
|
return None
|
||||||
|
|
||||||
if "operatingMode" in self._op_mode_device.fibaro_device.properties:
|
if "operatingMode" in self._op_mode_device.fibaro_device.properties:
|
||||||
@@ -227,25 +263,31 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||||||
properties.operatingMode)
|
properties.operatingMode)
|
||||||
else:
|
else:
|
||||||
mode = int(self._op_mode_device.fibaro_device.properties.mode)
|
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
|
@property
|
||||||
def operation_list(self):
|
def preset_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return a list of available preset modes.
|
||||||
if self._op_mode_device is None:
|
|
||||||
return None
|
|
||||||
return list(self._op_state_to_mode)
|
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
Requires SUPPORT_PRESET_MODE.
|
||||||
"""Set new target operation 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:
|
if self._op_mode_device is None:
|
||||||
return
|
return
|
||||||
if "setOperatingMode" in self._op_mode_device.fibaro_device.actions:
|
if "setOperatingMode" in self._op_mode_device.fibaro_device.actions:
|
||||||
self._op_mode_device.action(
|
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:
|
elif "setMode" in self._op_mode_device.fibaro_device.actions:
|
||||||
self._op_mode_device.action(
|
self._op_mode_device.action(
|
||||||
"setMode", self._op_state_to_mode[operation_mode])
|
"setMode", HA_OPMODES_PRESET[preset_mode])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
@@ -275,15 +317,6 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
|
|||||||
if temperature is not None:
|
if temperature is not None:
|
||||||
if "setThermostatSetpoint" in target.fibaro_device.actions:
|
if "setThermostatSetpoint" in target.fibaro_device.actions:
|
||||||
target.action("setThermostatSetpoint",
|
target.action("setThermostatSetpoint",
|
||||||
self._op_state_to_mode[self.current_operation],
|
self.fibaro_op_mode, temperature)
|
||||||
temperature)
|
|
||||||
else:
|
else:
|
||||||
target.action("setTargetLevel",
|
target.action("setTargetLevel", temperature)
|
||||||
temperature)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_on(self):
|
|
||||||
"""Return true if on."""
|
|
||||||
if self.current_operation == STATE_OFF:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ For more details about this platform, please refer to the documentation
|
|||||||
https://home-assistant.io/components/climate.flexit/
|
https://home-assistant.io/components/climate.flexit/
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
from typing import List
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@@ -20,7 +21,7 @@ from homeassistant.const import (
|
|||||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_TARGET_TEMPERATURE,
|
||||||
SUPPORT_FAN_MODE)
|
SUPPORT_FAN_MODE, HVAC_MODE_COOL)
|
||||||
from homeassistant.components.modbus import (
|
from homeassistant.components.modbus import (
|
||||||
CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN)
|
CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@@ -57,7 +58,7 @@ class Flexit(ClimateDevice):
|
|||||||
self._current_temperature = None
|
self._current_temperature = None
|
||||||
self._current_fan_mode = None
|
self._current_fan_mode = None
|
||||||
self._current_operation = None
|
self._current_operation = None
|
||||||
self._fan_list = ['Off', 'Low', 'Medium', 'High']
|
self._fan_modes = ['Off', 'Low', 'Medium', 'High']
|
||||||
self._current_operation = None
|
self._current_operation = None
|
||||||
self._filter_hours = None
|
self._filter_hours = None
|
||||||
self._filter_alarm = None
|
self._filter_alarm = None
|
||||||
@@ -81,7 +82,7 @@ class Flexit(ClimateDevice):
|
|||||||
self._target_temperature = self.unit.get_target_temp
|
self._target_temperature = self.unit.get_target_temp
|
||||||
self._current_temperature = self.unit.get_temp
|
self._current_temperature = self.unit.get_temp
|
||||||
self._current_fan_mode =\
|
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
|
self._filter_hours = self.unit.get_filter_hours
|
||||||
# Mechanical heat recovery, 0-100%
|
# Mechanical heat recovery, 0-100%
|
||||||
self._heat_recovery = self.unit.get_heat_recovery
|
self._heat_recovery = self.unit.get_heat_recovery
|
||||||
@@ -134,19 +135,27 @@ class Flexit(ClimateDevice):
|
|||||||
return self._target_temperature
|
return self._target_temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return self._current_operation
|
return self._current_operation
|
||||||
|
|
||||||
@property
|
@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 the fan setting."""
|
||||||
return self._current_fan_mode
|
return self._current_fan_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""Return the list of available fan modes."""
|
||||||
return self._fan_list
|
return self._fan_modes
|
||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
@@ -156,4 +165,4 @@ class Flexit(ClimateDevice):
|
|||||||
|
|
||||||
def set_fan_mode(self, fan_mode):
|
def set_fan_mode(self, fan_mode):
|
||||||
"""Set new 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 import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_OPERATION_MODE, STATE_ECO, STATE_HEAT, STATE_MANUAL,
|
ATTR_HVAC_MODE, HVAC_MODE_HEAT, PRESET_ECO, PRESET_COMFORT,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, SUPPORT_PRESET_MODE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES, STATE_OFF,
|
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES,
|
||||||
STATE_ON, TEMP_CELSIUS)
|
TEMP_CELSIUS)
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
ATTR_STATE_BATTERY_LOW, ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_HOLIDAY_MODE,
|
ATTR_STATE_BATTERY_LOW, ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_HOLIDAY_MODE,
|
||||||
@@ -18,13 +18,15 @@ from . import (
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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
|
MIN_TEMPERATURE = 8
|
||||||
MAX_TEMPERATURE = 28
|
MAX_TEMPERATURE = 28
|
||||||
|
|
||||||
|
PRESET_MANUAL = 'manual'
|
||||||
|
|
||||||
# special temperatures for on/off in Fritz!Box API (modified by pyfritzhome)
|
# special temperatures for on/off in Fritz!Box API (modified by pyfritzhome)
|
||||||
ON_API_TEMPERATURE = 127.0
|
ON_API_TEMPERATURE = 127.0
|
||||||
OFF_API_TEMPERATURE = 126.5
|
OFF_API_TEMPERATURE = 126.5
|
||||||
@@ -98,41 +100,51 @@ class FritzboxThermostat(ClimateDevice):
|
|||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
if ATTR_OPERATION_MODE in kwargs:
|
if ATTR_HVAC_MODE in kwargs:
|
||||||
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
|
hvac_mode = kwargs.get(ATTR_HVAC_MODE)
|
||||||
self.set_operation_mode(operation_mode)
|
self.set_hvac_mode(hvac_mode)
|
||||||
elif ATTR_TEMPERATURE in kwargs:
|
elif ATTR_TEMPERATURE in kwargs:
|
||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
self._device.set_target_temperature(temperature)
|
self._device.set_target_temperature(temperature)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return the current operation mode."""
|
"""Return the current operation mode."""
|
||||||
if self._target_temperature == ON_API_TEMPERATURE:
|
if self._target_temperature == OFF_REPORT_SET_TEMPERATURE:
|
||||||
return STATE_ON
|
return HVAC_MODE_OFF
|
||||||
if self._target_temperature == OFF_API_TEMPERATURE:
|
|
||||||
return STATE_OFF
|
return HVAC_MODE_HEAT
|
||||||
if self._target_temperature == self._comfort_temperature:
|
|
||||||
return STATE_HEAT
|
|
||||||
if self._target_temperature == self._eco_temperature:
|
|
||||||
return STATE_ECO
|
|
||||||
return STATE_MANUAL
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return OPERATION_LIST
|
return OPERATION_LIST
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set new operation mode."""
|
"""Set new operation mode."""
|
||||||
if operation_mode == STATE_HEAT:
|
if hvac_mode == HVAC_MODE_OFF:
|
||||||
self.set_temperature(temperature=self._comfort_temperature)
|
|
||||||
elif operation_mode == STATE_ECO:
|
|
||||||
self.set_temperature(temperature=self._eco_temperature)
|
|
||||||
elif operation_mode == STATE_OFF:
|
|
||||||
self.set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE)
|
self.set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE)
|
||||||
elif operation_mode == STATE_ON:
|
else:
|
||||||
self.set_temperature(temperature=ON_REPORT_SET_TEMPERATURE)
|
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
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"name": "Home Assistant Frontend",
|
"name": "Home Assistant Frontend",
|
||||||
"documentation": "https://www.home-assistant.io/components/frontend",
|
"documentation": "https://www.home-assistant.io/components/frontend",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"home-assistant-frontend==20190702.0"
|
"home-assistant-frontend==20190705.0"
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"api",
|
"api",
|
||||||
|
|||||||
@@ -4,10 +4,15 @@ import logging
|
|||||||
|
|
||||||
import voluptuous as vol
|
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 (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_NAME, PRECISION_HALVES,
|
ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_NAME, EVENT_HOMEASSISTANT_START,
|
||||||
PRECISION_TENTHS, PRECISION_WHOLE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE, SERVICE_TURN_OFF,
|
||||||
STATE_OFF, STATE_ON, STATE_UNKNOWN)
|
SERVICE_TURN_ON, STATE_ON, STATE_UNKNOWN)
|
||||||
from homeassistant.core import DOMAIN as HA_DOMAIN, callback
|
from homeassistant.core import DOMAIN as HA_DOMAIN, callback
|
||||||
from homeassistant.helpers import condition
|
from homeassistant.helpers import condition
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@@ -15,12 +20,6 @@ from homeassistant.helpers.event import (
|
|||||||
async_track_state_change, async_track_time_interval)
|
async_track_state_change, async_track_time_interval)
|
||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_TOLERANCE = 0.3
|
DEFAULT_TOLERANCE = 0.3
|
||||||
@@ -36,11 +35,10 @@ CONF_MIN_DUR = 'min_cycle_duration'
|
|||||||
CONF_COLD_TOLERANCE = 'cold_tolerance'
|
CONF_COLD_TOLERANCE = 'cold_tolerance'
|
||||||
CONF_HOT_TOLERANCE = 'hot_tolerance'
|
CONF_HOT_TOLERANCE = 'hot_tolerance'
|
||||||
CONF_KEEP_ALIVE = 'keep_alive'
|
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_AWAY_TEMP = 'away_temp'
|
||||||
CONF_PRECISION = 'precision'
|
CONF_PRECISION = 'precision'
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
|
||||||
SUPPORT_OPERATION_MODE)
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_HEATER): cv.entity_id,
|
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_TARGET_TEMP): vol.Coerce(float),
|
||||||
vol.Optional(CONF_KEEP_ALIVE): vol.All(
|
vol.Optional(CONF_KEEP_ALIVE): vol.All(
|
||||||
cv.time_period, cv.positive_timedelta),
|
cv.time_period, cv.positive_timedelta),
|
||||||
vol.Optional(CONF_INITIAL_OPERATION_MODE):
|
vol.Optional(CONF_INITIAL_HVAC_MODE):
|
||||||
vol.In([STATE_AUTO, STATE_OFF]),
|
vol.In([HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF]),
|
||||||
vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float),
|
vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float),
|
||||||
vol.Optional(CONF_PRECISION): vol.In(
|
vol.Optional(CONF_PRECISION): vol.In(
|
||||||
[PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]),
|
[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)
|
cold_tolerance = config.get(CONF_COLD_TOLERANCE)
|
||||||
hot_tolerance = config.get(CONF_HOT_TOLERANCE)
|
hot_tolerance = config.get(CONF_HOT_TOLERANCE)
|
||||||
keep_alive = config.get(CONF_KEEP_ALIVE)
|
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)
|
away_temp = config.get(CONF_AWAY_TEMP)
|
||||||
precision = config.get(CONF_PRECISION)
|
precision = config.get(CONF_PRECISION)
|
||||||
|
unit = hass.config.units.temperature_unit
|
||||||
|
|
||||||
async_add_entities([GenericThermostat(
|
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,
|
target_temp, ac_mode, min_cycle_duration, cold_tolerance,
|
||||||
hot_tolerance, keep_alive, initial_operation_mode, away_temp,
|
hot_tolerance, keep_alive, initial_hvac_mode, away_temp,
|
||||||
precision)])
|
precision, unit)])
|
||||||
|
|
||||||
|
|
||||||
class GenericThermostat(ClimateDevice, RestoreEntity):
|
class GenericThermostat(ClimateDevice, RestoreEntity):
|
||||||
"""Representation of a Generic Thermostat device."""
|
"""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,
|
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
|
||||||
cold_tolerance, hot_tolerance, keep_alive,
|
cold_tolerance, hot_tolerance, keep_alive,
|
||||||
initial_operation_mode, away_temp, precision):
|
initial_hvac_mode, away_temp, precision, unit):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
self.hass = hass
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self.heater_entity_id = heater_entity_id
|
self.heater_entity_id = heater_entity_id
|
||||||
|
self.sensor_entity_id = sensor_entity_id
|
||||||
self.ac_mode = ac_mode
|
self.ac_mode = ac_mode
|
||||||
self.min_cycle_duration = min_cycle_duration
|
self.min_cycle_duration = min_cycle_duration
|
||||||
self._cold_tolerance = cold_tolerance
|
self._cold_tolerance = cold_tolerance
|
||||||
self._hot_tolerance = hot_tolerance
|
self._hot_tolerance = hot_tolerance
|
||||||
self._keep_alive = keep_alive
|
self._keep_alive = keep_alive
|
||||||
self._initial_operation_mode = initial_operation_mode
|
self._hvac_mode = initial_hvac_mode
|
||||||
self._saved_target_temp = target_temp if target_temp is not None \
|
self._saved_target_temp = target_temp or away_temp
|
||||||
else away_temp
|
|
||||||
self._temp_precision = precision
|
self._temp_precision = precision
|
||||||
if self.ac_mode:
|
if self.ac_mode:
|
||||||
self._current_operation = STATE_COOL
|
self._hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
|
||||||
self._operation_list = [STATE_COOL, STATE_OFF]
|
|
||||||
else:
|
else:
|
||||||
self._current_operation = STATE_HEAT
|
self._hvac_list = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||||
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._active = False
|
self._active = False
|
||||||
self._cur_temp = None
|
self._cur_temp = None
|
||||||
self._temp_lock = asyncio.Lock()
|
self._temp_lock = asyncio.Lock()
|
||||||
self._min_temp = min_temp
|
self._min_temp = min_temp
|
||||||
self._max_temp = max_temp
|
self._max_temp = max_temp
|
||||||
self._target_temp = target_temp
|
self._target_temp = target_temp
|
||||||
self._unit = hass.config.units.temperature_unit
|
self._unit = unit
|
||||||
self._support_flags = SUPPORT_FLAGS
|
self._support_flags = SUPPORT_FLAGS
|
||||||
if away_temp is not None:
|
if away_temp:
|
||||||
self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE
|
self._support_flags = SUPPORT_FLAGS | SUPPORT_PRESET_MODE
|
||||||
self._away_temp = away_temp
|
self._away_temp = away_temp
|
||||||
self._is_away = False
|
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):
|
async def async_added_to_hass(self):
|
||||||
"""Run when entity about to be added."""
|
"""Run when entity about to be added."""
|
||||||
await super().async_added_to_hass()
|
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
|
# Check If we have an old state
|
||||||
old_state = await self.async_get_last_state()
|
old_state = await self.async_get_last_state()
|
||||||
if old_state is not None:
|
if old_state is not None:
|
||||||
@@ -166,14 +165,10 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||||||
else:
|
else:
|
||||||
self._target_temp = float(
|
self._target_temp = float(
|
||||||
old_state.attributes[ATTR_TEMPERATURE])
|
old_state.attributes[ATTR_TEMPERATURE])
|
||||||
if old_state.attributes.get(ATTR_AWAY_MODE) is not None:
|
if old_state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY:
|
||||||
self._is_away = str(
|
self._is_away = True
|
||||||
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON
|
if not self._hvac_mode and old_state.state:
|
||||||
if (self._initial_operation_mode is None and
|
self._hvac_mode = old_state.state
|
||||||
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
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# No previous state, try and restore defaults
|
# No previous state, try and restore defaults
|
||||||
@@ -185,14 +180,9 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||||||
_LOGGER.warning("No previously saved temperature, setting to %s",
|
_LOGGER.warning("No previously saved temperature, setting to %s",
|
||||||
self._target_temp)
|
self._target_temp)
|
||||||
|
|
||||||
@property
|
# Set default state to off
|
||||||
def state(self):
|
if not self._hvac_mode:
|
||||||
"""Return the current state."""
|
self._hvac_mode = HVAC_MODE_OFF
|
||||||
if self._is_device_active:
|
|
||||||
return self.current_operation
|
|
||||||
if self._enabled:
|
|
||||||
return STATE_IDLE
|
|
||||||
return STATE_OFF
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
@@ -222,9 +212,23 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||||||
return self._cur_temp
|
return self._cur_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation."""
|
"""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
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
@@ -232,39 +236,42 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||||||
return self._target_temp
|
return self._target_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""List of available operation modes."""
|
"""List of available operation modes."""
|
||||||
return self._operation_list
|
return self._hvac_list
|
||||||
|
|
||||||
async def async_set_operation_mode(self, operation_mode):
|
@property
|
||||||
"""Set operation mode."""
|
def preset_mode(self):
|
||||||
if operation_mode == STATE_HEAT:
|
"""Return the current preset mode, e.g., home, away, temp."""
|
||||||
self._current_operation = STATE_HEAT
|
if self._is_away:
|
||||||
self._enabled = True
|
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)
|
await self._async_control_heating(force=True)
|
||||||
elif operation_mode == STATE_COOL:
|
elif hvac_mode == HVAC_MODE_COOL:
|
||||||
self._current_operation = STATE_COOL
|
self._hvac_mode = HVAC_MODE_COOL
|
||||||
self._enabled = True
|
|
||||||
await self._async_control_heating(force=True)
|
await self._async_control_heating(force=True)
|
||||||
elif operation_mode == STATE_OFF:
|
elif hvac_mode == HVAC_MODE_OFF:
|
||||||
self._current_operation = STATE_OFF
|
self._hvac_mode = HVAC_MODE_OFF
|
||||||
self._enabled = False
|
|
||||||
if self._is_device_active:
|
if self._is_device_active:
|
||||||
await self._async_heater_turn_off()
|
await self._async_heater_turn_off()
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("Unrecognized operation mode: %s", operation_mode)
|
_LOGGER.error("Unrecognized hvac mode: %s", hvac_mode)
|
||||||
return
|
return
|
||||||
# Ensure we update the current operation after changing the mode
|
# Ensure we update the current operation after changing the mode
|
||||||
self.schedule_update_ha_state()
|
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):
|
async def async_set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
@@ -326,7 +333,7 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||||||
"Generic thermostat active. %s, %s",
|
"Generic thermostat active. %s, %s",
|
||||||
self._cur_temp, self._target_temp)
|
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
|
return
|
||||||
|
|
||||||
if not force and time is None:
|
if not force and time is None:
|
||||||
@@ -338,7 +345,7 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||||||
if self._is_device_active:
|
if self._is_device_active:
|
||||||
current_state = STATE_ON
|
current_state = STATE_ON
|
||||||
else:
|
else:
|
||||||
current_state = STATE_OFF
|
current_state = HVAC_MODE_OFF
|
||||||
long_enough = condition.state(
|
long_enough = condition.state(
|
||||||
self.hass, self.heater_entity_id, current_state,
|
self.hass, self.heater_entity_id, current_state,
|
||||||
self.min_cycle_duration)
|
self.min_cycle_duration)
|
||||||
@@ -387,26 +394,19 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
|
|||||||
data = {ATTR_ENTITY_ID: self.heater_entity_id}
|
data = {ATTR_ENTITY_ID: self.heater_entity_id}
|
||||||
await self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_OFF, data)
|
await self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_OFF, data)
|
||||||
|
|
||||||
@property
|
async def async_set_preset_mode(self, preset_mode: str):
|
||||||
def is_away_mode_on(self):
|
"""Set new preset mode.
|
||||||
"""Return true if away mode is on."""
|
|
||||||
return self._is_away
|
|
||||||
|
|
||||||
async def async_turn_away_mode_on(self):
|
This method must be run in the event loop and returns a coroutine.
|
||||||
"""Turn away mode on by setting it on away hold indefinitely."""
|
"""
|
||||||
if self._is_away:
|
if preset_mode == PRESET_AWAY and not self._is_away:
|
||||||
return
|
self._is_away = True
|
||||||
self._is_away = True
|
self._saved_target_temp = self._target_temp
|
||||||
self._saved_target_temp = self._target_temp
|
self._target_temp = self._away_temp
|
||||||
self._target_temp = self._away_temp
|
await self._async_control_heating(force=True)
|
||||||
await self._async_control_heating(force=True)
|
elif not preset_mode and self._is_away:
|
||||||
await self.async_update_ha_state()
|
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()
|
await self.async_update_ha_state()
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
"""Support for Genius Hub climate devices."""
|
"""Support for Genius Hub climate devices."""
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any, Awaitable, Dict, Optional, List
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_ECO, STATE_HEAT, STATE_MANUAL,
|
HVAC_MODE_OFF, HVAC_MODE_HEAT, PRESET_BOOST, PRESET_ACTIVITY,
|
||||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_ON_OFF)
|
SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS)
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
@@ -14,36 +14,25 @@ from . import DOMAIN
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ATTR_DURATION = 'duration'
|
||||||
|
|
||||||
GH_ZONES = ['radiator']
|
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
|
# 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,
|
async def async_setup_platform(hass, hass_config, async_add_entities,
|
||||||
@@ -63,28 +52,26 @@ class GeniusClimateZone(ClimateDevice):
|
|||||||
self._client = client
|
self._client = client
|
||||||
self._zone = zone
|
self._zone = zone
|
||||||
|
|
||||||
# Only some zones have movement detectors, which allows footprint mode
|
if hasattr(self._zone, 'occupied'): # has a movement sensor
|
||||||
op_list = list(HA_OPMODE_TO_GH)
|
self._preset_modes = list(HA_PRESET_TO_GH)
|
||||||
if not hasattr(self._zone, 'occupied'):
|
else:
|
||||||
op_list.remove(STATE_ECO)
|
self._preset_modes = [PRESET_BOOST]
|
||||||
self._operation_list = op_list
|
|
||||||
self._supported_features = GH_SUPPORT_FLAGS
|
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self) -> Awaitable[None]:
|
||||||
"""Run when entity about to be added."""
|
"""Run when entity about to be added."""
|
||||||
async_dispatcher_connect(self.hass, DOMAIN, self._refresh)
|
async_dispatcher_connect(self.hass, DOMAIN, self._refresh)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _refresh(self):
|
def _refresh(self) -> None:
|
||||||
self.async_schedule_update_ha_state(force_refresh=True)
|
self.async_schedule_update_ha_state(force_refresh=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
"""Return the name of the climate device."""
|
"""Return the name of the climate device."""
|
||||||
return self._zone.name
|
return self._zone.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self) -> Dict[str, Any]:
|
||||||
"""Return the device state attributes."""
|
"""Return the device state attributes."""
|
||||||
tmp = self._zone.__dict__.items()
|
tmp = self._zone.__dict__.items()
|
||||||
return {'status': {k: v for k, v in tmp if k in GH_STATE_ATTRS}}
|
return {'status': {k: v for k, v in tmp if k in GH_STATE_ATTRS}}
|
||||||
@@ -95,72 +82,69 @@ class GeniusClimateZone(ClimateDevice):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self) -> str:
|
||||||
"""Return the icon to use in the frontend UI."""
|
"""Return the icon to use in the frontend UI."""
|
||||||
return "mdi:radiator"
|
return "mdi:radiator"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self) -> Optional[float]:
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
return self._zone.temperature
|
return self._zone.temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self) -> Optional[float]:
|
||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
return self._zone.setpoint
|
return self._zone.setpoint
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self) -> float:
|
||||||
"""Return max valid temperature that can be set."""
|
"""Return max valid temperature that can be set."""
|
||||||
return GH_MIN_TEMP
|
return 4.0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self):
|
def max_temp(self) -> float:
|
||||||
"""Return max valid temperature that can be set."""
|
"""Return max valid temperature that can be set."""
|
||||||
return GH_MAX_TEMP
|
return 28.0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self) -> str:
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self) -> int:
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
return self._supported_features
|
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_mode(self) -> str:
|
||||||
"""Return the list of available operation modes."""
|
"""Return hvac operation ie. heat, cool mode."""
|
||||||
return self._operation_list
|
return GH_HVAC_TO_HA.get(self._zone.mode, HVAC_MODE_HEAT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_modes(self) -> List[str]:
|
||||||
"""Return the current operation mode."""
|
"""Return the list of available hvac operation modes."""
|
||||||
return GH_STATE_TO_HA[self._zone.mode]
|
return list(HA_HVAC_TO_GH)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def preset_mode(self) -> Optional[str]:
|
||||||
"""Return True if the device is on."""
|
"""Return the current preset mode, e.g., home, away, temp."""
|
||||||
return self._zone.mode != HA_OPMODE_TO_GH[STATE_OFF]
|
return GH_PRESET_TO_HA.get(self._zone.mode)
|
||||||
|
|
||||||
async def async_set_operation_mode(self, operation_mode):
|
@property
|
||||||
"""Set a new operation mode for this zone."""
|
def preset_modes(self) -> Optional[List[str]]:
|
||||||
await self._zone.set_mode(HA_OPMODE_TO_GH[operation_mode])
|
"""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."""
|
"""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):
|
async def async_set_hvac_mode(self, hvac_mode: str) -> Awaitable[None]:
|
||||||
"""Turn on this heating zone.
|
"""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
|
async def async_set_preset_mode(self, preset_mode: str) -> Awaitable[None]:
|
||||||
mode otherwise.
|
"""Set a new preset mode."""
|
||||||
"""
|
await self._zone.set_mode(HA_PRESET_TO_GH.get(preset_mode, 'timer'))
|
||||||
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])
|
|
||||||
|
|||||||
@@ -537,26 +537,59 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
]
|
]
|
||||||
# We do not support "on" as we are unable to know how to restore
|
# We do not support "on" as we are unable to know how to restore
|
||||||
# the last mode.
|
# the last mode.
|
||||||
hass_to_google = {
|
hvac_to_google = {
|
||||||
climate.STATE_HEAT: 'heat',
|
climate.HVAC_MODE_HEAT: 'heat',
|
||||||
climate.STATE_COOL: 'cool',
|
climate.HVAC_MODE_COOL: 'cool',
|
||||||
STATE_OFF: 'off',
|
climate.HVAC_MODE_OFF: 'off',
|
||||||
climate.STATE_AUTO: 'heatcool',
|
climate.HVAC_MODE_AUTO: 'auto',
|
||||||
climate.STATE_FAN_ONLY: 'fan-only',
|
climate.HVAC_MODE_HEAT_COOL: 'heatcool',
|
||||||
climate.STATE_DRY: 'dry',
|
climate.HVAC_MODE_FAN_ONLY: 'fan-only',
|
||||||
climate.STATE_ECO: 'eco'
|
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
|
@staticmethod
|
||||||
def supported(domain, features, device_class):
|
def supported(domain, features, device_class):
|
||||||
"""Test if state is supported."""
|
"""Test if state is supported."""
|
||||||
if domain == climate.DOMAIN:
|
if domain == climate.DOMAIN:
|
||||||
return features & climate.SUPPORT_OPERATION_MODE
|
return True
|
||||||
|
|
||||||
return (domain == sensor.DOMAIN
|
return (domain == sensor.DOMAIN
|
||||||
and device_class == sensor.DEVICE_CLASS_TEMPERATURE)
|
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):
|
def sync_attributes(self):
|
||||||
"""Return temperature point and modes attributes for a sync request."""
|
"""Return temperature point and modes attributes for a sync request."""
|
||||||
response = {}
|
response = {}
|
||||||
@@ -571,18 +604,10 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
response["queryOnlyTemperatureSetting"] = True
|
response["queryOnlyTemperatureSetting"] = True
|
||||||
|
|
||||||
elif domain == climate.DOMAIN:
|
elif domain == climate.DOMAIN:
|
||||||
modes = []
|
modes = self.climate_google_modes
|
||||||
supported = attrs.get(ATTR_SUPPORTED_FEATURES)
|
on_mode = self.climate_on_mode
|
||||||
|
if on_mode is not None:
|
||||||
if supported & climate.SUPPORT_ON_OFF != 0:
|
modes.append('on')
|
||||||
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)
|
|
||||||
response['availableThermostatModes'] = ','.join(modes)
|
response['availableThermostatModes'] = ','.join(modes)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@@ -606,17 +631,14 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
), 1)
|
), 1)
|
||||||
|
|
||||||
elif domain == climate.DOMAIN:
|
elif domain == climate.DOMAIN:
|
||||||
operation = attrs.get(climate.ATTR_OPERATION_MODE)
|
operation = self.state.state
|
||||||
supported = attrs.get(ATTR_SUPPORTED_FEATURES)
|
preset = attrs.get(climate.ATTR_PRESET_MODE)
|
||||||
|
supported = attrs.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
|
|
||||||
if (supported & climate.SUPPORT_ON_OFF
|
if preset in self.preset_to_google:
|
||||||
and self.state.state == STATE_OFF):
|
response['thermostatMode'] = self.preset_to_google[preset]
|
||||||
response['thermostatMode'] = 'off'
|
else:
|
||||||
elif (supported & climate.SUPPORT_OPERATION_MODE
|
response['thermostatMode'] = self.hvac_to_google.get(operation)
|
||||||
and operation in self.hass_to_google):
|
|
||||||
response['thermostatMode'] = self.hass_to_google[operation]
|
|
||||||
elif supported & climate.SUPPORT_ON_OFF:
|
|
||||||
response['thermostatMode'] = 'on'
|
|
||||||
|
|
||||||
current_temp = attrs.get(climate.ATTR_CURRENT_TEMPERATURE)
|
current_temp = attrs.get(climate.ATTR_CURRENT_TEMPERATURE)
|
||||||
if current_temp is not None:
|
if current_temp is not None:
|
||||||
@@ -631,9 +653,9 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
if current_humidity is not None:
|
if current_humidity is not None:
|
||||||
response['thermostatHumidityAmbient'] = current_humidity
|
response['thermostatHumidityAmbient'] = current_humidity
|
||||||
|
|
||||||
if operation == climate.STATE_AUTO:
|
if operation in (climate.HVAC_MODE_AUTO,
|
||||||
if (supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH and
|
climate.HVAC_MODE_HEAT_COOL):
|
||||||
supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW):
|
if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE:
|
||||||
response['thermostatTemperatureSetpointHigh'] = \
|
response['thermostatTemperatureSetpointHigh'] = \
|
||||||
round(temp_util.convert(
|
round(temp_util.convert(
|
||||||
attrs[climate.ATTR_TARGET_TEMP_HIGH],
|
attrs[climate.ATTR_TARGET_TEMP_HIGH],
|
||||||
@@ -725,8 +747,7 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
if(supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH
|
if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE:
|
||||||
and supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW):
|
|
||||||
svc_data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high
|
svc_data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high
|
||||||
svc_data[climate.ATTR_TARGET_TEMP_LOW] = temp_low
|
svc_data[climate.ATTR_TARGET_TEMP_LOW] = temp_low
|
||||||
else:
|
else:
|
||||||
@@ -740,22 +761,40 @@ class TemperatureSettingTrait(_Trait):
|
|||||||
target_mode = params['thermostatMode']
|
target_mode = params['thermostatMode']
|
||||||
supported = self.state.attributes.get(ATTR_SUPPORTED_FEATURES)
|
supported = self.state.attributes.get(ATTR_SUPPORTED_FEATURES)
|
||||||
|
|
||||||
if (target_mode in [STATE_ON, STATE_OFF] and
|
if target_mode in self.google_to_preset:
|
||||||
supported & climate.SUPPORT_ON_OFF):
|
|
||||||
await self.hass.services.async_call(
|
await self.hass.services.async_call(
|
||||||
climate.DOMAIN,
|
climate.DOMAIN, climate.SERVICE_SET_PRESET_MODE,
|
||||||
(SERVICE_TURN_ON
|
{
|
||||||
if target_mode == STATE_ON
|
climate.ATTR_PRESET_MODE:
|
||||||
else SERVICE_TURN_OFF),
|
self.google_to_preset[target_mode],
|
||||||
{ATTR_ENTITY_ID: self.state.entity_id},
|
ATTR_ENTITY_ID: self.state.entity_id
|
||||||
blocking=True, context=data.context)
|
},
|
||||||
elif supported & climate.SUPPORT_OPERATION_MODE:
|
blocking=True, context=data.context
|
||||||
await self.hass.services.async_call(
|
)
|
||||||
climate.DOMAIN, climate.SERVICE_SET_OPERATION_MODE, {
|
return
|
||||||
ATTR_ENTITY_ID: self.state.entity_id,
|
|
||||||
climate.ATTR_OPERATION_MODE:
|
if target_mode == 'on':
|
||||||
self.google_to_hass[target_mode],
|
# When targetting 'on', we're going to try best effort.
|
||||||
}, blocking=True, context=data.context)
|
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
|
@register_trait
|
||||||
|
|||||||
@@ -39,11 +39,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
serport = connection.connection(ipaddress, port)
|
serport = connection.connection(ipaddress, port)
|
||||||
serport.open()
|
serport.open()
|
||||||
|
|
||||||
for tstat in tstats.values():
|
add_entities([
|
||||||
add_entities([
|
HeatmiserV3Thermostat(
|
||||||
HeatmiserV3Thermostat(
|
heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport)
|
||||||
heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport)
|
for tstat in tstats.values()], True)
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
class HeatmiserV3Thermostat(ClimateDevice):
|
class HeatmiserV3Thermostat(ClimateDevice):
|
||||||
@@ -54,11 +53,10 @@ class HeatmiserV3Thermostat(ClimateDevice):
|
|||||||
self.heatmiser = heatmiser
|
self.heatmiser = heatmiser
|
||||||
self.serport = serport
|
self.serport = serport
|
||||||
self._current_temperature = None
|
self._current_temperature = None
|
||||||
|
self._target_temperature = None
|
||||||
self._name = name
|
self._name = name
|
||||||
self._id = device
|
self._id = device
|
||||||
self.dcb = None
|
self.dcb = None
|
||||||
self.update()
|
|
||||||
self._target_temperature = int(self.dcb.get('roomset'))
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
@@ -78,13 +76,6 @@ class HeatmiserV3Thermostat(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
"""Return the current temperature."""
|
"""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
|
return self._current_temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -95,16 +86,17 @@ class HeatmiserV3Thermostat(ClimateDevice):
|
|||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
if temperature is None:
|
|
||||||
return
|
|
||||||
self.heatmiser.hmSendAddress(
|
self.heatmiser.hmSendAddress(
|
||||||
self._id,
|
self._id,
|
||||||
18,
|
18,
|
||||||
temperature,
|
temperature,
|
||||||
1,
|
1,
|
||||||
self.serport)
|
self.serport)
|
||||||
self._target_temperature = temperature
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Get the latest data."""
|
"""Get the latest data."""
|
||||||
self.dcb = self.heatmiser.hmReadAddress(self._id, 'prt', self.serport)
|
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."""
|
"""Support for the Hive devices."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from pyhiveapi import Pyhiveapi
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@@ -45,8 +46,6 @@ class HiveSession:
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Set up the Hive Component."""
|
"""Set up the Hive Component."""
|
||||||
from pyhiveapi import Pyhiveapi
|
|
||||||
|
|
||||||
session = HiveSession()
|
session = HiveSession()
|
||||||
session.core = Pyhiveapi()
|
session.core = Pyhiveapi()
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,41 @@
|
|||||||
"""Support for the Hive climate devices."""
|
"""Support for the Hive climate devices."""
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_HEAT, SUPPORT_AUX_HEAT, SUPPORT_OPERATION_MODE,
|
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_BOOST,
|
||||||
SUPPORT_TARGET_TEMPERATURE)
|
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS)
|
|
||||||
|
|
||||||
from . import DATA_HIVE, DOMAIN
|
from . import DATA_HIVE, DOMAIN
|
||||||
|
|
||||||
HIVE_TO_HASS_STATE = {
|
HIVE_TO_HASS_STATE = {
|
||||||
'SCHEDULE': STATE_AUTO,
|
'SCHEDULE': HVAC_MODE_AUTO,
|
||||||
'MANUAL': STATE_HEAT,
|
'MANUAL': HVAC_MODE_HEAT,
|
||||||
'ON': STATE_ON,
|
'OFF': HVAC_MODE_OFF,
|
||||||
'OFF': STATE_OFF,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HASS_TO_HIVE_STATE = {
|
HASS_TO_HIVE_STATE = {
|
||||||
STATE_AUTO: 'SCHEDULE',
|
HVAC_MODE_AUTO: 'SCHEDULE',
|
||||||
STATE_HEAT: 'MANUAL',
|
HVAC_MODE_HEAT: 'MANUAL',
|
||||||
STATE_ON: 'ON',
|
HVAC_MODE_OFF: 'OFF',
|
||||||
STATE_OFF: 'OFF',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||||
SUPPORT_OPERATION_MODE |
|
SUPPORT_HVAC = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||||
SUPPORT_AUX_HEAT)
|
SUPPORT_PRESET = [PRESET_BOOST]
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up Hive climate devices."""
|
"""Set up Hive climate devices."""
|
||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
return
|
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):
|
class HiveClimateEntity(ClimateDevice):
|
||||||
@@ -43,21 +45,11 @@ class HiveClimateEntity(ClimateDevice):
|
|||||||
"""Initialize the Climate device."""
|
"""Initialize the Climate device."""
|
||||||
self.node_id = hivedevice["Hive_NodeID"]
|
self.node_id = hivedevice["Hive_NodeID"]
|
||||||
self.node_name = hivedevice["Hive_NodeName"]
|
self.node_name = hivedevice["Hive_NodeName"]
|
||||||
self.device_type = hivedevice["HA_DeviceType"]
|
self.thermostat_node_id = hivedevice["Thermostat_NodeID"]
|
||||||
if self.device_type == "Heating":
|
|
||||||
self.thermostat_node_id = hivedevice["Thermostat_NodeID"]
|
|
||||||
self.session = hivesession
|
self.session = hivesession
|
||||||
self.attributes = {}
|
self.attributes = {}
|
||||||
self.data_updatesource = '{}.{}'.format(
|
self.data_updatesource = 'Heating.{}'.format(self.node_id)
|
||||||
self.device_type, self.node_id)
|
self._unique_id = '{}-Heating'.format(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)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
@@ -81,19 +73,15 @@ class HiveClimateEntity(ClimateDevice):
|
|||||||
|
|
||||||
def handle_update(self, updatesource):
|
def handle_update(self, updatesource):
|
||||||
"""Handle the new update request."""
|
"""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()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the Climate device."""
|
"""Return the name of the Climate device."""
|
||||||
friendly_name = "Climate Device"
|
friendly_name = "Heating"
|
||||||
if self.device_type == "Heating":
|
if self.node_name is not None:
|
||||||
friendly_name = "Heating"
|
friendly_name = '{} {}'.format(self.node_name, friendly_name)
|
||||||
if self.node_name is not None:
|
|
||||||
friendly_name = '{} {}'.format(self.node_name, friendly_name)
|
|
||||||
elif self.device_type == "HotWater":
|
|
||||||
friendly_name = "Hot Water"
|
|
||||||
return friendly_name
|
return friendly_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -101,6 +89,22 @@ class HiveClimateEntity(ClimateDevice):
|
|||||||
"""Show Device Attributes."""
|
"""Show Device Attributes."""
|
||||||
return self.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
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
@@ -109,48 +113,39 @@ class HiveClimateEntity(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
"""Return the current temperature."""
|
"""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
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
"""Return the target temperature."""
|
"""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
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
"""Return minimum temperature."""
|
"""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
|
@property
|
||||||
def max_temp(self):
|
def max_temp(self):
|
||||||
"""Return the maximum temperature."""
|
"""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
|
@property
|
||||||
def operation_list(self):
|
def preset_mode(self):
|
||||||
"""List of the operation modes."""
|
"""Return the current preset mode, e.g., home, away, temp."""
|
||||||
return self.modes
|
if self.session.heating.get_boost(self.node_id) == "ON":
|
||||||
|
return PRESET_BOOST
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def preset_modes(self):
|
||||||
"""Return current mode."""
|
"""Return a list of available preset modes."""
|
||||||
if self.device_type == "Heating":
|
return SUPPORT_PRESET
|
||||||
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 set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set new Heating mode."""
|
"""Set new target hvac mode."""
|
||||||
new_mode = HASS_TO_HIVE_STATE.get(operation_mode)
|
new_mode = HASS_TO_HIVE_STATE[hvac_mode]
|
||||||
if self.device_type == "Heating":
|
self.session.heating.set_mode(self.node_id, new_mode)
|
||||||
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)
|
|
||||||
|
|
||||||
for entity in self.session.entities:
|
for entity in self.session.entities:
|
||||||
entity.handle_update(self.data_updatesource)
|
entity.handle_update(self.data_updatesource)
|
||||||
@@ -159,55 +154,29 @@ class HiveClimateEntity(ClimateDevice):
|
|||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
new_temperature = kwargs.get(ATTR_TEMPERATURE)
|
new_temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
if new_temperature is not None:
|
if new_temperature is not None:
|
||||||
if self.device_type == "Heating":
|
self.session.heating.set_target_temperature(
|
||||||
self.session.heating.set_target_temperature(self.node_id,
|
self.node_id, new_temperature)
|
||||||
new_temperature)
|
|
||||||
|
|
||||||
for entity in self.session.entities:
|
for entity in self.session.entities:
|
||||||
entity.handle_update(self.data_updatesource)
|
entity.handle_update(self.data_updatesource)
|
||||||
|
|
||||||
@property
|
def set_preset_mode(self, preset_mode) -> None:
|
||||||
def is_aux_heat_on(self):
|
"""Set new preset mode."""
|
||||||
"""Return true if auxiliary heater is on."""
|
if preset_mode is None and self.preset_mode == PRESET_BOOST:
|
||||||
boost_status = None
|
self.session.heating.turn_boost_off(self.node_id)
|
||||||
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 turn_aux_heat_on(self):
|
elif preset_mode == PRESET_BOOST:
|
||||||
"""Turn auxiliary heater on."""
|
|
||||||
target_boost_time = 30
|
|
||||||
if self.device_type == "Heating":
|
|
||||||
curtemp = self.session.heating.current_temperature(self.node_id)
|
curtemp = self.session.heating.current_temperature(self.node_id)
|
||||||
curtemp = round(curtemp * 2) / 2
|
curtemp = round(curtemp * 2) / 2
|
||||||
target_boost_temperature = curtemp + 0.5
|
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)
|
|
||||||
|
|
||||||
for entity in self.session.entities:
|
self.session.heating.turn_boost_on(self.node_id, 30, temperature)
|
||||||
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)
|
|
||||||
|
|
||||||
for entity in self.session.entities:
|
for entity in self.session.entities:
|
||||||
entity.handle_update(self.data_updatesource)
|
entity.handle_update(self.data_updatesource)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update all Node data from Hive."""
|
"""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.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 pyhap.const import CATEGORY_THERMOSTAT
|
||||||
|
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_CURRENT_TEMPERATURE, ATTR_MAX_TEMP, ATTR_MIN_TEMP,
|
ATTR_CURRENT_TEMPERATURE, ATTR_HVAC_ACTIONS, ATTR_HVAC_MODE, ATTR_MAX_TEMP,
|
||||||
ATTR_OPERATION_LIST, ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH,
|
ATTR_MIN_TEMP, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||||
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_STEP,
|
ATTR_TARGET_TEMP_STEP, CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT,
|
||||||
DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP,
|
CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP,
|
||||||
DOMAIN as DOMAIN_CLIMATE,
|
DOMAIN as DOMAIN_CLIMATE, HVAC_MODE_COOL, HVAC_MODE_HEAT,
|
||||||
SERVICE_SET_OPERATION_MODE as SERVICE_SET_OPERATION_MODE_THERMOSTAT,
|
HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF,
|
||||||
SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT, STATE_AUTO,
|
SERVICE_SET_HVAC_MODE as SERVICE_SET_HVAC_MODE_THERMOSTAT,
|
||||||
STATE_COOL, STATE_HEAT, SUPPORT_ON_OFF, SUPPORT_TARGET_TEMPERATURE_HIGH,
|
SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT,
|
||||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||||
from homeassistant.components.water_heater import (
|
from homeassistant.components.water_heater import (
|
||||||
DOMAIN as DOMAIN_WATER_HEATER,
|
DOMAIN as DOMAIN_WATER_HEATER,
|
||||||
SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_WATER_HEATER)
|
SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_WATER_HEATER)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE,
|
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, TEMP_CELSIUS,
|
||||||
SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, TEMP_CELSIUS,
|
|
||||||
TEMP_FAHRENHEIT)
|
TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
from . import TYPES
|
from . import TYPES
|
||||||
@@ -36,12 +35,16 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
UNIT_HASS_TO_HOMEKIT = {TEMP_CELSIUS: 0, TEMP_FAHRENHEIT: 1}
|
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()}
|
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,
|
HC_HASS_TO_HOMEKIT = {HVAC_MODE_OFF: 0, HVAC_MODE_HEAT: 1,
|
||||||
STATE_COOL: 2, STATE_AUTO: 3}
|
HVAC_MODE_COOL: 2, HVAC_MODE_HEAT_COOL: 3}
|
||||||
HC_HOMEKIT_TO_HASS = {c: s for s, c in HC_HASS_TO_HOMEKIT.items()}
|
HC_HOMEKIT_TO_HASS = {c: s for s, c in HC_HASS_TO_HOMEKIT.items()}
|
||||||
|
|
||||||
SUPPORT_TEMP_RANGE = SUPPORT_TARGET_TEMPERATURE_LOW | \
|
HC_HASS_TO_HOMEKIT_ACTION = {
|
||||||
SUPPORT_TARGET_TEMPERATURE_HIGH
|
CURRENT_HVAC_OFF: 0,
|
||||||
|
CURRENT_HVAC_IDLE: 0,
|
||||||
|
CURRENT_HVAC_HEAT: 1,
|
||||||
|
CURRENT_HVAC_COOL: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@TYPES.register('Thermostat')
|
@TYPES.register('Thermostat')
|
||||||
@@ -56,7 +59,6 @@ class Thermostat(HomeAccessory):
|
|||||||
self._flag_temperature = False
|
self._flag_temperature = False
|
||||||
self._flag_coolingthresh = False
|
self._flag_coolingthresh = False
|
||||||
self._flag_heatingthresh = False
|
self._flag_heatingthresh = False
|
||||||
self.support_power_state = False
|
|
||||||
min_temp, max_temp = self.get_temperature_range()
|
min_temp, max_temp = self.get_temperature_range()
|
||||||
temp_step = self.hass.states.get(self.entity_id) \
|
temp_step = self.hass.states.get(self.entity_id) \
|
||||||
.attributes.get(ATTR_TARGET_TEMP_STEP, 0.5)
|
.attributes.get(ATTR_TARGET_TEMP_STEP, 0.5)
|
||||||
@@ -65,9 +67,7 @@ class Thermostat(HomeAccessory):
|
|||||||
self.chars = []
|
self.chars = []
|
||||||
features = self.hass.states.get(self.entity_id) \
|
features = self.hass.states.get(self.entity_id) \
|
||||||
.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
if features & SUPPORT_ON_OFF:
|
if features & SUPPORT_TARGET_TEMPERATURE_RANGE:
|
||||||
self.support_power_state = True
|
|
||||||
if features & SUPPORT_TEMP_RANGE:
|
|
||||||
self.chars.extend((CHAR_COOLING_THRESHOLD_TEMPERATURE,
|
self.chars.extend((CHAR_COOLING_THRESHOLD_TEMPERATURE,
|
||||||
CHAR_HEATING_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)
|
_LOGGER.debug('%s: Set heat-cool to %d', self.entity_id, value)
|
||||||
self._flag_heat_cool = True
|
self._flag_heat_cool = True
|
||||||
hass_value = HC_HOMEKIT_TO_HASS[value]
|
hass_value = HC_HOMEKIT_TO_HASS[value]
|
||||||
if self.support_power_state is True:
|
params = {
|
||||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
ATTR_ENTITY_ID: self.entity_id,
|
||||||
if hass_value == STATE_OFF:
|
ATTR_HVAC_MODE: hass_value
|
||||||
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}
|
|
||||||
self.call_service(
|
self.call_service(
|
||||||
DOMAIN_CLIMATE, SERVICE_SET_OPERATION_MODE_THERMOSTAT,
|
DOMAIN_CLIMATE, SERVICE_SET_HVAC_MODE_THERMOSTAT, params,
|
||||||
params, hass_value)
|
hass_value)
|
||||||
|
|
||||||
@debounce
|
@debounce
|
||||||
def set_cooling_threshold(self, value):
|
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])
|
self.char_display_units.set_value(UNIT_HASS_TO_HOMEKIT[self._unit])
|
||||||
|
|
||||||
# Update target operation mode
|
# Update target operation mode
|
||||||
operation_mode = new_state.attributes.get(ATTR_OPERATION_MODE)
|
hvac_mode = new_state.state
|
||||||
if self.support_power_state is True and new_state.state == STATE_OFF:
|
if hvac_mode and hvac_mode in HC_HASS_TO_HOMEKIT:
|
||||||
self.char_target_heat_cool.set_value(0) # Off
|
|
||||||
elif operation_mode and operation_mode in HC_HASS_TO_HOMEKIT:
|
|
||||||
if not self._flag_heat_cool:
|
if not self._flag_heat_cool:
|
||||||
self.char_target_heat_cool.set_value(
|
self.char_target_heat_cool.set_value(
|
||||||
HC_HASS_TO_HOMEKIT[operation_mode])
|
HC_HASS_TO_HOMEKIT[hvac_mode])
|
||||||
self._flag_heat_cool = False
|
self._flag_heat_cool = False
|
||||||
|
|
||||||
# Set current operation mode based on temperatures and target mode
|
# Set current operation mode for supported thermostats
|
||||||
if self.support_power_state is True and new_state.state == STATE_OFF:
|
hvac_action = new_state.attributes.get(ATTR_HVAC_ACTIONS)
|
||||||
current_operation_mode = STATE_OFF
|
if hvac_action:
|
||||||
elif operation_mode == STATE_HEAT:
|
self.char_current_heat_cool.set_value(
|
||||||
if isinstance(target_temp, float) and current_temp < target_temp:
|
HC_HASS_TO_HOMEKIT_ACTION[hvac_action])
|
||||||
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])
|
|
||||||
|
|
||||||
|
|
||||||
@TYPES.register('WaterHeater')
|
@TYPES.register('WaterHeater')
|
||||||
@@ -337,7 +295,7 @@ class WaterHeater(HomeAccessory):
|
|||||||
_LOGGER.debug('%s: Set heat-cool to %d', self.entity_id, value)
|
_LOGGER.debug('%s: Set heat-cool to %d', self.entity_id, value)
|
||||||
self._flag_heat_cool = True
|
self._flag_heat_cool = True
|
||||||
hass_value = HC_HOMEKIT_TO_HASS[value]
|
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
|
self.char_target_heat_cool.set_value(1) # Heat
|
||||||
|
|
||||||
@debounce
|
@debounce
|
||||||
@@ -370,7 +328,7 @@ class WaterHeater(HomeAccessory):
|
|||||||
self.char_display_units.set_value(UNIT_HASS_TO_HOMEKIT[self._unit])
|
self.char_display_units.set_value(UNIT_HASS_TO_HOMEKIT[self._unit])
|
||||||
|
|
||||||
# Update target operation mode
|
# 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:
|
if operation_mode and not self._flag_heat_cool:
|
||||||
self.char_target_heat_cool.set_value(1) # Heat
|
self.char_target_heat_cool.set_value(1) # Heat
|
||||||
self._flag_heat_cool = False
|
self._flag_heat_cool = False
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
"""Support for Homekit climate devices."""
|
"""Support for Homekit climate devices."""
|
||||||
import logging
|
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 (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_OPERATION_MODE,
|
HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY,
|
CURRENT_HVAC_OFF, CURRENT_HVAC_HEAT, CURRENT_HVAC_COOL,
|
||||||
SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW)
|
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY)
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
|
|
||||||
from . import KNOWN_DEVICES, HomeKitEntity
|
from . import KNOWN_DEVICES, HomeKitEntity
|
||||||
|
|
||||||
@@ -14,10 +16,10 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# Map of Homekit operation modes to hass modes
|
# Map of Homekit operation modes to hass modes
|
||||||
MODE_HOMEKIT_TO_HASS = {
|
MODE_HOMEKIT_TO_HASS = {
|
||||||
0: STATE_OFF,
|
0: HVAC_MODE_OFF,
|
||||||
1: STATE_HEAT,
|
1: HVAC_MODE_HEAT,
|
||||||
2: STATE_COOL,
|
2: HVAC_MODE_COOL,
|
||||||
3: STATE_AUTO,
|
3: HVAC_MODE_HEAT_COOL,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Map of hass operation modes to homekit modes
|
# 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)
|
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(
|
async def async_setup_platform(
|
||||||
hass, config, async_add_entities, discovery_info=None):
|
hass, config, async_add_entities, discovery_info=None):
|
||||||
@@ -53,6 +61,7 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Initialise the device."""
|
"""Initialise the device."""
|
||||||
self._state = None
|
self._state = None
|
||||||
|
self._target_mode = None
|
||||||
self._current_mode = None
|
self._current_mode = None
|
||||||
self._valid_modes = []
|
self._valid_modes = []
|
||||||
self._current_temp = None
|
self._current_temp = None
|
||||||
@@ -61,8 +70,8 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||||||
self._target_humidity = None
|
self._target_humidity = None
|
||||||
self._min_target_temp = None
|
self._min_target_temp = None
|
||||||
self._max_target_temp = None
|
self._max_target_temp = None
|
||||||
self._min_target_humidity = None
|
self._min_target_humidity = DEFAULT_MIN_HUMIDITY
|
||||||
self._max_target_humidity = None
|
self._max_target_humidity = DEFAULT_MAX_HUMIDITY
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
|
||||||
def get_characteristic_types(self):
|
def get_characteristic_types(self):
|
||||||
@@ -79,8 +88,6 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def _setup_heating_cooling_target(self, characteristic):
|
def _setup_heating_cooling_target(self, characteristic):
|
||||||
self._features |= SUPPORT_OPERATION_MODE
|
|
||||||
|
|
||||||
if 'valid-values' in characteristic:
|
if 'valid-values' in characteristic:
|
||||||
valid_values = [
|
valid_values = [
|
||||||
val for val in DEFAULT_VALID_MODES
|
val for val in DEFAULT_VALID_MODES
|
||||||
@@ -117,17 +124,22 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||||||
|
|
||||||
if 'minValue' in characteristic:
|
if 'minValue' in characteristic:
|
||||||
self._min_target_humidity = characteristic['minValue']
|
self._min_target_humidity = characteristic['minValue']
|
||||||
self._features |= SUPPORT_TARGET_HUMIDITY_LOW
|
|
||||||
|
|
||||||
if 'maxValue' in characteristic:
|
if 'maxValue' in characteristic:
|
||||||
self._max_target_humidity = characteristic['maxValue']
|
self._max_target_humidity = characteristic['maxValue']
|
||||||
self._features |= SUPPORT_TARGET_HUMIDITY_HIGH
|
|
||||||
|
|
||||||
def _update_heating_cooling_current(self, value):
|
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):
|
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):
|
def _update_temperature_current(self, value):
|
||||||
self._current_temp = value
|
self._current_temp = value
|
||||||
@@ -157,25 +169,13 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||||||
'value': humidity}]
|
'value': humidity}]
|
||||||
await self._accessory.put_characteristics(characteristics)
|
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."""
|
"""Set new target operation mode."""
|
||||||
characteristics = [{'aid': self._aid,
|
characteristics = [{'aid': self._aid,
|
||||||
'iid': self._chars['heating-cooling.target'],
|
'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)
|
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
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
@@ -221,13 +221,18 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||||||
return self._max_target_humidity
|
return self._max_target_humidity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_action(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return the current running hvac operation."""
|
||||||
return self._current_mode
|
return self._current_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_mode(self):
|
||||||
"""Return the list of available operation modes."""
|
"""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
|
return self._valid_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -3,26 +3,14 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_MANUAL, SUPPORT_OPERATION_MODE,
|
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_AUTO,
|
||||||
SUPPORT_TARGET_TEMPERATURE)
|
HVAC_MODE_HEAT, PRESET_BOOST, PRESET_COMFORT, PRESET_ECO)
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
|
|
||||||
from . import ATTR_DISCOVER_DEVICES, HM_ATTRIBUTE_SUPPORT, HMDevice
|
from . import ATTR_DISCOVER_DEVICES, HM_ATTRIBUTE_SUPPORT, HMDevice
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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 = [
|
HM_TEMP_MAP = [
|
||||||
'ACTUAL_TEMPERATURE',
|
'ACTUAL_TEMPERATURE',
|
||||||
'TEMPERATURE',
|
'TEMPERATURE',
|
||||||
@@ -33,10 +21,16 @@ HM_HUMI_MAP = [
|
|||||||
'HUMIDITY',
|
'HUMIDITY',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
HM_PRESET_MAP = {
|
||||||
|
"BOOST_MODE": PRESET_BOOST,
|
||||||
|
"COMFORT_MODE": PRESET_COMFORT,
|
||||||
|
"LOWERING_MODE": PRESET_ECO,
|
||||||
|
}
|
||||||
|
|
||||||
HM_CONTROL_MODE = 'CONTROL_MODE'
|
HM_CONTROL_MODE = 'CONTROL_MODE'
|
||||||
HMIP_CONTROL_MODE = 'SET_POINT_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):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
@@ -66,40 +60,54 @@ class HMThermostat(HMDevice, ClimateDevice):
|
|||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return hvac operation ie. heat, cool mode.
|
||||||
if HM_CONTROL_MODE not in self._data:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# boost mode is active
|
Need to be one of HVAC_MODE_*.
|
||||||
if self._data.get('BOOST_MODE', False):
|
"""
|
||||||
return STATE_BOOST
|
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
|
# Simple devices
|
||||||
# auto or manual
|
if self._data.get("BOOST_MODE"):
|
||||||
if HMIP_CONTROL_MODE in self._data:
|
return HVAC_MODE_AUTO
|
||||||
code = self._data[HMIP_CONTROL_MODE]
|
return HVAC_MODE_HEAT
|
||||||
# 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()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available hvac operation modes.
|
||||||
# HMIP use set_point_mode for operation
|
|
||||||
if HMIP_CONTROL_MODE in self._data:
|
|
||||||
return [STATE_MANUAL, STATE_AUTO, STATE_BOOST]
|
|
||||||
|
|
||||||
# HM
|
Need to be a subset of HVAC_MODES.
|
||||||
op_list = []
|
"""
|
||||||
|
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:
|
for mode in self._hmdevice.ACTIONNODE:
|
||||||
if mode in HM_STATE_MAP:
|
if mode in HM_PRESET_MAP:
|
||||||
op_list.append(HM_STATE_MAP.get(mode))
|
preset_modes.append(HM_PRESET_MAP[mode])
|
||||||
return op_list
|
return preset_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_humidity(self):
|
def current_humidity(self):
|
||||||
@@ -128,13 +136,21 @@ class HMThermostat(HMDevice, ClimateDevice):
|
|||||||
|
|
||||||
self._hmdevice.writeNodeData(self._state, float(temperature))
|
self._hmdevice.writeNodeData(self._state, float(temperature))
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set new target operation mode."""
|
"""Set new target hvac mode."""
|
||||||
for mode, state in HM_STATE_MAP.items():
|
if hvac_mode == HVAC_MODE_AUTO:
|
||||||
if state == operation_mode:
|
self._hmdevice.MODE = self._hmdevice.AUTO_MODE
|
||||||
code = getattr(self._hmdevice, mode, 0)
|
else:
|
||||||
self._hmdevice.MODE = code
|
self._hmdevice.MODE = self._hmdevice.MANU_MODE
|
||||||
return
|
|
||||||
|
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
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
@@ -146,6 +162,19 @@ class HMThermostat(HMDevice, ClimateDevice):
|
|||||||
"""Return the maximum temperature - 30.5 means on."""
|
"""Return the maximum temperature - 30.5 means on."""
|
||||||
return 30.5
|
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):
|
def _init_data_struct(self):
|
||||||
"""Generate a data dict (self._data) from the Homematic metadata."""
|
"""Generate a data dict (self._data) from the Homematic metadata."""
|
||||||
self._state = next(iter(self._hmdevice.WRITENODE.keys()))
|
self._state = next(iter(self._hmdevice.WRITENODE.keys()))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"""Support for HomematicIP Cloud climate devices."""
|
"""Support for HomematicIP Cloud climate devices."""
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Awaitable
|
||||||
|
|
||||||
from homematicip.aio.device import (
|
from homematicip.aio.device import (
|
||||||
AsyncHeatingThermostat, AsyncHeatingThermostatCompact)
|
AsyncHeatingThermostat, AsyncHeatingThermostatCompact)
|
||||||
@@ -8,7 +9,8 @@ from homematicip.aio.home import AsyncHome
|
|||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
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.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@@ -17,12 +19,9 @@ from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
HA_STATE_TO_HMIP = {
|
HMIP_AUTOMATIC_CM = 'AUTOMATIC'
|
||||||
STATE_AUTO: 'AUTOMATIC',
|
HMIP_MANUAL_CM = 'MANUAL'
|
||||||
STATE_MANUAL: 'MANUAL',
|
HMIP_ECO_CM = 'ECO'
|
||||||
}
|
|
||||||
|
|
||||||
HMIP_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_HMIP.items()}
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
@@ -63,7 +62,7 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
return SUPPORT_TARGET_TEMPERATURE
|
return SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self) -> float:
|
def target_temperature(self) -> float:
|
||||||
@@ -83,9 +82,48 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
|
|||||||
return self._device.humidity
|
return self._device.humidity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self) -> str:
|
def hvac_mode(self) -> str:
|
||||||
"""Return current operation ie. automatic or manual."""
|
"""Return hvac operation ie. heat, cool mode.
|
||||||
return HMIP_STATE_TO_HA.get(self._device.controlMode)
|
|
||||||
|
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
|
@property
|
||||||
def min_temp(self) -> float:
|
def min_temp(self) -> float:
|
||||||
@@ -104,6 +142,22 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
|
|||||||
return
|
return
|
||||||
await self._device.set_point_temperature(temperature)
|
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):
|
def _get_first_heating_thermostat(heating_group: AsyncHeatingGroup):
|
||||||
"""Return the first HeatingThermostat from a HeatingGroup."""
|
"""Return the first HeatingThermostat from a HeatingGroup."""
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/components/homematicip_cloud",
|
"documentation": "https://www.home-assistant.io/components/homematicip_cloud",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"homematicip==0.10.7"
|
"homematicip==0.10.9"
|
||||||
],
|
],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": []
|
"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."""
|
"""Support for Honeywell (US) Total Connect Comfort climate systems."""
|
||||||
import logging
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict, Optional, List
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import voluptuous as vol
|
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 import ClimateDevice, PLATFORM_SCHEMA
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_FAN_MODE, ATTR_FAN_LIST,
|
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||||
ATTR_OPERATION_MODE, ATTR_OPERATION_LIST, SUPPORT_TARGET_TEMPERATURE,
|
FAN_AUTO, FAN_DIFFUSE, FAN_ON,
|
||||||
SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE)
|
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 (
|
from homeassistant.const import (
|
||||||
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
||||||
ATTR_TEMPERATURE, CONF_REGION)
|
ATTR_TEMPERATURE, CONF_REGION)
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ATTR_FAN = 'fan'
|
ATTR_FAN_ACTION = 'fan_action'
|
||||||
ATTR_SYSTEM_MODE = 'system_mode'
|
|
||||||
ATTR_CURRENT_OPERATION = 'equipment_output_status'
|
|
||||||
|
|
||||||
CONF_AWAY_TEMPERATURE = 'away_temperature'
|
|
||||||
CONF_COOL_AWAY_TEMPERATURE = 'away_cool_temperature'
|
CONF_COOL_AWAY_TEMPERATURE = 'away_cool_temperature'
|
||||||
CONF_HEAT_AWAY_TEMPERATURE = 'away_heat_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_COOL_AWAY_TEMPERATURE = 88
|
||||||
DEFAULT_HEAT_AWAY_TEMPERATURE = 61
|
DEFAULT_HEAT_AWAY_TEMPERATURE = 61
|
||||||
DEFAULT_REGION = 'eu'
|
DEFAULT_REGION = 'eu'
|
||||||
@@ -34,8 +38,6 @@ REGIONS = ['eu', 'us']
|
|||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_USERNAME): cv.string,
|
vol.Required(CONF_USERNAME): cv.string,
|
||||||
vol.Required(CONF_PASSWORD): 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,
|
vol.Optional(CONF_COOL_AWAY_TEMPERATURE,
|
||||||
default=DEFAULT_COOL_AWAY_TEMPERATURE): vol.Coerce(int),
|
default=DEFAULT_COOL_AWAY_TEMPERATURE): vol.Coerce(int),
|
||||||
vol.Optional(CONF_HEAT_AWAY_TEMPERATURE,
|
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),
|
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):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up the Honeywell thermostat."""
|
"""Set up the Honeywell thermostat."""
|
||||||
username = config.get(CONF_USERNAME)
|
username = config.get(CONF_USERNAME)
|
||||||
password = config.get(CONF_PASSWORD)
|
password = config.get(CONF_PASSWORD)
|
||||||
region = config.get(CONF_REGION)
|
|
||||||
|
|
||||||
if region == 'us':
|
if config.get(CONF_REGION) == 'us':
|
||||||
return _setup_us(username, password, config, add_entities)
|
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(
|
_LOGGER.warning(
|
||||||
"The honeywell component is deprecated for EU (i.e. non-US) systems, "
|
"The honeywell component has been deprecated for EU (i.e. non-US) "
|
||||||
"this functionality will be removed in version 0.96. "
|
"systems. For EU-based systems, use the evohome component, "
|
||||||
"Please switch to the evohome component, "
|
|
||||||
"see: https://home-assistant.io/components/evohome")
|
"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):
|
class HoneywellUSThermostat(ClimateDevice):
|
||||||
"""Representation of a Honeywell US Thermostat."""
|
"""Representation of a Honeywell US Thermostat."""
|
||||||
@@ -243,61 +125,132 @@ class HoneywellUSThermostat(ClimateDevice):
|
|||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
|
|
||||||
@property
|
self._supported_features = (SUPPORT_PRESET_MODE |
|
||||||
def supported_features(self):
|
SUPPORT_TARGET_TEMPERATURE |
|
||||||
"""Return the list of supported features."""
|
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||||
supported = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE)
|
|
||||||
if hasattr(self._device, ATTR_SYSTEM_MODE):
|
# pylint: disable=protected-access
|
||||||
supported |= SUPPORT_OPERATION_MODE
|
_LOGGER.debug("uiData = %s ", device._data['uiData'])
|
||||||
return supported
|
|
||||||
|
# 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
|
@property
|
||||||
def is_fan_on(self):
|
def name(self) -> Optional[str]:
|
||||||
"""Return true if fan is on."""
|
|
||||||
return self._device.fan_running
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the honeywell, if any."""
|
"""Return the name of the honeywell, if any."""
|
||||||
return self._device.name
|
return self._device.name
|
||||||
|
|
||||||
@property
|
@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 the unit of measurement."""
|
||||||
return (TEMP_CELSIUS if self._device.temperature_unit == 'C'
|
return (TEMP_CELSIUS if self._device.temperature_unit == 'C'
|
||||||
else TEMP_FAHRENHEIT)
|
else TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_humidity(self) -> Optional[int]:
|
||||||
"""Return the current temperature."""
|
|
||||||
return self._device.current_temperature
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_humidity(self):
|
|
||||||
"""Return the current humidity."""
|
"""Return the current humidity."""
|
||||||
return self._device.current_humidity
|
return self._device.current_humidity
|
||||||
|
|
||||||
@property
|
@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."""
|
"""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
|
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
|
return self._device.setpoint_heat
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self) -> str:
|
def preset_mode(self) -> Optional[str]:
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return the current preset mode, e.g., home, away, temp."""
|
||||||
oper = getattr(self._device, ATTR_CURRENT_OPERATION, None)
|
return PRESET_AWAY if self._away else None
|
||||||
if oper == "off":
|
|
||||||
oper = "idle"
|
|
||||||
return oper
|
|
||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
@property
|
||||||
"""Set target temperature."""
|
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)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
if temperature is None:
|
if temperature is None:
|
||||||
return
|
return
|
||||||
import somecomfort
|
|
||||||
try:
|
try:
|
||||||
# Get current mode
|
# Get current mode
|
||||||
mode = self._device.system_mode
|
mode = self._device.system_mode
|
||||||
@@ -320,25 +273,31 @@ class HoneywellUSThermostat(ClimateDevice):
|
|||||||
except somecomfort.SomeComfortError:
|
except somecomfort.SomeComfortError:
|
||||||
_LOGGER.error("Temperature %.1f out of range", temperature)
|
_LOGGER.error("Temperature %.1f out of range", temperature)
|
||||||
|
|
||||||
@property
|
def set_temperature(self, **kwargs) -> None:
|
||||||
def device_state_attributes(self):
|
"""Set new target temperature."""
|
||||||
"""Return the device specific state attributes."""
|
if {HVAC_MODE_COOL, HVAC_MODE_HEAT} & set(self._hvac_mode_map):
|
||||||
import somecomfort
|
self._set_temperature(**kwargs)
|
||||||
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
|
|
||||||
|
|
||||||
@property
|
try:
|
||||||
def is_away_mode_on(self):
|
if HVAC_MODE_HEAT_COOL in self._hvac_mode_map:
|
||||||
"""Return true if away mode is on."""
|
temperature = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||||
return self._away
|
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.
|
"""Turn away on.
|
||||||
|
|
||||||
Somecomfort does have a proprietary away mode, but it doesn't really
|
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.
|
it doesn't get overwritten when away mode is switched on.
|
||||||
"""
|
"""
|
||||||
self._away = True
|
self._away = True
|
||||||
import somecomfort
|
|
||||||
try:
|
try:
|
||||||
# Get current mode
|
# Get current mode
|
||||||
mode = self._device.system_mode
|
mode = self._device.system_mode
|
||||||
@@ -367,10 +325,9 @@ class HoneywellUSThermostat(ClimateDevice):
|
|||||||
_LOGGER.error('Temperature %.1f out of range',
|
_LOGGER.error('Temperature %.1f out of range',
|
||||||
getattr(self, "_{}_away_temp".format(mode)))
|
getattr(self, "_{}_away_temp".format(mode)))
|
||||||
|
|
||||||
def turn_away_mode_off(self):
|
def _turn_away_mode_off(self) -> None:
|
||||||
"""Turn away off."""
|
"""Turn away off."""
|
||||||
self._away = False
|
self._away = False
|
||||||
import somecomfort
|
|
||||||
try:
|
try:
|
||||||
# Disabling all hold modes
|
# Disabling all hold modes
|
||||||
self._device.hold_cool = False
|
self._device.hold_cool = False
|
||||||
@@ -378,36 +335,27 @@ class HoneywellUSThermostat(ClimateDevice):
|
|||||||
except somecomfort.SomeComfortError:
|
except somecomfort.SomeComfortError:
|
||||||
_LOGGER.error('Can not stop hold mode')
|
_LOGGER.error('Can not stop hold mode')
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode: str) -> None:
|
def set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set the system mode (Cool, Heat, etc)."""
|
"""Set new preset mode."""
|
||||||
if hasattr(self._device, ATTR_SYSTEM_MODE):
|
if preset_mode == PRESET_AWAY:
|
||||||
self._device.system_mode = operation_mode
|
self._turn_away_mode_on()
|
||||||
|
else:
|
||||||
|
self._turn_away_mode_off()
|
||||||
|
|
||||||
def update(self):
|
def turn_aux_heat_on(self) -> None:
|
||||||
"""Update the state."""
|
"""Turn auxiliary heater on."""
|
||||||
import somecomfort
|
self._device.system_mode = 'emheat'
|
||||||
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 _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.
|
"""Recreate a new somecomfort client.
|
||||||
|
|
||||||
When we got an error, the best way to be sure that the next query
|
When we got an error, the best way to be sure that the next query
|
||||||
will succeed, is to recreate a new somecomfort client.
|
will succeed, is to recreate a new somecomfort client.
|
||||||
"""
|
"""
|
||||||
import somecomfort
|
|
||||||
try:
|
try:
|
||||||
self._client = somecomfort.SomeComfort(
|
self._client = somecomfort.SomeComfort(
|
||||||
self._username, self._password)
|
self._username, self._password)
|
||||||
@@ -431,3 +379,20 @@ class HoneywellUSThermostat(ClimateDevice):
|
|||||||
|
|
||||||
self._device = devices[0]
|
self._device = devices[0]
|
||||||
return True
|
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",
|
"name": "Honeywell",
|
||||||
"documentation": "https://www.home-assistant.io/components/honeywell",
|
"documentation": "https://www.home-assistant.io/components/honeywell",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"evohomeclient==0.3.2",
|
|
||||||
"somecomfort==0.5.2"
|
"somecomfort==0.5.2"
|
||||||
],
|
],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
"""Support for an Intergas boiler via an InComfort/InTouch Lan2RF gateway."""
|
"""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 import ClimateDevice
|
||||||
from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE
|
from homeassistant.components.climate.const import (
|
||||||
from homeassistant.const import (ATTR_TEMPERATURE, TEMP_CELSIUS)
|
HVAC_MODE_HEAT, SUPPORT_TARGET_TEMPERATURE)
|
||||||
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
from . import DOMAIN
|
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,
|
async def async_setup_platform(hass, hass_config, async_add_entities,
|
||||||
discovery_info=None):
|
discovery_info=None):
|
||||||
@@ -31,7 +29,7 @@ class InComfortClimate(ClimateDevice):
|
|||||||
self._room = room
|
self._room = room
|
||||||
self._name = 'Room {}'.format(room.room_no)
|
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."""
|
"""Set up a listener when this entity is added to HA."""
|
||||||
async_dispatcher_connect(self.hass, DOMAIN, self._refresh)
|
async_dispatcher_connect(self.hass, DOMAIN, self._refresh)
|
||||||
|
|
||||||
@@ -40,51 +38,65 @@ class InComfortClimate(ClimateDevice):
|
|||||||
self.async_schedule_update_ha_state(force_refresh=True)
|
self.async_schedule_update_ha_state(force_refresh=True)
|
||||||
|
|
||||||
@property
|
@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 the name of the climate device."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self) -> Dict[str, Any]:
|
||||||
"""Return the device state attributes."""
|
"""Return the device state attributes."""
|
||||||
return {'status': self._room.status}
|
return {'status': self._room.status}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def temperature_unit(self) -> str:
|
||||||
"""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):
|
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def hvac_mode(self) -> str:
|
||||||
"""Return the list of supported features."""
|
"""Return hvac operation ie. heat, cool mode."""
|
||||||
return INTOUCH_SUPPORT_FLAGS
|
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."""
|
"""Set a new target temperature for this zone."""
|
||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
await self._room.set_override(temperature)
|
await self._room.set_override(temperature)
|
||||||
|
|
||||||
@property
|
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
|
||||||
def should_poll(self) -> bool:
|
"""Set new target hvac mode."""
|
||||||
"""Return False as this device should never be polled."""
|
pass
|
||||||
return False
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
"""Support for KNX/IP climate devices."""
|
"""Support for KNX/IP climate devices."""
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_DRY, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, STATE_MANUAL,
|
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||||
SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
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.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@@ -41,19 +44,25 @@ DEFAULT_SETPOINT_SHIFT_MAX = 6
|
|||||||
DEFAULT_SETPOINT_SHIFT_MIN = -6
|
DEFAULT_SETPOINT_SHIFT_MIN = -6
|
||||||
# Map KNX operation modes to HA modes. This list might not be full.
|
# Map KNX operation modes to HA modes. This list might not be full.
|
||||||
OPERATION_MODES = {
|
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
|
# Map DPT 201.104 HVAC control modes
|
||||||
"Fan only": STATE_FAN_ONLY,
|
"Fan only": HVAC_MODE_FAN_ONLY,
|
||||||
"Dehumidification": STATE_DRY
|
"Dehumidification": HVAC_MODE_DRY
|
||||||
}
|
}
|
||||||
|
|
||||||
OPERATION_MODES_INV = dict((
|
OPERATION_MODES_INV = dict((
|
||||||
reversed(item) for item in OPERATION_MODES.items()))
|
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({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
|
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
|
||||||
@@ -167,16 +176,11 @@ class KNXClimate(ClimateDevice):
|
|||||||
self._unit_of_measurement = TEMP_CELSIUS
|
self._unit_of_measurement = TEMP_CELSIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self) -> int:
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
support = SUPPORT_TARGET_TEMPERATURE
|
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||||
if self.device.mode.supports_operation_mode:
|
|
||||||
support |= SUPPORT_OPERATION_MODE
|
|
||||||
if self.device.supports_on_off:
|
|
||||||
support |= SUPPORT_ON_OFF
|
|
||||||
return support
|
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Register callbacks to update hass after device was changed."""
|
"""Register callbacks to update hass after device was changed."""
|
||||||
async def after_update_callback(device):
|
async def after_update_callback(device):
|
||||||
"""Call after device was updated."""
|
"""Call after device was updated."""
|
||||||
@@ -184,17 +188,17 @@ class KNXClimate(ClimateDevice):
|
|||||||
self.device.register_device_updated_cb(after_update_callback)
|
self.device.register_device_updated_cb(after_update_callback)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
"""Return the name of the KNX device."""
|
"""Return the name of the KNX device."""
|
||||||
return self.device.name
|
return self.device.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return self.hass.data[DATA_KNX].connected
|
return self.hass.data[DATA_KNX].connected
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self) -> bool:
|
||||||
"""No polling needed within KNX."""
|
"""No polling needed within KNX."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -211,7 +215,7 @@ class KNXClimate(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def target_temperature_step(self):
|
def target_temperature_step(self):
|
||||||
"""Return the supported step of target temperature."""
|
"""Return the supported step of target temperature."""
|
||||||
return self.device.setpoint_shift_step
|
return self.device.temperature_step
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
@@ -228,7 +232,7 @@ class KNXClimate(ClimateDevice):
|
|||||||
"""Return the maximum temperature."""
|
"""Return the maximum temperature."""
|
||||||
return self.device.target_temperature_max
|
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."""
|
"""Set new target temperature."""
|
||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
if temperature is None:
|
if temperature is None:
|
||||||
@@ -237,39 +241,74 @@ class KNXClimate(ClimateDevice):
|
|||||||
await self.async_update_ha_state()
|
await self.async_update_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self) -> Optional[str]:
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""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:
|
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
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self) -> Optional[List[str]]:
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return [OPERATION_MODES.get(operation_mode.value) for
|
_operations = [OPERATION_MODES.get(operation_mode.value) for
|
||||||
operation_mode in
|
operation_mode in
|
||||||
self.device.mode.operation_modes]
|
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."""
|
"""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
|
from xknx.knx import HVACOperationMode
|
||||||
knx_operation_mode = 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.device.mode.set_operation_mode(knx_operation_mode)
|
||||||
await self.async_update_ha_state()
|
await self.async_update_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def preset_mode(self) -> Optional[str]:
|
||||||
"""Return true if the device is on."""
|
"""Return the current preset mode, e.g., home, away, temp.
|
||||||
if self.device.supports_on_off:
|
|
||||||
return self.device.is_on
|
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
|
return None
|
||||||
|
|
||||||
async def async_turn_on(self):
|
@property
|
||||||
"""Turn on."""
|
def preset_modes(self) -> Optional[List[str]]:
|
||||||
await self.device.turn_on()
|
"""Return a list of available preset modes.
|
||||||
|
|
||||||
async def async_turn_off(self):
|
Requires SUPPORT_PRESET_MODE.
|
||||||
"""Turn off."""
|
"""
|
||||||
await self.device.turn_off()
|
_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."""
|
"""Support for LCN climate control."""
|
||||||
|
|
||||||
import pypck
|
import pypck
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice, const
|
from homeassistant.components.climate import ClimateDevice, const
|
||||||
@@ -53,10 +54,6 @@ class LcnClimate(LcnDevice, ClimateDevice):
|
|||||||
self._target_temperature = None
|
self._target_temperature = None
|
||||||
self._is_on = 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):
|
async def async_added_to_hass(self):
|
||||||
"""Run when entity about to be added to hass."""
|
"""Run when entity about to be added to hass."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
@@ -68,7 +65,7 @@ class LcnClimate(LcnDevice, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
return self.support
|
return const.SUPPORT_TARGET_TEMPERATURE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
@@ -86,9 +83,25 @@ class LcnClimate(LcnDevice, ClimateDevice):
|
|||||||
return self._target_temperature
|
return self._target_temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def hvac_mode(self):
|
||||||
"""Return true if the device is on."""
|
"""Return hvac operation ie. heat, cool mode.
|
||||||
return self._is_on
|
|
||||||
|
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
|
@property
|
||||||
def max_temp(self):
|
def max_temp(self):
|
||||||
@@ -100,18 +113,17 @@ class LcnClimate(LcnDevice, ClimateDevice):
|
|||||||
"""Return the minimum temperature."""
|
"""Return the minimum temperature."""
|
||||||
return self._min_temp
|
return self._min_temp
|
||||||
|
|
||||||
async def async_turn_on(self):
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
"""Turn on."""
|
"""Set new target hvac mode."""
|
||||||
self._is_on = True
|
if hvac_mode == const.HVAC_MODE_HEAT:
|
||||||
self.address_connection.lock_regulator(self.regulator_id, False)
|
self._is_on = True
|
||||||
await self.async_update_ha_state()
|
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):
|
self.async_schedule_update_ha_state()
|
||||||
"""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()
|
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs):
|
async def async_set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
@@ -122,7 +134,7 @@ class LcnClimate(LcnDevice, ClimateDevice):
|
|||||||
self._target_temperature = temperature
|
self._target_temperature = temperature
|
||||||
self.address_connection.var_abs(
|
self.address_connection.var_abs(
|
||||||
self.setpoint, self._target_temperature, self.unit)
|
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):
|
def input_received(self, input_obj):
|
||||||
"""Set temperature value when LCN input object is received."""
|
"""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)
|
input_obj.get_value().to_var_unit(self.unit)
|
||||||
elif input_obj.get_var() == self.setpoint:
|
elif input_obj.get_var() == self.setpoint:
|
||||||
self._is_on = not input_obj.get_value().is_locked_regulator()
|
self._is_on = not input_obj.get_value().is_locked_regulator()
|
||||||
if self.is_on:
|
if self._is_on:
|
||||||
self._target_temperature = \
|
self._target_temperature = \
|
||||||
input_obj.get_value().to_var_unit(self.unit)
|
input_obj.get_value().to_var_unit(self.unit)
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,26 @@
|
|||||||
import logging
|
import logging
|
||||||
import socket
|
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 import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
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 homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
|
|
||||||
from . import DATA_KEY
|
from . import DATA_KEY
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
STATE_MANUAL = 'manual'
|
PRESET_MANUAL = 'manual'
|
||||||
STATE_BOOST = 'boost'
|
PRESET_BOOST = 'boost'
|
||||||
STATE_VACATION = 'vacation'
|
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):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
@@ -41,8 +47,7 @@ class MaxCubeClimate(ClimateDevice):
|
|||||||
def __init__(self, handler, name, rf_address):
|
def __init__(self, handler, name, rf_address):
|
||||||
"""Initialize MAX! Cube ClimateDevice."""
|
"""Initialize MAX! Cube ClimateDevice."""
|
||||||
self._name = name
|
self._name = name
|
||||||
self._operation_list = [STATE_AUTO, STATE_MANUAL, STATE_BOOST,
|
self._operation_list = [HVAC_MODE_AUTO]
|
||||||
STATE_VACATION]
|
|
||||||
self._rf_address = rf_address
|
self._rf_address = rf_address
|
||||||
self._cubehandle = handler
|
self._cubehandle = handler
|
||||||
|
|
||||||
@@ -87,13 +92,12 @@ class MaxCubeClimate(ClimateDevice):
|
|||||||
return self.map_temperature_max_hass(device.actual_temperature)
|
return self.map_temperature_max_hass(device.actual_temperature)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation (auto, manual, boost, vacation)."""
|
"""Return current operation (auto, manual, boost, vacation)."""
|
||||||
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
return HVAC_MODE_AUTO
|
||||||
return self.map_mode_max_hass(device.mode)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return self._operation_list
|
return self._operation_list
|
||||||
|
|
||||||
@@ -120,13 +124,25 @@ class MaxCubeClimate(ClimateDevice):
|
|||||||
_LOGGER.error("Setting target temperature failed")
|
_LOGGER.error("Setting target temperature failed")
|
||||||
return False
|
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."""
|
"""Set new operation mode."""
|
||||||
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||||
mode = self.map_mode_hass_max(operation_mode)
|
mode = self.map_mode_hass_max(preset_mode) or MAX_DEVICE_MODE_AUTOMATIC
|
||||||
|
|
||||||
if mode is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
with self._cubehandle.mutex:
|
with self._cubehandle.mutex:
|
||||||
try:
|
try:
|
||||||
@@ -148,21 +164,13 @@ class MaxCubeClimate(ClimateDevice):
|
|||||||
return temperature
|
return temperature
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def map_mode_hass_max(operation_mode):
|
def map_mode_hass_max(mode):
|
||||||
"""Map Home Assistant Operation Modes to MAX! Operation Modes."""
|
"""Map Home Assistant Operation Modes to MAX! Operation Modes."""
|
||||||
from maxcube.device import \
|
if mode == PRESET_MANUAL:
|
||||||
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:
|
|
||||||
mode = MAX_DEVICE_MODE_MANUAL
|
mode = MAX_DEVICE_MODE_MANUAL
|
||||||
elif operation_mode == STATE_VACATION:
|
elif mode == PRESET_VACATION:
|
||||||
mode = MAX_DEVICE_MODE_VACATION
|
mode = MAX_DEVICE_MODE_VACATION
|
||||||
elif operation_mode == STATE_BOOST:
|
elif mode == PRESET_BOOST:
|
||||||
mode = MAX_DEVICE_MODE_BOOST
|
mode = MAX_DEVICE_MODE_BOOST
|
||||||
else:
|
else:
|
||||||
mode = None
|
mode = None
|
||||||
@@ -172,20 +180,12 @@ class MaxCubeClimate(ClimateDevice):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def map_mode_max_hass(mode):
|
def map_mode_max_hass(mode):
|
||||||
"""Map MAX! Operation Modes to Home Assistant Operation Modes."""
|
"""Map MAX! Operation Modes to Home Assistant Operation Modes."""
|
||||||
from maxcube.device import \
|
if mode == MAX_DEVICE_MODE_MANUAL:
|
||||||
MAX_DEVICE_MODE_AUTOMATIC, \
|
operation_mode = PRESET_MANUAL
|
||||||
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
|
|
||||||
elif mode == MAX_DEVICE_MODE_VACATION:
|
elif mode == MAX_DEVICE_MODE_VACATION:
|
||||||
operation_mode = STATE_VACATION
|
operation_mode = PRESET_VACATION
|
||||||
elif mode == MAX_DEVICE_MODE_BOOST:
|
elif mode == MAX_DEVICE_MODE_BOOST:
|
||||||
operation_mode = STATE_BOOST
|
operation_mode = PRESET_BOOST
|
||||||
else:
|
else:
|
||||||
operation_mode = None
|
operation_mode = None
|
||||||
|
|
||||||
|
|||||||
@@ -3,27 +3,26 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT,
|
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
|
||||||
SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
|
HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
|
||||||
SUPPORT_TARGET_TEMPERATURE)
|
SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
|
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, PRECISION_WHOLE, STATE_IDLE, STATE_OFF, STATE_ON,
|
ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS)
|
||||||
TEMP_CELSIUS)
|
|
||||||
|
|
||||||
from . import DATA_MELISSA
|
from . import DATA_MELISSA
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_OPERATION_MODE |
|
SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_TARGET_TEMPERATURE)
|
||||||
SUPPORT_ON_OFF | SUPPORT_TARGET_TEMPERATURE)
|
|
||||||
|
|
||||||
OP_MODES = [
|
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 = [
|
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
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def fan_mode(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):
|
|
||||||
"""Return the current fan mode."""
|
"""Return the current fan mode."""
|
||||||
if self._cur_settings is not None:
|
if self._cur_settings is not None:
|
||||||
return self.melissa_fan_to_hass(
|
return self.melissa_fan_to_hass(
|
||||||
@@ -93,19 +84,26 @@ class MelissaClimate(ClimateDevice):
|
|||||||
return PRECISION_WHOLE
|
return PRECISION_WHOLE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return the current operation mode."""
|
"""Return the current operation mode."""
|
||||||
if self._cur_settings is not None:
|
if self._cur_settings is None:
|
||||||
return self.melissa_op_to_hass(
|
return None
|
||||||
self._cur_settings[self._api.MODE])
|
|
||||||
|
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
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return OP_MODES
|
return OP_MODES
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""List of available fan modes."""
|
"""List of available fan modes."""
|
||||||
return FAN_MODES
|
return FAN_MODES
|
||||||
|
|
||||||
@@ -116,13 +114,6 @@ class MelissaClimate(ClimateDevice):
|
|||||||
return None
|
return None
|
||||||
return self._cur_settings[self._api.TEMP]
|
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
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
"""Return the unit of measurement which this thermostat uses."""
|
"""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)
|
melissa_fan_mode = self.hass_fan_to_melissa(fan_mode)
|
||||||
await self.async_send({self._api.FAN: 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."""
|
"""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})
|
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):
|
async def async_send(self, value):
|
||||||
"""Send action to service."""
|
"""Send action to service."""
|
||||||
try:
|
try:
|
||||||
@@ -189,26 +176,16 @@ class MelissaClimate(ClimateDevice):
|
|||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
'Unable to update entity %s', self.entity_id)
|
'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):
|
def melissa_op_to_hass(self, mode):
|
||||||
"""Translate Melissa modes to hass states."""
|
"""Translate Melissa modes to hass states."""
|
||||||
if mode == self._api.MODE_HEAT:
|
if mode == self._api.MODE_HEAT:
|
||||||
return STATE_HEAT
|
return HVAC_MODE_HEAT
|
||||||
if mode == self._api.MODE_COOL:
|
if mode == self._api.MODE_COOL:
|
||||||
return STATE_COOL
|
return HVAC_MODE_COOL
|
||||||
if mode == self._api.MODE_DRY:
|
if mode == self._api.MODE_DRY:
|
||||||
return STATE_DRY
|
return HVAC_MODE_DRY
|
||||||
if mode == self._api.MODE_FAN:
|
if mode == self._api.MODE_FAN:
|
||||||
return STATE_FAN_ONLY
|
return HVAC_MODE_FAN_ONLY
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Operation mode %s could not be mapped to hass", mode)
|
"Operation mode %s could not be mapped to hass", mode)
|
||||||
return None
|
return None
|
||||||
@@ -216,7 +193,7 @@ class MelissaClimate(ClimateDevice):
|
|||||||
def melissa_fan_to_hass(self, fan):
|
def melissa_fan_to_hass(self, fan):
|
||||||
"""Translate Melissa fan modes to hass modes."""
|
"""Translate Melissa fan modes to hass modes."""
|
||||||
if fan == self._api.FAN_AUTO:
|
if fan == self._api.FAN_AUTO:
|
||||||
return STATE_AUTO
|
return HVAC_MODE_AUTO
|
||||||
if fan == self._api.FAN_LOW:
|
if fan == self._api.FAN_LOW:
|
||||||
return SPEED_LOW
|
return SPEED_LOW
|
||||||
if fan == self._api.FAN_MEDIUM:
|
if fan == self._api.FAN_MEDIUM:
|
||||||
@@ -228,19 +205,19 @@ class MelissaClimate(ClimateDevice):
|
|||||||
|
|
||||||
def hass_mode_to_melissa(self, mode):
|
def hass_mode_to_melissa(self, mode):
|
||||||
"""Translate hass states to melissa modes."""
|
"""Translate hass states to melissa modes."""
|
||||||
if mode == STATE_HEAT:
|
if mode == HVAC_MODE_HEAT:
|
||||||
return self._api.MODE_HEAT
|
return self._api.MODE_HEAT
|
||||||
if mode == STATE_COOL:
|
if mode == HVAC_MODE_COOL:
|
||||||
return self._api.MODE_COOL
|
return self._api.MODE_COOL
|
||||||
if mode == STATE_DRY:
|
if mode == HVAC_MODE_DRY:
|
||||||
return self._api.MODE_DRY
|
return self._api.MODE_DRY
|
||||||
if mode == STATE_FAN_ONLY:
|
if mode == HVAC_MODE_FAN_ONLY:
|
||||||
return self._api.MODE_FAN
|
return self._api.MODE_FAN
|
||||||
_LOGGER.warning("Melissa have no setting for %s mode", mode)
|
_LOGGER.warning("Melissa have no setting for %s mode", mode)
|
||||||
|
|
||||||
def hass_fan_to_melissa(self, fan):
|
def hass_fan_to_melissa(self, fan):
|
||||||
"""Translate hass fan modes to melissa modes."""
|
"""Translate hass fan modes to melissa modes."""
|
||||||
if fan == STATE_AUTO:
|
if fan == HVAC_MODE_AUTO:
|
||||||
return self._api.FAN_AUTO
|
return self._api.FAN_AUTO
|
||||||
if fan == SPEED_LOW:
|
if fan == SPEED_LOW:
|
||||||
return self._api.FAN_LOW
|
return self._api.FAN_LOW
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
"""Support for mill wifi-enabled home heaters."""
|
"""Support for mill wifi-enabled home heaters."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from mill import Mill
|
||||||
import voluptuous as vol
|
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 (
|
from homeassistant.components.climate.const import (
|
||||||
DOMAIN, STATE_HEAT,
|
DOMAIN, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
|
||||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
|
SUPPORT_TARGET_TEMPERATURE, FAN_ON)
|
||||||
SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE)
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME,
|
ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS)
|
||||||
STATE_ON, STATE_OFF, TEMP_CELSIUS)
|
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
@@ -25,8 +23,7 @@ MAX_TEMP = 35
|
|||||||
MIN_TEMP = 5
|
MIN_TEMP = 5
|
||||||
SERVICE_SET_ROOM_TEMP = 'mill_set_room_temperature'
|
SERVICE_SET_ROOM_TEMP = 'mill_set_room_temperature'
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
|
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||||
SUPPORT_FAN_MODE)
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_USERNAME): cv.string,
|
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,
|
async def async_setup_platform(hass, config, async_add_entities,
|
||||||
discovery_info=None):
|
discovery_info=None):
|
||||||
"""Set up the Mill heater."""
|
"""Set up the Mill heater."""
|
||||||
from mill import Mill
|
|
||||||
mill_data_connection = Mill(config[CONF_USERNAME],
|
mill_data_connection = Mill(config[CONF_USERNAME],
|
||||||
config[CONF_PASSWORD],
|
config[CONF_PASSWORD],
|
||||||
websession=async_get_clientsession(hass))
|
websession=async_get_clientsession(hass))
|
||||||
@@ -85,9 +81,7 @@ class MillHeater(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
if self._heater.is_gen1:
|
return SUPPORT_FLAGS
|
||||||
return SUPPORT_FLAGS
|
|
||||||
return SUPPORT_FLAGS | SUPPORT_ON_OFF | SUPPORT_OPERATION_MODE
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
@@ -141,21 +135,14 @@ class MillHeater(ClimateDevice):
|
|||||||
return self._heater.current_temp
|
return self._heater.current_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""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
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""List of available fan modes."""
|
"""List of available fan modes."""
|
||||||
return [STATE_ON, STATE_OFF]
|
return [FAN_ON, HVAC_MODE_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
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
@@ -168,50 +155,48 @@ class MillHeater(ClimateDevice):
|
|||||||
return MAX_TEMP
|
return MAX_TEMP
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self) -> str:
|
||||||
"""Return current operation."""
|
"""Return hvac operation ie. heat, cool mode.
|
||||||
return STATE_HEAT if self.is_on else STATE_OFF
|
|
||||||
|
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
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""List of available operation modes."""
|
"""Return the list of available hvac operation modes.
|
||||||
|
|
||||||
|
Need to be a subset of HVAC_MODES.
|
||||||
|
"""
|
||||||
if self._heater.is_gen1:
|
if self._heater.is_gen1:
|
||||||
return None
|
return [HVAC_MODE_HEAT]
|
||||||
return [STATE_HEAT, STATE_OFF]
|
return [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs):
|
async def async_set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
if temperature is None:
|
if temperature is None:
|
||||||
return
|
return
|
||||||
await self._conn.set_heater_temp(self._heater.device_id,
|
await self._conn.set_heater_temp(
|
||||||
int(temperature))
|
self._heater.device_id, int(temperature))
|
||||||
|
|
||||||
async def async_set_fan_mode(self, fan_mode):
|
async def async_set_fan_mode(self, fan_mode):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
fan_status = 1 if fan_mode == STATE_ON else 0
|
fan_status = 1 if fan_mode == FAN_ON else 0
|
||||||
await self._conn.heater_control(self._heater.device_id,
|
await self._conn.heater_control(
|
||||||
fan_status=fan_status)
|
self._heater.device_id, fan_status=fan_status)
|
||||||
|
|
||||||
async def async_turn_on(self):
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
"""Turn Mill unit on."""
|
"""Set new target hvac mode."""
|
||||||
await self._conn.heater_control(self._heater.device_id,
|
if hvac_mode == HVAC_MODE_HEAT:
|
||||||
power_status=1)
|
await self._conn.heater_control(
|
||||||
|
self._heater.device_id, power_status=1)
|
||||||
async def async_turn_off(self):
|
elif hvac_mode == HVAC_MODE_OFF and not self._heater.is_gen1:
|
||||||
"""Turn Mill unit off."""
|
await self._conn.heater_control(
|
||||||
await self._conn.heater_control(self._heater.device_id,
|
self._heater.device_id, power_status=0)
|
||||||
power_status=0)
|
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Retrieve latest state."""
|
"""Retrieve latest state."""
|
||||||
self._heater = await self._conn.update_device(self._heater.device_id)
|
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
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
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
|
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, CONF_SLAVE
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ DATA_TYPE_INT = 'int'
|
|||||||
DATA_TYPE_UINT = 'uint'
|
DATA_TYPE_UINT = 'uint'
|
||||||
DATA_TYPE_FLOAT = 'float'
|
DATA_TYPE_FLOAT = 'float'
|
||||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
|
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
|
||||||
|
HVAC_MODES = [HVAC_MODE_HEAT]
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_CURRENT_TEMP): cv.positive_int,
|
vol.Required(CONF_CURRENT_TEMP): cv.positive_int,
|
||||||
@@ -93,6 +95,16 @@ class ModbusThermostat(ClimateDevice):
|
|||||||
self._current_temperature = self.read_register(
|
self._current_temperature = self.read_register(
|
||||||
self._current_temperature_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
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the climate device."""
|
"""Return the name of the climate device."""
|
||||||
|
|||||||
@@ -7,17 +7,15 @@ from homeassistant.components import climate, mqtt
|
|||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import (
|
||||||
PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, ClimateDevice)
|
PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, ClimateDevice)
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_OPERATION_MODE, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, STATE_AUTO,
|
ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||||
STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT, SUPPORT_AUX_HEAT,
|
DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, HVAC_MODE_AUTO, HVAC_MODE_COOL,
|
||||||
SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, SUPPORT_HOLD_MODE,
|
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE,
|
||||||
ATTR_TARGET_TEMP_LOW,
|
SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, PRESET_AWAY,
|
||||||
ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
|
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||||
SUPPORT_TARGET_TEMPERATURE_HIGH)
|
|
||||||
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
|
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_OFF,
|
ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_ON)
|
||||||
STATE_ON)
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
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_COMMAND_TOPIC = 'hold_command_topic'
|
||||||
CONF_HOLD_STATE_TEMPLATE = 'hold_state_template'
|
CONF_HOLD_STATE_TEMPLATE = 'hold_state_template'
|
||||||
CONF_HOLD_STATE_TOPIC = 'hold_state_topic'
|
CONF_HOLD_STATE_TOPIC = 'hold_state_topic'
|
||||||
|
CONF_HOLD_LIST = 'hold_modes'
|
||||||
CONF_MODE_COMMAND_TOPIC = 'mode_command_topic'
|
CONF_MODE_COMMAND_TOPIC = 'mode_command_topic'
|
||||||
CONF_MODE_LIST = 'modes'
|
CONF_MODE_LIST = 'modes'
|
||||||
CONF_MODE_STATE_TEMPLATE = 'mode_state_template'
|
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_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
|
||||||
vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||||
vol.Optional(CONF_FAN_MODE_LIST,
|
vol.Optional(CONF_FAN_MODE_LIST,
|
||||||
default=[STATE_AUTO, SPEED_LOW,
|
default=[HVAC_MODE_AUTO, SPEED_LOW,
|
||||||
SPEED_MEDIUM, SPEED_HIGH]): cv.ensure_list,
|
SPEED_MEDIUM, SPEED_HIGH]): cv.ensure_list,
|
||||||
vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template,
|
vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
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_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||||
vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template,
|
vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
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_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||||
vol.Optional(CONF_MODE_LIST,
|
vol.Optional(CONF_MODE_LIST,
|
||||||
default=[STATE_AUTO, STATE_OFF, STATE_COOL, STATE_HEAT,
|
default=[HVAC_MODE_AUTO, HVAC_MODE_OFF, HVAC_MODE_COOL,
|
||||||
STATE_DRY, STATE_FAN_ONLY]): cv.ensure_list,
|
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_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
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_SEND_IF_OFF, default=True): cv.boolean,
|
||||||
vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||||
vol.Optional(CONF_SWING_MODE_LIST,
|
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_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
||||||
vol.Optional(CONF_TEMP_INITIAL, default=21): cv.positive_int,
|
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:
|
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None:
|
||||||
self._current_fan_mode = SPEED_LOW
|
self._current_fan_mode = SPEED_LOW
|
||||||
if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None:
|
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:
|
if self._topic[CONF_MODE_STATE_TOPIC] is None:
|
||||||
self._current_operation = STATE_OFF
|
self._current_operation = HVAC_MODE_OFF
|
||||||
self._away = False
|
self._away = False
|
||||||
self._hold = None
|
self._hold = None
|
||||||
self._aux = False
|
self._aux = False
|
||||||
@@ -442,6 +443,9 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||||||
"""Handle receiving hold mode via MQTT."""
|
"""Handle receiving hold mode via MQTT."""
|
||||||
payload = render_template(msg, CONF_HOLD_STATE_TEMPLATE)
|
payload = render_template(msg, CONF_HOLD_STATE_TEMPLATE)
|
||||||
|
|
||||||
|
if payload == 'off':
|
||||||
|
payload = None
|
||||||
|
|
||||||
self._hold = payload
|
self._hold = payload
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@@ -500,12 +504,12 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||||||
return self._target_temp_high
|
return self._target_temp_high
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return self._current_operation
|
return self._current_operation
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return self._config[CONF_MODE_LIST]
|
return self._config[CONF_MODE_LIST]
|
||||||
|
|
||||||
@@ -515,27 +519,39 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||||||
return self._config[CONF_TEMP_STEP]
|
return self._config[CONF_TEMP_STEP]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_away_mode_on(self):
|
def preset_mode(self):
|
||||||
"""Return if away mode is on."""
|
"""Return preset mode."""
|
||||||
return self._away
|
if self._hold:
|
||||||
|
return self._hold
|
||||||
|
if self._away:
|
||||||
|
return PRESET_AWAY
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_hold_mode(self):
|
def preset_modes(self):
|
||||||
"""Return hold mode setting."""
|
"""Return preset modes."""
|
||||||
return self._hold
|
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
|
@property
|
||||||
def is_aux_heat_on(self):
|
def is_aux_heat(self):
|
||||||
"""Return true if away mode is on."""
|
"""Return true if away mode is on."""
|
||||||
return self._aux
|
return self._aux
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self._current_fan_mode
|
return self._current_fan_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""Return the list of available fan modes."""
|
||||||
return self._config[CONF_FAN_MODE_LIST]
|
return self._config[CONF_FAN_MODE_LIST]
|
||||||
|
|
||||||
@@ -552,14 +568,14 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||||||
setattr(self, attr, temp)
|
setattr(self, attr, temp)
|
||||||
|
|
||||||
if (self._config[CONF_SEND_IF_OFF] or
|
if (self._config[CONF_SEND_IF_OFF] or
|
||||||
self._current_operation != STATE_OFF):
|
self._current_operation != HVAC_MODE_OFF):
|
||||||
self._publish(cmnd_topic, temp)
|
self._publish(cmnd_topic, temp)
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs):
|
async def async_set_temperature(self, **kwargs):
|
||||||
"""Set new target temperatures."""
|
"""Set new target temperatures."""
|
||||||
if kwargs.get(ATTR_OPERATION_MODE) is not None:
|
if kwargs.get(ATTR_HVAC_MODE) is not None:
|
||||||
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
|
operation_mode = kwargs.get(ATTR_HVAC_MODE)
|
||||||
await self.async_set_operation_mode(operation_mode)
|
await self.async_set_hvac_mode(operation_mode)
|
||||||
|
|
||||||
self._set_temperature(
|
self._set_temperature(
|
||||||
kwargs.get(ATTR_TEMPERATURE), CONF_TEMP_COMMAND_TOPIC,
|
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):
|
async def async_set_swing_mode(self, swing_mode):
|
||||||
"""Set new swing mode."""
|
"""Set new swing mode."""
|
||||||
if (self._config[CONF_SEND_IF_OFF] or
|
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,
|
self._publish(CONF_SWING_MODE_COMMAND_TOPIC,
|
||||||
swing_mode)
|
swing_mode)
|
||||||
|
|
||||||
@@ -590,7 +606,7 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||||||
async def async_set_fan_mode(self, fan_mode):
|
async def async_set_fan_mode(self, fan_mode):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
if (self._config[CONF_SEND_IF_OFF] or
|
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,
|
self._publish(CONF_FAN_MODE_COMMAND_TOPIC,
|
||||||
fan_mode)
|
fan_mode)
|
||||||
|
|
||||||
@@ -598,58 +614,83 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||||||
self._current_fan_mode = fan_mode
|
self._current_fan_mode = fan_mode
|
||||||
self.async_write_ha_state()
|
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."""
|
"""Set new operation mode."""
|
||||||
if (self._current_operation == STATE_OFF and
|
if (self._current_operation == HVAC_MODE_OFF and
|
||||||
operation_mode != STATE_OFF):
|
hvac_mode != HVAC_MODE_OFF):
|
||||||
self._publish(CONF_POWER_COMMAND_TOPIC,
|
self._publish(CONF_POWER_COMMAND_TOPIC,
|
||||||
self._config[CONF_PAYLOAD_ON])
|
self._config[CONF_PAYLOAD_ON])
|
||||||
elif (self._current_operation != STATE_OFF and
|
elif (self._current_operation != HVAC_MODE_OFF and
|
||||||
operation_mode == STATE_OFF):
|
hvac_mode == HVAC_MODE_OFF):
|
||||||
self._publish(CONF_POWER_COMMAND_TOPIC,
|
self._publish(CONF_POWER_COMMAND_TOPIC,
|
||||||
self._config[CONF_PAYLOAD_OFF])
|
self._config[CONF_PAYLOAD_OFF])
|
||||||
|
|
||||||
self._publish(CONF_MODE_COMMAND_TOPIC,
|
self._publish(CONF_MODE_COMMAND_TOPIC,
|
||||||
operation_mode)
|
hvac_mode)
|
||||||
|
|
||||||
if self._topic[CONF_MODE_STATE_TOPIC] is None:
|
if self._topic[CONF_MODE_STATE_TOPIC] is None:
|
||||||
self._current_operation = operation_mode
|
self._current_operation = hvac_mode
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_swing_mode(self):
|
def swing_mode(self):
|
||||||
"""Return the swing setting."""
|
"""Return the swing setting."""
|
||||||
return self._current_swing_mode
|
return self._current_swing_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def swing_list(self):
|
def swing_modes(self):
|
||||||
"""List of available swing modes."""
|
"""List of available swing modes."""
|
||||||
return self._config[CONF_SWING_MODE_LIST]
|
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):
|
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._publish(CONF_AWAY_MODE_COMMAND_TOPIC,
|
||||||
self._config[CONF_PAYLOAD_ON] if state
|
self._config[CONF_PAYLOAD_ON] if state
|
||||||
else self._config[CONF_PAYLOAD_OFF])
|
else self._config[CONF_PAYLOAD_OFF])
|
||||||
|
|
||||||
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None:
|
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None:
|
||||||
self._away = state
|
return False
|
||||||
self.async_write_ha_state()
|
|
||||||
|
|
||||||
async def async_turn_away_mode_on(self):
|
self._away = state
|
||||||
"""Turn away mode on."""
|
return True
|
||||||
self._set_away_mode(True)
|
|
||||||
|
|
||||||
async def async_turn_away_mode_off(self):
|
def _set_hold_mode(self, hold_mode):
|
||||||
"""Turn away mode off."""
|
"""Set hold mode.
|
||||||
self._set_away_mode(False)
|
|
||||||
|
|
||||||
async def async_set_hold_mode(self, hold_mode):
|
Returns if we should optimistically write the state.
|
||||||
"""Update hold mode on."""
|
"""
|
||||||
self._publish(CONF_HOLD_COMMAND_TOPIC, hold_mode)
|
self._publish(CONF_HOLD_COMMAND_TOPIC, hold_mode or "off")
|
||||||
|
|
||||||
if self._topic[CONF_HOLD_STATE_TOPIC] is None:
|
if self._topic[CONF_HOLD_STATE_TOPIC] is not None:
|
||||||
self._hold = hold_mode
|
return False
|
||||||
self.async_write_ha_state()
|
|
||||||
|
self._hold = hold_mode
|
||||||
|
return True
|
||||||
|
|
||||||
def _set_aux_heat(self, state):
|
def _set_aux_heat(self, state):
|
||||||
self._publish(CONF_AUX_COMMAND_TOPIC,
|
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 \
|
if (self._topic[CONF_TEMP_LOW_STATE_TOPIC] is not None) or \
|
||||||
(self._topic[CONF_TEMP_LOW_COMMAND_TOPIC] is not None):
|
(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 \
|
if (self._topic[CONF_TEMP_HIGH_STATE_TOPIC] is not None) or \
|
||||||
(self._topic[CONF_TEMP_HIGH_COMMAND_TOPIC] is not None):
|
(self._topic[CONF_TEMP_HIGH_COMMAND_TOPIC] is not None):
|
||||||
support |= SUPPORT_TARGET_TEMPERATURE_HIGH
|
support |= SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||||
|
|
||||||
if (self._topic[CONF_MODE_COMMAND_TOPIC] is not None) or \
|
|
||||||
(self._topic[CONF_MODE_STATE_TOPIC] is not None):
|
|
||||||
support |= SUPPORT_OPERATION_MODE
|
|
||||||
|
|
||||||
if (self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None) or \
|
if (self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None) or \
|
||||||
(self._topic[CONF_FAN_MODE_COMMAND_TOPIC] is not None):
|
(self._topic[CONF_FAN_MODE_COMMAND_TOPIC] is not None):
|
||||||
@@ -698,12 +735,10 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||||||
support |= SUPPORT_SWING_MODE
|
support |= SUPPORT_SWING_MODE
|
||||||
|
|
||||||
if (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) or \
|
if (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) or \
|
||||||
(self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None):
|
(self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None) or \
|
||||||
support |= SUPPORT_AWAY_MODE
|
(self._topic[CONF_HOLD_STATE_TOPIC] is not None) or \
|
||||||
|
|
||||||
if (self._topic[CONF_HOLD_STATE_TOPIC] is not None) or \
|
|
||||||
(self._topic[CONF_HOLD_COMMAND_TOPIC] is not None):
|
(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 \
|
if (self._topic[CONF_AUX_STATE_TOPIC] is not None) or \
|
||||||
(self._topic[CONF_AUX_COMMAND_TOPIC] is not None):
|
(self._topic[CONF_AUX_COMMAND_TOPIC] is not None):
|
||||||
|
|||||||
@@ -2,28 +2,30 @@
|
|||||||
from homeassistant.components import mysensors
|
from homeassistant.components import mysensors
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO,
|
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, HVAC_MODE_AUTO,
|
||||||
STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
|
HVAC_MODE_COOL, HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_TARGET_TEMPERATURE,
|
||||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
|
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||||
|
HVAC_MODE_OFF)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
DICT_HA_TO_MYS = {
|
DICT_HA_TO_MYS = {
|
||||||
STATE_AUTO: 'AutoChangeOver',
|
HVAC_MODE_AUTO: 'AutoChangeOver',
|
||||||
STATE_COOL: 'CoolOn',
|
HVAC_MODE_COOL: 'CoolOn',
|
||||||
STATE_HEAT: 'HeatOn',
|
HVAC_MODE_HEAT: 'HeatOn',
|
||||||
STATE_OFF: 'Off',
|
HVAC_MODE_OFF: 'Off',
|
||||||
}
|
}
|
||||||
DICT_MYS_TO_HA = {
|
DICT_MYS_TO_HA = {
|
||||||
'AutoChangeOver': STATE_AUTO,
|
'AutoChangeOver': HVAC_MODE_AUTO,
|
||||||
'CoolOn': STATE_COOL,
|
'CoolOn': HVAC_MODE_COOL,
|
||||||
'HeatOn': STATE_HEAT,
|
'HeatOn': HVAC_MODE_HEAT,
|
||||||
'Off': STATE_OFF,
|
'Off': HVAC_MODE_OFF,
|
||||||
}
|
}
|
||||||
|
|
||||||
FAN_LIST = ['Auto', 'Min', 'Normal', 'Max']
|
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(
|
async def async_setup_platform(
|
||||||
@@ -40,15 +42,14 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
features = SUPPORT_OPERATION_MODE
|
features = 0
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
if set_req.V_HVAC_SPEED in self._values:
|
if set_req.V_HVAC_SPEED in self._values:
|
||||||
features = features | SUPPORT_FAN_MODE
|
features = features | SUPPORT_FAN_MODE
|
||||||
if (set_req.V_HVAC_SETPOINT_COOL in self._values and
|
if (set_req.V_HVAC_SETPOINT_COOL in self._values and
|
||||||
set_req.V_HVAC_SETPOINT_HEAT in self._values):
|
set_req.V_HVAC_SETPOINT_HEAT in self._values):
|
||||||
features = (
|
features = (
|
||||||
features | SUPPORT_TARGET_TEMPERATURE_HIGH |
|
features | SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
|
||||||
else:
|
else:
|
||||||
features = features | SUPPORT_TARGET_TEMPERATURE
|
features = features | SUPPORT_TARGET_TEMPERATURE
|
||||||
return features
|
return features
|
||||||
@@ -102,22 +103,22 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice):
|
|||||||
return float(temp) if temp is not None else None
|
return float(temp) if temp is not None else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return self._values.get(self.value_type)
|
return self._values.get(self.value_type)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""List of available operation modes."""
|
"""List of available operation modes."""
|
||||||
return OPERATION_LIST
|
return OPERATION_LIST
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self._values.get(self.gateway.const.SetReq.V_HVAC_SPEED)
|
return self._values.get(self.gateway.const.SetReq.V_HVAC_SPEED)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""List of available fan modes."""
|
"""List of available fan modes."""
|
||||||
return FAN_LIST
|
return FAN_LIST
|
||||||
|
|
||||||
@@ -161,14 +162,14 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice):
|
|||||||
self._values[set_req.V_HVAC_SPEED] = fan_mode
|
self._values[set_req.V_HVAC_SPEED] = fan_mode
|
||||||
self.async_schedule_update_ha_state()
|
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."""
|
"""Set new target temperature."""
|
||||||
self.gateway.set_child_value(
|
self.gateway.set_child_value(
|
||||||
self.node_id, self.child_id, self.value_type,
|
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:
|
if self.gateway.optimistic:
|
||||||
# Optimistically assume that device has changed state
|
# 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()
|
self.async_schedule_update_ha_state()
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import threading
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.climate.const import (
|
|
||||||
ATTR_AWAY_MODE, SERVICE_SET_AWAY_MODE)
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_BINARY_SENSORS, CONF_FILENAME, CONF_MONITORED_CONDITIONS,
|
CONF_BINARY_SENSORS, CONF_FILENAME, CONF_MONITORED_CONDITIONS,
|
||||||
CONF_SENSORS, CONF_STRUCTURE, EVENT_HOMEASSISTANT_START,
|
CONF_SENSORS, CONF_STRUCTURE, EVENT_HOMEASSISTANT_START,
|
||||||
@@ -45,6 +43,9 @@ ATTR_TRIP_ID = 'trip_id'
|
|||||||
AWAY_MODE_AWAY = 'away'
|
AWAY_MODE_AWAY = 'away'
|
||||||
AWAY_MODE_HOME = 'home'
|
AWAY_MODE_HOME = 'home'
|
||||||
|
|
||||||
|
ATTR_AWAY_MODE = 'away_mode'
|
||||||
|
SERVICE_SET_AWAY_MODE = 'set_away_mode'
|
||||||
|
|
||||||
SENSOR_SCHEMA = vol.Schema({
|
SENSOR_SCHEMA = vol.Schema({
|
||||||
vol.Optional(CONF_MONITORED_CONDITIONS): vol.All(cv.ensure_list),
|
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 import PLATFORM_SCHEMA, ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, STATE_AUTO, STATE_COOL,
|
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, FAN_AUTO, FAN_ON,
|
||||||
STATE_ECO, STATE_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
|
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_PRESET_MODE, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
|
SUPPORT_TARGET_TEMPERATURE_RANGE, PRESET_AWAY, PRESET_ECO)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, CONF_SCAN_INTERVAL, STATE_OFF, STATE_ON, TEMP_CELSIUS,
|
ATTR_TEMPERATURE, CONF_SCAN_INTERVAL, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||||
TEMP_FAHRENHEIT)
|
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
from . import DATA_NEST, DOMAIN as NEST_DOMAIN, SIGNAL_NEST_UPDATE
|
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_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):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
@@ -53,29 +58,28 @@ class NestThermostat(ClimateDevice):
|
|||||||
self._unit = temp_unit
|
self._unit = temp_unit
|
||||||
self.structure = structure
|
self.structure = structure
|
||||||
self.device = device
|
self.device = device
|
||||||
self._fan_list = [STATE_ON, STATE_AUTO]
|
self._fan_modes = [FAN_ON, FAN_AUTO]
|
||||||
|
|
||||||
# Set the default supported features
|
# Set the default supported features
|
||||||
self._support_flags = (SUPPORT_TARGET_TEMPERATURE |
|
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
|
# 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
|
# Add supported nest thermostat features
|
||||||
if self.device.can_heat:
|
if self.device.can_heat:
|
||||||
self._operation_list.append(STATE_HEAT)
|
self._operation_list.append(HVAC_MODE_HEAT)
|
||||||
|
|
||||||
if self.device.can_cool:
|
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(HVAC_MODE_OFF)
|
||||||
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)
|
|
||||||
|
|
||||||
# feature of device
|
# feature of device
|
||||||
self._has_fan = self.device.has_fan
|
self._has_fan = self.device.has_fan
|
||||||
@@ -151,25 +155,29 @@ class NestThermostat(ClimateDevice):
|
|||||||
return self._temperature
|
return self._temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""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
|
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:
|
if self._mode == NEST_MODE_HEAT_COOL:
|
||||||
return STATE_AUTO
|
return HVAC_MODE_AUTO
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
"""Return the temperature we try to reach."""
|
"""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 self._target_temperature
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_low(self):
|
def target_temperature_low(self):
|
||||||
"""Return the lower bound temperature we try to reach."""
|
"""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]
|
return self._eco_temperature[0]
|
||||||
if self._mode == NEST_MODE_HEAT_COOL:
|
if self._mode == NEST_MODE_HEAT_COOL:
|
||||||
return self._target_temperature[0]
|
return self._target_temperature[0]
|
||||||
@@ -178,17 +186,12 @@ class NestThermostat(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def target_temperature_high(self):
|
def target_temperature_high(self):
|
||||||
"""Return the upper bound temperature we try to reach."""
|
"""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]
|
return self._eco_temperature[1]
|
||||||
if self._mode == NEST_MODE_HEAT_COOL:
|
if self._mode == NEST_MODE_HEAT_COOL:
|
||||||
return self._target_temperature[1]
|
return self._target_temperature[1]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
|
||||||
def is_away_mode_on(self):
|
|
||||||
"""Return if away mode is on."""
|
|
||||||
return self._away
|
|
||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
import nest
|
import nest
|
||||||
@@ -211,46 +214,69 @@ class NestThermostat(ClimateDevice):
|
|||||||
# restore target temperature
|
# restore target temperature
|
||||||
self.schedule_update_ha_state(True)
|
self.schedule_update_ha_state(True)
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set operation mode."""
|
"""Set operation mode."""
|
||||||
if operation_mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]:
|
if hvac_mode in (HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF):
|
||||||
device_mode = operation_mode
|
device_mode = hvac_mode
|
||||||
elif operation_mode == STATE_AUTO:
|
elif hvac_mode == HVAC_MODE_AUTO:
|
||||||
device_mode = NEST_MODE_HEAT_COOL
|
device_mode = NEST_MODE_HEAT_COOL
|
||||||
else:
|
else:
|
||||||
device_mode = STATE_OFF
|
device_mode = HVAC_MODE_OFF
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"An error occurred while setting device mode. "
|
"An error occurred while setting device mode. "
|
||||||
"Invalid operation mode: %s", operation_mode)
|
"Invalid operation mode: %s", hvac_mode)
|
||||||
self.device.mode = device_mode
|
self.device.mode = device_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""List of available operation modes."""
|
"""List of available operation modes."""
|
||||||
return self._operation_list
|
return self._operation_list
|
||||||
|
|
||||||
def turn_away_mode_on(self):
|
@property
|
||||||
"""Turn away on."""
|
def preset_mode(self):
|
||||||
self.structure.away = True
|
"""Return current preset mode."""
|
||||||
|
if self._away:
|
||||||
|
return PRESET_AWAY
|
||||||
|
|
||||||
def turn_away_mode_off(self):
|
if self._mode == NEST_MODE_ECO:
|
||||||
"""Turn away off."""
|
return PRESET_ECO
|
||||||
self.structure.away = False
|
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@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."""
|
"""Return whether the fan is on."""
|
||||||
if self._has_fan:
|
if self._has_fan:
|
||||||
# Return whether the fan is on
|
# 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
|
# No Fan available so disable slider
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""List of available fan modes."""
|
"""List of available fan modes."""
|
||||||
if self._has_fan:
|
if self._has_fan:
|
||||||
return self._fan_list
|
return self._fan_modes
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def set_fan_mode(self, fan_mode):
|
def set_fan_mode(self, fan_mode):
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
"""Support for Nest Thermostat sensors."""
|
"""Support for Nest Thermostat sensors."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.climate.const import STATE_COOL, STATE_HEAT
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_MONITORED_CONDITIONS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE,
|
CONF_MONITORED_CONDITIONS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE,
|
||||||
STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
from . import CONF_SENSORS, DATA_NEST, DATA_NEST_CONFIG, NestSensorDevice
|
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']
|
TEMP_SENSOR_TYPES = ['temperature', 'target']
|
||||||
|
|
||||||
@@ -20,6 +19,9 @@ PROTECT_SENSOR_TYPES = ['co_status',
|
|||||||
|
|
||||||
STRUCTURE_SENSOR_TYPES = ['eta']
|
STRUCTURE_SENSOR_TYPES = ['eta']
|
||||||
|
|
||||||
|
STATE_HEAT = 'heat'
|
||||||
|
STATE_COOL = 'cool'
|
||||||
|
|
||||||
# security_state is structure level sensor, but only meaningful when
|
# security_state is structure level sensor, but only meaningful when
|
||||||
# Nest Cam exist
|
# Nest Cam exist
|
||||||
STRUCTURE_CAMERA_SENSOR_TYPES = ['security_state']
|
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'}
|
VARIABLE_NAME_MAPPING = {'eta': 'eta_begin', 'operation_mode': 'mode'}
|
||||||
|
|
||||||
VALUE_MAPPING = {
|
VALUE_MAPPING = {
|
||||||
'hvac_state': {
|
'hvac_mode': {
|
||||||
'heating': STATE_HEAT, 'cooling': STATE_COOL, 'off': STATE_OFF}}
|
'heating': STATE_HEAT, 'cooling': STATE_COOL, 'off': STATE_OFF}}
|
||||||
|
|
||||||
SENSOR_TYPES_DEPRECATED = ['last_ip',
|
SENSOR_TYPES_DEPRECATED = ['last_ip',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Support for Netatmo Smart thermostats."""
|
"""Support for Netatmo Smart thermostats."""
|
||||||
import logging
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -8,21 +9,54 @@ import voluptuous as vol
|
|||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_HEAT, SUPPORT_ON_OFF, SUPPORT_TARGET_TEMPERATURE,
|
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, STATE_MANUAL, STATE_AUTO,
|
PRESET_AWAY,
|
||||||
STATE_ECO, STATE_COOL)
|
CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE,
|
||||||
|
SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE
|
||||||
|
)
|
||||||
from homeassistant.const import (
|
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 homeassistant.util import Throttle
|
||||||
|
|
||||||
from .const import DATA_NETATMO_AUTH
|
from .const import DATA_NETATMO_AUTH
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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_HOMES = 'homes'
|
||||||
CONF_ROOMS = 'rooms'
|
CONF_ROOMS = 'rooms'
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
|
||||||
|
|
||||||
HOME_CONFIG_SCHEMA = vol.Schema({
|
HOME_CONFIG_SCHEMA = vol.Schema({
|
||||||
vol.Required(CONF_NAME): cv.string,
|
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])
|
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_THERM = 'NATherm1'
|
||||||
NA_VALVE = 'NRV'
|
NA_VALVE = 'NRV'
|
||||||
|
|
||||||
@@ -115,28 +121,26 @@ class NetatmoThermostat(ClimateDevice):
|
|||||||
self._data = data
|
self._data = data
|
||||||
self._state = None
|
self._state = None
|
||||||
self._room_id = room_id
|
self._room_id = room_id
|
||||||
room_name = self._data.homedata.rooms[self._data.home][room_id]['name']
|
self._room_name = self._data.homedata.rooms[
|
||||||
self._name = 'netatmo_{}'.format(room_name)
|
self._data.home][room_id]['name']
|
||||||
|
self._name = 'netatmo_{}'.format(self._room_name)
|
||||||
|
self._current_temperature = None
|
||||||
self._target_temperature = None
|
self._target_temperature = None
|
||||||
|
self._preset = None
|
||||||
self._away = None
|
self._away = None
|
||||||
self._module_type = self._data.room_status[room_id]['module_type']
|
self._operation_list = [HVAC_MODE_AUTO, HVAC_MODE_HEAT]
|
||||||
if self._module_type == NA_VALVE:
|
self._support_flags = SUPPORT_FLAGS
|
||||||
self._operation_list = [DICT_NETATMO_TO_HA[STATE_NETATMO_SCHEDULE],
|
self._hvac_mode = None
|
||||||
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.update_without_throttle = False
|
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
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
@@ -155,113 +159,86 @@ class NetatmoThermostat(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
return self._data.room_status[self._room_id]['current_temperature']
|
return self._current_temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
return self._data.room_status[self._room_id]['target_temperature']
|
return self._target_temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def target_temperature_step(self) -> Optional[float]:
|
||||||
"""Return the current state of the thermostat."""
|
"""Return the supported step of target temperature."""
|
||||||
return self._operation_mode
|
return PRECISION_HALVES
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_mode(self):
|
||||||
"""Return the operation modes list."""
|
"""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
|
return self._operation_list
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def hvac_action(self) -> Optional[str]:
|
||||||
"""Return device specific state attributes."""
|
"""Return the current running hvac operation if supported."""
|
||||||
module_type = self._data.room_status[self._room_id]['module_type']
|
if self._module_type == NA_THERM:
|
||||||
if module_type not in (NA_THERM, NA_VALVE):
|
return CURRENT_HVAC_MAP_NETATMO[self._data.boilerstatus]
|
||||||
return {}
|
# Maybe it is a valve
|
||||||
state_attributes = {
|
if self._data.room_status[self._room_id]['heating_power_request'] > 0:
|
||||||
"home_id": self._data.homedata.gethomeId(self._data.home),
|
return CURRENT_HVAC_HEAT
|
||||||
"room_id": self._room_id,
|
return CURRENT_HVAC_IDLE
|
||||||
"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
|
|
||||||
|
|
||||||
@property
|
def set_hvac_mode(self, hvac_mode: str) -> None:
|
||||||
def is_away_mode_on(self):
|
"""Set new target hvac mode."""
|
||||||
"""Return true if away mode is on."""
|
mode = None
|
||||||
return self._away
|
|
||||||
|
|
||||||
@property
|
if hvac_mode == HVAC_MODE_OFF:
|
||||||
def is_on(self):
|
mode = STATE_NETATMO_OFF
|
||||||
"""Return true if on."""
|
elif hvac_mode == HVAC_MODE_AUTO:
|
||||||
return self.target_temperature > 0
|
mode = STATE_NETATMO_SCHEDULE
|
||||||
|
elif hvac_mode == HVAC_MODE_HEAT:
|
||||||
|
mode = STATE_NETATMO_MAX
|
||||||
|
|
||||||
def turn_away_mode_on(self):
|
self.set_preset_mode(mode)
|
||||||
"""Turn away on."""
|
|
||||||
self.set_operation_mode(DICT_NETATMO_TO_HA[STATE_NETATMO_AWAY])
|
|
||||||
|
|
||||||
def turn_away_mode_off(self):
|
def set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Turn away off."""
|
"""Set new preset mode."""
|
||||||
self.set_operation_mode(DICT_NETATMO_TO_HA[STATE_NETATMO_SCHEDULE])
|
if preset_mode is None:
|
||||||
|
|
||||||
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]]:
|
|
||||||
self._data.homestatus.setroomThermpoint(
|
self._data.homestatus.setroomThermpoint(
|
||||||
self._data.homedata.gethomeId(self._data.home),
|
self._data.home_id, self._room_id, "off"
|
||||||
self._room_id, DICT_HA_TO_NETATMO[operation_mode])
|
)
|
||||||
elif operation_mode in [DICT_NETATMO_TO_HA[STATE_NETATMO_HG],
|
if preset_mode == STATE_NETATMO_MAX:
|
||||||
DICT_NETATMO_TO_HA[STATE_NETATMO_SCHEDULE],
|
self._data.homestatus.setroomThermpoint(
|
||||||
DICT_NETATMO_TO_HA[STATE_NETATMO_AWAY]]:
|
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.homestatus.setThermmode(
|
||||||
self._data.homedata.gethomeId(self._data.home),
|
self._data.home_id, preset_mode
|
||||||
DICT_HA_TO_NETATMO[operation_mode])
|
)
|
||||||
self.update_without_throttle = True
|
|
||||||
self.schedule_update_ha_state()
|
@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):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature for 2 hours."""
|
"""Set new target temperature for 2 hours."""
|
||||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||||
if temp is None:
|
if temp is None:
|
||||||
return
|
return
|
||||||
mode = STATE_NETATMO_MANUAL
|
|
||||||
self._data.homestatus.setroomThermpoint(
|
self._data.homestatus.setroomThermpoint(
|
||||||
self._data.homedata.gethomeId(self._data.home),
|
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.update_without_throttle = True
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
@@ -277,12 +254,20 @@ class NetatmoThermostat(ClimateDevice):
|
|||||||
_LOGGER.error("NetatmoThermostat::update() "
|
_LOGGER.error("NetatmoThermostat::update() "
|
||||||
"got exception.")
|
"got exception.")
|
||||||
return
|
return
|
||||||
self._target_temperature = \
|
try:
|
||||||
self._data.room_status[self._room_id]['target_temperature']
|
self._current_temperature = \
|
||||||
self._operation_mode = DICT_NETATMO_TO_HA[
|
self._data.room_status[self._room_id]['current_temperature']
|
||||||
self._data.room_status[self._room_id]['setpoint_mode']]
|
self._target_temperature = \
|
||||||
self._away = self._operation_mode == DICT_NETATMO_TO_HA[
|
self._data.room_status[self._room_id]['target_temperature']
|
||||||
STATE_NETATMO_AWAY]
|
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:
|
class HomeData:
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
DOMAIN, STATE_AUTO, STATE_HEAT, STATE_IDLE, SUPPORT_HOLD_MODE,
|
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_PRESET_MODE,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
ATTR_ENTITY_ID, ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@@ -17,29 +17,26 @@ from . import DOMAIN as NUHEAT_DOMAIN
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ICON = "mdi:thermometer"
|
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
|
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
|
||||||
|
|
||||||
# Hold modes
|
# Hold modes
|
||||||
MODE_AUTO = STATE_AUTO # Run device schedule
|
MODE_AUTO = HVAC_MODE_AUTO # Run device schedule
|
||||||
MODE_HOLD_TEMPERATURE = "temperature"
|
MODE_HOLD_TEMPERATURE = "temperature"
|
||||||
MODE_TEMPORARY_HOLD = "temporary_temperature"
|
MODE_TEMPORARY_HOLD = "temporary_temperature"
|
||||||
|
|
||||||
OPERATION_LIST = [STATE_HEAT, STATE_IDLE]
|
OPERATION_LIST = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||||
|
|
||||||
SCHEDULE_HOLD = 3
|
SCHEDULE_HOLD = 3
|
||||||
SCHEDULE_RUN = 1
|
SCHEDULE_RUN = 1
|
||||||
SCHEDULE_TEMPORARY_HOLD = 2
|
SCHEDULE_TEMPORARY_HOLD = 2
|
||||||
|
|
||||||
SERVICE_RESUME_PROGRAM = "nuheat_resume_program"
|
SERVICE_RESUME_PROGRAM = "resume_program"
|
||||||
|
|
||||||
RESUME_PROGRAM_SCHEMA = vol.Schema({
|
RESUME_PROGRAM_SCHEMA = vol.Schema({
|
||||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids
|
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids
|
||||||
})
|
})
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_HOLD_MODE |
|
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE)
|
||||||
SUPPORT_OPERATION_MODE)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
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)
|
thermostat.schedule_update_ha_state(True)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.register(
|
||||||
DOMAIN, SERVICE_RESUME_PROGRAM, resume_program_set_service,
|
NUHEAT_DOMAIN, SERVICE_RESUME_PROGRAM, resume_program_set_service,
|
||||||
schema=RESUME_PROGRAM_SCHEMA)
|
schema=RESUME_PROGRAM_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
@@ -88,11 +85,6 @@ class NuHeatThermostat(ClimateDevice):
|
|||||||
"""Return the name of the thermostat."""
|
"""Return the name of the thermostat."""
|
||||||
return self._thermostat.room
|
return self._thermostat.room
|
||||||
|
|
||||||
@property
|
|
||||||
def icon(self):
|
|
||||||
"""Return the icon to use in the frontend."""
|
|
||||||
return ICON
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
@@ -115,12 +107,12 @@ class NuHeatThermostat(ClimateDevice):
|
|||||||
return self._thermostat.fahrenheit
|
return self._thermostat.fahrenheit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation. ie. heat, idle."""
|
"""Return current operation. ie. heat, idle."""
|
||||||
if self._thermostat.heating:
|
if self._thermostat.heating:
|
||||||
return STATE_HEAT
|
return HVAC_MODE_HEAT
|
||||||
|
|
||||||
return STATE_IDLE
|
return HVAC_MODE_OFF
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
@@ -147,8 +139,8 @@ class NuHeatThermostat(ClimateDevice):
|
|||||||
return self._thermostat.target_fahrenheit
|
return self._thermostat.target_fahrenheit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_hold_mode(self):
|
def preset_mode(self):
|
||||||
"""Return current hold mode."""
|
"""Return current preset mode."""
|
||||||
schedule_mode = self._thermostat.schedule_mode
|
schedule_mode = self._thermostat.schedule_mode
|
||||||
if schedule_mode == SCHEDULE_RUN:
|
if schedule_mode == SCHEDULE_RUN:
|
||||||
return MODE_AUTO
|
return MODE_AUTO
|
||||||
@@ -162,7 +154,15 @@ class NuHeatThermostat(ClimateDevice):
|
|||||||
return MODE_AUTO
|
return MODE_AUTO
|
||||||
|
|
||||||
@property
|
@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 list of possible operation modes."""
|
||||||
return OPERATION_LIST
|
return OPERATION_LIST
|
||||||
|
|
||||||
@@ -171,15 +171,15 @@ class NuHeatThermostat(ClimateDevice):
|
|||||||
self._thermostat.resume_schedule()
|
self._thermostat.resume_schedule()
|
||||||
self._force_update = True
|
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."""
|
"""Update the hold mode of the thermostat."""
|
||||||
if hold_mode == MODE_AUTO:
|
if preset_mode is None:
|
||||||
schedule_mode = SCHEDULE_RUN
|
schedule_mode = SCHEDULE_RUN
|
||||||
|
|
||||||
if hold_mode == MODE_HOLD_TEMPERATURE:
|
elif preset_mode == MODE_HOLD_TEMPERATURE:
|
||||||
schedule_mode = SCHEDULE_HOLD
|
schedule_mode = SCHEDULE_HOLD
|
||||||
|
|
||||||
if hold_mode == MODE_TEMPORARY_HOLD:
|
elif preset_mode == MODE_TEMPORARY_HOLD:
|
||||||
schedule_mode = SCHEDULE_TEMPORARY_HOLD
|
schedule_mode = SCHEDULE_TEMPORARY_HOLD
|
||||||
|
|
||||||
self._thermostat.schedule_mode = schedule_mode
|
self._thermostat.schedule_mode = schedule_mode
|
||||||
|
|||||||
@@ -1,29 +1,21 @@
|
|||||||
"""
|
"""OpenEnergyMonitor Thermostat Support."""
|
||||||
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/
|
|
||||||
"""
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from oemthermostat import Thermostat
|
||||||
import requests
|
import requests
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
# Import the device class from the component that you want to support
|
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
|
||||||
from homeassistant.components.climate.const import (
|
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 (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, CONF_HOST, CONF_USERNAME, CONF_PASSWORD,
|
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT,
|
||||||
CONF_PORT, TEMP_CELSIUS, CONF_NAME)
|
CONF_USERNAME, TEMP_CELSIUS)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_AWAY_TEMP = 'away_temp'
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_HOST): cv.string,
|
vol.Required(CONF_HOST): cv.string,
|
||||||
@@ -31,22 +23,19 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
vol.Optional(CONF_PORT, default=80): cv.port,
|
vol.Optional(CONF_PORT, default=80): cv.port,
|
||||||
vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string,
|
vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string,
|
||||||
vol.Inclusive(CONF_PASSWORD, '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):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up the oemthermostat platform."""
|
"""Set up the oemthermostat platform."""
|
||||||
from oemthermostat import Thermostat
|
|
||||||
|
|
||||||
name = config.get(CONF_NAME)
|
name = config.get(CONF_NAME)
|
||||||
host = config.get(CONF_HOST)
|
host = config.get(CONF_HOST)
|
||||||
port = config.get(CONF_PORT)
|
port = config.get(CONF_PORT)
|
||||||
username = config.get(CONF_USERNAME)
|
username = config.get(CONF_USERNAME)
|
||||||
password = config.get(CONF_PASSWORD)
|
password = config.get(CONF_PASSWORD)
|
||||||
away_temp = config.get(CONF_AWAY_TEMP)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
therm = Thermostat(
|
therm = Thermostat(
|
||||||
@@ -54,36 +43,48 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
except (ValueError, AssertionError, requests.RequestException):
|
except (ValueError, AssertionError, requests.RequestException):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
add_entities((ThermostatDevice(hass, therm, name, away_temp), ), True)
|
add_entities((ThermostatDevice(therm, name), ), True)
|
||||||
|
|
||||||
|
|
||||||
class ThermostatDevice(ClimateDevice):
|
class ThermostatDevice(ClimateDevice):
|
||||||
"""Interface class for the oemthermostat module."""
|
"""Interface class for the oemthermostat module."""
|
||||||
|
|
||||||
def __init__(self, hass, thermostat, name, away_temp):
|
def __init__(self, thermostat, name):
|
||||||
"""Initialize the device."""
|
"""Initialize the device."""
|
||||||
self._name = name
|
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
|
self.thermostat = thermostat
|
||||||
# Set the thermostat mode to manual
|
|
||||||
self.thermostat.mode = 2
|
|
||||||
|
|
||||||
# set up internal state varS
|
# set up internal state varS
|
||||||
self._state = None
|
self._state = None
|
||||||
self._temperature = None
|
self._temperature = None
|
||||||
self._setpoint = None
|
self._setpoint = None
|
||||||
|
self._mode = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
return SUPPORT_FLAGS
|
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
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of this Thermostat."""
|
"""Return the name of this Thermostat."""
|
||||||
@@ -95,11 +96,13 @@ class ThermostatDevice(ClimateDevice):
|
|||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_action(self):
|
||||||
"""Return current operation i.e. heat, cool, idle."""
|
"""Return current hvac i.e. heat, cool, idle."""
|
||||||
|
if not self._mode:
|
||||||
|
return CURRENT_HVAC_OFF
|
||||||
if self._state:
|
if self._state:
|
||||||
return STATE_HEAT
|
return CURRENT_HVAC_HEAT
|
||||||
return STATE_IDLE
|
return CURRENT_HVAC_IDLE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
@@ -111,36 +114,23 @@ class ThermostatDevice(ClimateDevice):
|
|||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
return self._setpoint
|
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):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set the temperature."""
|
"""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)
|
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||||
self.thermostat.setpoint = temp
|
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):
|
def update(self):
|
||||||
"""Update local state."""
|
"""Update local state."""
|
||||||
self._setpoint = self.thermostat.setpoint
|
self._setpoint = self.thermostat.setpoint
|
||||||
self._temperature = self.thermostat.temperature
|
self._temperature = self.thermostat.temperature
|
||||||
self._state = self.thermostat.state
|
self._state = self.thermostat.state
|
||||||
|
self._mode = self.thermostat.mode
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import logging
|
|||||||
|
|
||||||
from pyotgw import vars as gw_vars
|
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 (
|
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 (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE,
|
ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE,
|
||||||
TEMP_CELSIUS)
|
TEMP_CELSIUS)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity import async_generate_entity_id
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_FLOOR_TEMP, CONF_PRECISION, DATA_GATEWAYS, DATA_OPENTHERM_GW)
|
CONF_FLOOR_TEMP, CONF_PRECISION, DATA_GATEWAYS, DATA_OPENTHERM_GW)
|
||||||
@@ -19,13 +19,14 @@ from .const import (
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
|
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass, config, async_add_entities, discovery_info=None):
|
hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Set up the opentherm_gw device."""
|
"""Set up the opentherm_gw device."""
|
||||||
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info]
|
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info]
|
||||||
|
|
||||||
gateway = OpenThermClimate(gw_dev)
|
gateway = OpenThermClimate(gw_dev)
|
||||||
async_add_entities([gateway])
|
async_add_entities([gateway])
|
||||||
|
|
||||||
@@ -36,12 +37,10 @@ class OpenThermClimate(ClimateDevice):
|
|||||||
def __init__(self, gw_dev):
|
def __init__(self, gw_dev):
|
||||||
"""Initialize the device."""
|
"""Initialize the device."""
|
||||||
self._gateway = gw_dev
|
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.friendly_name = gw_dev.name
|
||||||
self.floor_temp = gw_dev.climate_config[CONF_FLOOR_TEMP]
|
self.floor_temp = gw_dev.climate_config[CONF_FLOOR_TEMP]
|
||||||
self.temp_precision = gw_dev.climate_config.get(CONF_PRECISION)
|
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._current_temperature = None
|
||||||
self._new_target_temperature = None
|
self._new_target_temperature = None
|
||||||
self._target_temperature = None
|
self._target_temperature = None
|
||||||
@@ -63,13 +62,15 @@ class OpenThermClimate(ClimateDevice):
|
|||||||
flame_on = status.get(gw_vars.DATA_SLAVE_FLAME_ON)
|
flame_on = status.get(gw_vars.DATA_SLAVE_FLAME_ON)
|
||||||
cooling_active = status.get(gw_vars.DATA_SLAVE_COOLING_ACTIVE)
|
cooling_active = status.get(gw_vars.DATA_SLAVE_COOLING_ACTIVE)
|
||||||
if ch_active and flame_on:
|
if ch_active and flame_on:
|
||||||
self._current_operation = STATE_HEAT
|
self._current_operation = HVAC_MODE_HEAT
|
||||||
elif cooling_active:
|
elif cooling_active:
|
||||||
self._current_operation = STATE_COOL
|
self._current_operation = HVAC_MODE_COOL
|
||||||
else:
|
else:
|
||||||
self._current_operation = STATE_IDLE
|
self._current_operation = HVAC_MODE_OFF
|
||||||
|
|
||||||
self._current_temperature = status.get(gw_vars.DATA_ROOM_TEMP)
|
self._current_temperature = status.get(gw_vars.DATA_ROOM_TEMP)
|
||||||
temp_upd = status.get(gw_vars.DATA_ROOM_SETPOINT)
|
temp_upd = status.get(gw_vars.DATA_ROOM_SETPOINT)
|
||||||
|
|
||||||
if self._target_temperature != temp_upd:
|
if self._target_temperature != temp_upd:
|
||||||
self._new_target_temperature = None
|
self._new_target_temperature = None
|
||||||
self._target_temperature = temp_upd
|
self._target_temperature = temp_upd
|
||||||
@@ -103,6 +104,11 @@ class OpenThermClimate(ClimateDevice):
|
|||||||
"""Return the friendly name."""
|
"""Return the friendly name."""
|
||||||
return self.friendly_name
|
return self.friendly_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return a unique ID."""
|
||||||
|
return self._gateway.gw_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def precision(self):
|
def precision(self):
|
||||||
"""Return the precision of the system."""
|
"""Return the precision of the system."""
|
||||||
@@ -123,7 +129,7 @@ class OpenThermClimate(ClimateDevice):
|
|||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return self._current_operation
|
return self._current_operation
|
||||||
|
|
||||||
@@ -151,9 +157,19 @@ class OpenThermClimate(ClimateDevice):
|
|||||||
return self.precision
|
return self.precision
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_away_mode_on(self):
|
def preset_mode(self):
|
||||||
"""Return true if away mode is on."""
|
"""Return current preset mode."""
|
||||||
return self._away_state_a or self._away_state_b
|
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):
|
async def async_set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||||
from homeassistant.components.climate.const import (
|
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 (
|
from homeassistant.const import (
|
||||||
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, PRECISION_TENTHS, TEMP_FAHRENHEIT,
|
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, PRECISION_TENTHS, TEMP_FAHRENHEIT,
|
||||||
ATTR_TEMPERATURE)
|
ATTR_TEMPERATURE)
|
||||||
@@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
|
|
||||||
pdp = proliphix.PDP(host, username, password)
|
pdp = proliphix.PDP(host, username, password)
|
||||||
|
|
||||||
add_entities([ProliphixThermostat(pdp)])
|
add_entities([ProliphixThermostat(pdp)], True)
|
||||||
|
|
||||||
|
|
||||||
class ProliphixThermostat(ClimateDevice):
|
class ProliphixThermostat(ClimateDevice):
|
||||||
@@ -37,7 +37,6 @@ class ProliphixThermostat(ClimateDevice):
|
|||||||
def __init__(self, pdp):
|
def __init__(self, pdp):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
self._pdp = pdp
|
self._pdp = pdp
|
||||||
self._pdp.update()
|
|
||||||
self._name = self._pdp.name
|
self._name = self._pdp.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -91,15 +90,20 @@ class ProliphixThermostat(ClimateDevice):
|
|||||||
return self._pdp.setback
|
return self._pdp.setback
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return the current state of the thermostat."""
|
"""Return the current state of the thermostat."""
|
||||||
state = self._pdp.hvac_state
|
state = self._pdp.hvac_mode
|
||||||
if state in (1, 2):
|
if state in (1, 2):
|
||||||
return STATE_IDLE
|
return HVAC_MODE_OFF
|
||||||
if state == 3:
|
if state == 3:
|
||||||
return STATE_HEAT
|
return HVAC_MODE_HEAT
|
||||||
if state == 6:
|
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):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import datetime
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
import radiotherm
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||||
from homeassistant.components.climate.const import (
|
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_TARGET_TEMPERATURE,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE)
|
SUPPORT_FAN_MODE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, CONF_HOST, PRECISION_HALVES, TEMP_FAHRENHEIT, STATE_ON,
|
ATTR_TEMPERATURE, CONF_HOST, PRECISION_HALVES, TEMP_FAHRENHEIT, STATE_ON)
|
||||||
STATE_OFF)
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -20,37 +20,35 @@ ATTR_FAN = 'fan'
|
|||||||
ATTR_MODE = 'mode'
|
ATTR_MODE = 'mode'
|
||||||
|
|
||||||
CONF_HOLD_TEMP = 'hold_temp'
|
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"
|
STATE_CIRCULATE = "circulate"
|
||||||
|
|
||||||
OPERATION_LIST = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF]
|
OPERATION_LIST = [HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT,
|
||||||
CT30_FAN_OPERATION_LIST = [STATE_ON, STATE_AUTO]
|
HVAC_MODE_OFF]
|
||||||
CT80_FAN_OPERATION_LIST = [STATE_ON, STATE_CIRCULATE, STATE_AUTO]
|
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
|
# Mappings from radiotherm json data codes to and from HASS state
|
||||||
# flags. CODE is the thermostat integer code and these map to and
|
# flags. CODE is the thermostat integer code and these map to and
|
||||||
# from HASS state flags.
|
# from HASS state flags.
|
||||||
|
|
||||||
# Programmed temperature mode of the thermostat.
|
# 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()}
|
TEMP_MODE_TO_CODE = {v: k for k, v in CODE_TO_TEMP_MODE.items()}
|
||||||
|
|
||||||
# Programmed fan mode (circulate is supported by CT80 models)
|
# 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()}
|
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
|
# Active thermostat state (is it heating or cooling?). In the future
|
||||||
# this should probably made into heat and cool binary sensors.
|
# 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
|
# 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.
|
# 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):
|
def round_temp(temperature):
|
||||||
@@ -65,22 +63,13 @@ def round_temp(temperature):
|
|||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Optional(CONF_HOST): vol.All(cv.ensure_list, [cv.string]),
|
vol.Optional(CONF_HOST): vol.All(cv.ensure_list, [cv.string]),
|
||||||
vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean,
|
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_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||||
SUPPORT_FAN_MODE | SUPPORT_AWAY_MODE)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up the Radio Thermostat."""
|
"""Set up the Radio Thermostat."""
|
||||||
import radiotherm
|
|
||||||
|
|
||||||
hosts = []
|
hosts = []
|
||||||
if CONF_HOST in config:
|
if CONF_HOST in config:
|
||||||
hosts = config[CONF_HOST]
|
hosts = config[CONF_HOST]
|
||||||
@@ -92,16 +81,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
hold_temp = config.get(CONF_HOLD_TEMP)
|
hold_temp = config.get(CONF_HOLD_TEMP)
|
||||||
away_temps = [
|
|
||||||
config.get(CONF_AWAY_TEMPERATURE_HEAT),
|
|
||||||
config.get(CONF_AWAY_TEMPERATURE_COOL)
|
|
||||||
]
|
|
||||||
tstats = []
|
tstats = []
|
||||||
|
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
try:
|
try:
|
||||||
tstat = radiotherm.get_thermostat(host)
|
tstat = radiotherm.get_thermostat(host)
|
||||||
tstats.append(RadioThermostat(tstat, hold_temp, away_temps))
|
tstats.append(RadioThermostat(tstat, hold_temp))
|
||||||
except OSError:
|
except OSError:
|
||||||
_LOGGER.exception("Unable to connect to Radio Thermostat: %s",
|
_LOGGER.exception("Unable to connect to Radio Thermostat: %s",
|
||||||
host)
|
host)
|
||||||
@@ -112,12 +97,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
class RadioThermostat(ClimateDevice):
|
class RadioThermostat(ClimateDevice):
|
||||||
"""Representation of a Radio Thermostat."""
|
"""Representation of a Radio Thermostat."""
|
||||||
|
|
||||||
def __init__(self, device, hold_temp, away_temps):
|
def __init__(self, device, hold_temp):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
self.device = device
|
self.device = device
|
||||||
self._target_temperature = None
|
self._target_temperature = None
|
||||||
self._current_temperature = None
|
self._current_temperature = None
|
||||||
self._current_operation = STATE_IDLE
|
self._current_operation = HVAC_MODE_OFF
|
||||||
self._name = None
|
self._name = None
|
||||||
self._fmode = None
|
self._fmode = None
|
||||||
self._fstate = None
|
self._fstate = None
|
||||||
@@ -125,12 +110,9 @@ class RadioThermostat(ClimateDevice):
|
|||||||
self._tstate = None
|
self._tstate = None
|
||||||
self._hold_temp = hold_temp
|
self._hold_temp = hold_temp
|
||||||
self._hold_set = False
|
self._hold_set = False
|
||||||
self._away = False
|
|
||||||
self._away_temps = away_temps
|
|
||||||
self._prev_temp = None
|
self._prev_temp = None
|
||||||
|
|
||||||
# Fan circulate mode is only supported by the CT80 models.
|
# Fan circulate mode is only supported by the CT80 models.
|
||||||
import radiotherm
|
|
||||||
self._is_model_ct80 = isinstance(
|
self._is_model_ct80 = isinstance(
|
||||||
self.device, radiotherm.thermostat.CT80)
|
self.device, radiotherm.thermostat.CT80)
|
||||||
|
|
||||||
@@ -172,14 +154,14 @@ class RadioThermostat(ClimateDevice):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""List of available fan modes."""
|
"""List of available fan modes."""
|
||||||
if self._is_model_ct80:
|
if self._is_model_ct80:
|
||||||
return CT80_FAN_OPERATION_LIST
|
return CT80_FAN_OPERATION_LIST
|
||||||
return CT30_FAN_OPERATION_LIST
|
return CT30_FAN_OPERATION_LIST
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return whether the fan is on."""
|
"""Return whether the fan is on."""
|
||||||
return self._fmode
|
return self._fmode
|
||||||
|
|
||||||
@@ -195,12 +177,12 @@ class RadioThermostat(ClimateDevice):
|
|||||||
return self._current_temperature
|
return self._current_temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return the current operation. head, cool idle."""
|
"""Return the current operation. head, cool idle."""
|
||||||
return self._current_operation
|
return self._current_operation
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the operation modes list."""
|
"""Return the operation modes list."""
|
||||||
return OPERATION_LIST
|
return OPERATION_LIST
|
||||||
|
|
||||||
@@ -209,16 +191,6 @@ class RadioThermostat(ClimateDevice):
|
|||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
return self._target_temperature
|
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):
|
def update(self):
|
||||||
"""Update and validate the data from the thermostat."""
|
"""Update and validate the data from the thermostat."""
|
||||||
# Radio thermostats are very slow, and sometimes don't respond
|
# Radio thermostats are very slow, and sometimes don't respond
|
||||||
@@ -235,7 +207,6 @@ class RadioThermostat(ClimateDevice):
|
|||||||
self._name = self.device.name['raw']
|
self._name = self.device.name['raw']
|
||||||
|
|
||||||
# Request the current state from the thermostat.
|
# Request the current state from the thermostat.
|
||||||
import radiotherm
|
|
||||||
try:
|
try:
|
||||||
data = self.device.tstat['raw']
|
data = self.device.tstat['raw']
|
||||||
except radiotherm.validate.RadiothermTstatError:
|
except radiotherm.validate.RadiothermTstatError:
|
||||||
@@ -253,20 +224,20 @@ class RadioThermostat(ClimateDevice):
|
|||||||
self._tstate = CODE_TO_TEMP_STATE[data['tstate']]
|
self._tstate = CODE_TO_TEMP_STATE[data['tstate']]
|
||||||
|
|
||||||
self._current_operation = self._tmode
|
self._current_operation = self._tmode
|
||||||
if self._tmode == STATE_COOL:
|
if self._tmode == HVAC_MODE_COOL:
|
||||||
self._target_temperature = data['t_cool']
|
self._target_temperature = data['t_cool']
|
||||||
elif self._tmode == STATE_HEAT:
|
elif self._tmode == HVAC_MODE_HEAT:
|
||||||
self._target_temperature = data['t_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
|
# 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
|
# active. If it's idle, we don't know what to do with the target
|
||||||
# temperature.
|
# temperature.
|
||||||
if self._tstate == STATE_COOL:
|
if self._tstate == HVAC_MODE_COOL:
|
||||||
self._target_temperature = data['t_cool']
|
self._target_temperature = data['t_cool']
|
||||||
elif self._tstate == STATE_HEAT:
|
elif self._tstate == HVAC_MODE_HEAT:
|
||||||
self._target_temperature = data['t_heat']
|
self._target_temperature = data['t_heat']
|
||||||
else:
|
else:
|
||||||
self._current_operation = STATE_IDLE
|
self._current_operation = HVAC_MODE_OFF
|
||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
@@ -276,20 +247,20 @@ class RadioThermostat(ClimateDevice):
|
|||||||
|
|
||||||
temperature = round_temp(temperature)
|
temperature = round_temp(temperature)
|
||||||
|
|
||||||
if self._current_operation == STATE_COOL:
|
if self._current_operation == HVAC_MODE_COOL:
|
||||||
self.device.t_cool = temperature
|
self.device.t_cool = temperature
|
||||||
elif self._current_operation == STATE_HEAT:
|
elif self._current_operation == HVAC_MODE_HEAT:
|
||||||
self.device.t_heat = temperature
|
self.device.t_heat = temperature
|
||||||
elif self._current_operation == STATE_AUTO:
|
elif self._current_operation == HVAC_MODE_AUTO:
|
||||||
if self._tstate == STATE_COOL:
|
if self._tstate == HVAC_MODE_COOL:
|
||||||
self.device.t_cool = temperature
|
self.device.t_cool = temperature
|
||||||
elif self._tstate == STATE_HEAT:
|
elif self._tstate == HVAC_MODE_HEAT:
|
||||||
self.device.t_heat = temperature
|
self.device.t_heat = temperature
|
||||||
|
|
||||||
# Only change the hold if requested or if hold mode was turned
|
# Only change the hold if requested or if hold mode was turned
|
||||||
# on and we haven't set it yet.
|
# on and we haven't set it yet.
|
||||||
if kwargs.get('hold_changed', False) or not self._hold_set:
|
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.device.hold = 1
|
||||||
self._hold_set = True
|
self._hold_set = True
|
||||||
else:
|
else:
|
||||||
@@ -306,34 +277,13 @@ class RadioThermostat(ClimateDevice):
|
|||||||
'minute': now.minute
|
'minute': now.minute
|
||||||
}
|
}
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set operation mode (auto, cool, heat, off)."""
|
"""Set operation mode (auto, cool, heat, off)."""
|
||||||
if operation_mode in (STATE_OFF, STATE_AUTO):
|
if hvac_mode in (HVAC_MODE_OFF, HVAC_MODE_AUTO):
|
||||||
self.device.tmode = TEMP_MODE_TO_CODE[operation_mode]
|
self.device.tmode = TEMP_MODE_TO_CODE[hvac_mode]
|
||||||
|
|
||||||
# Setting t_cool or t_heat automatically changes tmode.
|
# 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
|
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
|
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 aiohttp
|
||||||
import async_timeout
|
import async_timeout
|
||||||
import voluptuous as vol
|
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 (
|
from homeassistant.components.climate.const import (
|
||||||
DOMAIN, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
|
HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_DRY,
|
||||||
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE,
|
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
|
||||||
SUPPORT_ON_OFF, STATE_HEAT, STATE_COOL, STATE_FAN_ONLY, STATE_DRY,
|
SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||||
STATE_AUTO)
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, ATTR_STATE, ATTR_TEMPERATURE, CONF_API_KEY, CONF_ID,
|
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.exceptions import PlatformNotReady
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.util.temperature import convert as convert_temperature
|
from homeassistant.util.temperature import convert as convert_temperature
|
||||||
|
|
||||||
|
from .const import DOMAIN as SENSIBO_DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ALL = ['all']
|
ALL = ['all']
|
||||||
TIMEOUT = 10
|
TIMEOUT = 10
|
||||||
|
|
||||||
SERVICE_ASSUME_STATE = 'sensibo_assume_state'
|
SERVICE_ASSUME_STATE = 'assume_state'
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_API_KEY): cv.string,
|
vol.Required(CONF_API_KEY): cv.string,
|
||||||
@@ -45,18 +47,16 @@ _INITIAL_FETCH_FIELDS = 'id,' + _FETCH_FIELDS
|
|||||||
|
|
||||||
FIELD_TO_FLAG = {
|
FIELD_TO_FLAG = {
|
||||||
'fanLevel': SUPPORT_FAN_MODE,
|
'fanLevel': SUPPORT_FAN_MODE,
|
||||||
'mode': SUPPORT_OPERATION_MODE,
|
|
||||||
'swing': SUPPORT_SWING_MODE,
|
'swing': SUPPORT_SWING_MODE,
|
||||||
'targetTemperature': SUPPORT_TARGET_TEMPERATURE,
|
'targetTemperature': SUPPORT_TARGET_TEMPERATURE,
|
||||||
'on': SUPPORT_ON_OFF,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SENSIBO_TO_HA = {
|
SENSIBO_TO_HA = {
|
||||||
"cool": STATE_COOL,
|
"cool": HVAC_MODE_COOL,
|
||||||
"heat": STATE_HEAT,
|
"heat": HVAC_MODE_HEAT,
|
||||||
"fan": STATE_FAN_ONLY,
|
"fan": HVAC_MODE_FAN_ONLY,
|
||||||
"auto": STATE_AUTO,
|
"auto": HVAC_MODE_HEAT_COOL,
|
||||||
"dry": STATE_DRY
|
"dry": HVAC_MODE_DRY
|
||||||
}
|
}
|
||||||
|
|
||||||
HA_TO_SENSIBO = {value: key for key, value in SENSIBO_TO_HA.items()}
|
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,
|
async def async_setup_platform(hass, config, async_add_entities,
|
||||||
discovery_info=None):
|
discovery_info=None):
|
||||||
"""Set up Sensibo devices."""
|
"""Set up Sensibo devices."""
|
||||||
import pysensibo
|
|
||||||
|
|
||||||
client = pysensibo.SensiboClient(
|
client = pysensibo.SensiboClient(
|
||||||
config[CONF_API_KEY], session=async_get_clientsession(hass),
|
config[CONF_API_KEY], session=async_get_clientsession(hass),
|
||||||
timeout=TIMEOUT)
|
timeout=TIMEOUT)
|
||||||
@@ -82,29 +80,32 @@ async def async_setup_platform(hass, config, async_add_entities,
|
|||||||
_LOGGER.exception('Failed to connect to Sensibo servers.')
|
_LOGGER.exception('Failed to connect to Sensibo servers.')
|
||||||
raise PlatformNotReady
|
raise PlatformNotReady
|
||||||
|
|
||||||
if devices:
|
if not devices:
|
||||||
async_add_entities(devices)
|
return
|
||||||
|
|
||||||
async def async_assume_state(service):
|
async_add_entities(devices)
|
||||||
"""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
|
|
||||||
|
|
||||||
update_tasks = []
|
async def async_assume_state(service):
|
||||||
for climate in target_climate:
|
"""Set state according to external service call.."""
|
||||||
await climate.async_assume_state(
|
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||||
service.data.get(ATTR_STATE))
|
if entity_ids:
|
||||||
update_tasks.append(climate.async_update_ha_state(True))
|
target_climate = [device for device in devices
|
||||||
|
if device.entity_id in entity_ids]
|
||||||
|
else:
|
||||||
|
target_climate = devices
|
||||||
|
|
||||||
if update_tasks:
|
update_tasks = []
|
||||||
await asyncio.wait(update_tasks)
|
for climate in target_climate:
|
||||||
hass.services.async_register(
|
await climate.async_assume_state(
|
||||||
DOMAIN, SERVICE_ASSUME_STATE, async_assume_state,
|
service.data.get(ATTR_STATE))
|
||||||
schema=ASSUME_STATE_SCHEMA)
|
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):
|
class SensiboClimate(ClimateDevice):
|
||||||
@@ -136,6 +137,7 @@ class SensiboClimate(ClimateDevice):
|
|||||||
capabilities = data['remoteCapabilities']
|
capabilities = data['remoteCapabilities']
|
||||||
self._operations = [SENSIBO_TO_HA[mode] for mode
|
self._operations = [SENSIBO_TO_HA[mode] for mode
|
||||||
in capabilities['modes']]
|
in capabilities['modes']]
|
||||||
|
self._operations.append(HVAC_MODE_OFF)
|
||||||
self._current_capabilities = \
|
self._current_capabilities = \
|
||||||
capabilities['modes'][self._ac_states['mode']]
|
capabilities['modes'][self._ac_states['mode']]
|
||||||
temperature_unit_key = data.get('temperatureUnit') or \
|
temperature_unit_key = data.get('temperatureUnit') or \
|
||||||
@@ -189,7 +191,7 @@ class SensiboClimate(ClimateDevice):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return SENSIBO_TO_HA.get(self._ac_states['mode'])
|
return SENSIBO_TO_HA.get(self._ac_states['mode'])
|
||||||
|
|
||||||
@@ -214,27 +216,27 @@ class SensiboClimate(ClimateDevice):
|
|||||||
self.temperature_unit)
|
self.temperature_unit)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""List of available operation modes."""
|
"""List of available operation modes."""
|
||||||
return self._operations
|
return self._operations
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self._ac_states.get('fanLevel')
|
return self._ac_states.get('fanLevel')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""List of available fan modes."""
|
"""List of available fan modes."""
|
||||||
return self._current_capabilities.get('fanLevels')
|
return self._current_capabilities.get('fanLevels')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_swing_mode(self):
|
def swing_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self._ac_states.get('swing')
|
return self._ac_states.get('swing')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def swing_list(self):
|
def swing_modes(self):
|
||||||
"""List of available swing modes."""
|
"""List of available swing modes."""
|
||||||
return self._current_capabilities.get('swing')
|
return self._current_capabilities.get('swing')
|
||||||
|
|
||||||
@@ -243,11 +245,6 @@ class SensiboClimate(ClimateDevice):
|
|||||||
"""Return the name of the entity."""
|
"""Return the name of the entity."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
|
||||||
def is_on(self):
|
|
||||||
"""Return true if AC is on."""
|
|
||||||
return self._ac_states['on']
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
"""Return the minimum temperature."""
|
"""Return the minimum temperature."""
|
||||||
@@ -294,11 +291,23 @@ class SensiboClimate(ClimateDevice):
|
|||||||
await self._client.async_set_ac_state_property(
|
await self._client.async_set_ac_state_property(
|
||||||
self._id, 'fanLevel', fan_mode, self._ac_states)
|
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."""
|
"""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):
|
with async_timeout.timeout(TIMEOUT):
|
||||||
await self._client.async_set_ac_state_property(
|
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)
|
self._ac_states)
|
||||||
|
|
||||||
async def async_set_swing_mode(self, swing_mode):
|
async def async_set_swing_mode(self, swing_mode):
|
||||||
@@ -307,40 +316,29 @@ class SensiboClimate(ClimateDevice):
|
|||||||
await self._client.async_set_ac_state_property(
|
await self._client.async_set_ac_state_property(
|
||||||
self._id, 'swing', swing_mode, self._ac_states)
|
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):
|
async def async_assume_state(self, state):
|
||||||
"""Set external state."""
|
"""Set external state."""
|
||||||
change_needed = (state != STATE_OFF and not self.is_on) \
|
change_needed = \
|
||||||
or (state == STATE_OFF and self.is_on)
|
(state != HVAC_MODE_OFF and not self._ac_states['on']) \
|
||||||
|
or (state == HVAC_MODE_OFF and self._ac_states['on'])
|
||||||
|
|
||||||
if change_needed:
|
if change_needed:
|
||||||
with async_timeout.timeout(TIMEOUT):
|
with async_timeout.timeout(TIMEOUT):
|
||||||
await self._client.async_set_ac_state_property(
|
await self._client.async_set_ac_state_property(
|
||||||
self._id,
|
self._id,
|
||||||
'on',
|
'on',
|
||||||
state != STATE_OFF, # value
|
state != HVAC_MODE_OFF, # value
|
||||||
self._ac_states,
|
self._ac_states,
|
||||||
True # assumed_state
|
True # assumed_state
|
||||||
)
|
)
|
||||||
|
|
||||||
if state in [STATE_ON, STATE_OFF]:
|
if state in [STATE_ON, HVAC_MODE_OFF]:
|
||||||
self._external_state = None
|
self._external_state = None
|
||||||
else:
|
else:
|
||||||
self._external_state = state
|
self._external_state = state
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Retrieve latest state."""
|
"""Retrieve latest state."""
|
||||||
import pysensibo
|
|
||||||
try:
|
try:
|
||||||
with async_timeout.timeout(TIMEOUT):
|
with async_timeout.timeout(TIMEOUT):
|
||||||
data = await self._client.async_get_device(
|
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 (
|
from homeassistant.components.climate import (
|
||||||
DOMAIN as CLIMATE_DOMAIN, ClimateDevice)
|
DOMAIN as CLIMATE_DOMAIN, ClimateDevice)
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||||
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
|
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, HVAC_MODE_AUTO,
|
||||||
SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
|
HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT,
|
||||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH,
|
HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
|
||||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
|
||||||
|
|
||||||
from . import SmartThingsEntity
|
from . import SmartThingsEntity
|
||||||
from .const import DATA_BROKERS, DOMAIN
|
from .const import DATA_BROKERS, DOMAIN
|
||||||
|
|
||||||
ATTR_OPERATION_STATE = 'operation_state'
|
ATTR_OPERATION_STATE = 'operation_state'
|
||||||
MODE_TO_STATE = {
|
MODE_TO_STATE = {
|
||||||
'auto': STATE_AUTO,
|
'auto': HVAC_MODE_HEAT_COOL,
|
||||||
'cool': STATE_COOL,
|
'cool': HVAC_MODE_COOL,
|
||||||
'eco': STATE_ECO,
|
'eco': HVAC_MODE_AUTO,
|
||||||
'rush hour': STATE_ECO,
|
'rush hour': HVAC_MODE_AUTO,
|
||||||
'emergency heat': STATE_HEAT,
|
'emergency heat': HVAC_MODE_HEAT,
|
||||||
'heat': STATE_HEAT,
|
'heat': HVAC_MODE_HEAT,
|
||||||
'off': STATE_OFF
|
'off': HVAC_MODE_OFF
|
||||||
}
|
}
|
||||||
STATE_TO_MODE = {
|
STATE_TO_MODE = {
|
||||||
STATE_AUTO: 'auto',
|
HVAC_MODE_HEAT_COOL: 'auto',
|
||||||
STATE_COOL: 'cool',
|
HVAC_MODE_COOL: 'cool',
|
||||||
STATE_ECO: 'eco',
|
HVAC_MODE_HEAT: 'heat',
|
||||||
STATE_HEAT: 'heat',
|
HVAC_MODE_OFF: 'off'
|
||||||
STATE_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 = {
|
AC_MODE_TO_STATE = {
|
||||||
'auto': STATE_AUTO,
|
'auto': HVAC_MODE_HEAT_COOL,
|
||||||
'cool': STATE_COOL,
|
'cool': HVAC_MODE_COOL,
|
||||||
'dry': STATE_DRY,
|
'dry': HVAC_MODE_DRY,
|
||||||
'coolClean': STATE_COOL,
|
'coolClean': HVAC_MODE_COOL,
|
||||||
'dryClean': STATE_DRY,
|
'dryClean': HVAC_MODE_DRY,
|
||||||
'heat': STATE_HEAT,
|
'heat': HVAC_MODE_HEAT,
|
||||||
'heatClean': STATE_HEAT,
|
'heatClean': HVAC_MODE_HEAT,
|
||||||
'fanOnly': STATE_FAN_ONLY
|
'fanOnly': HVAC_MODE_FAN_ONLY
|
||||||
}
|
}
|
||||||
STATE_TO_AC_MODE = {
|
STATE_TO_AC_MODE = {
|
||||||
STATE_AUTO: 'auto',
|
HVAC_MODE_HEAT_COOL: 'auto',
|
||||||
STATE_COOL: 'cool',
|
HVAC_MODE_COOL: 'cool',
|
||||||
STATE_DRY: 'dry',
|
HVAC_MODE_DRY: 'dry',
|
||||||
STATE_HEAT: 'heat',
|
HVAC_MODE_HEAT: 'heat',
|
||||||
STATE_FAN_ONLY: 'fanOnly'
|
HVAC_MODE_FAN_ONLY: 'fanOnly'
|
||||||
}
|
}
|
||||||
|
|
||||||
UNIT_MAP = {
|
UNIT_MAP = {
|
||||||
@@ -139,14 +147,13 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||||||
"""Init the class."""
|
"""Init the class."""
|
||||||
super().__init__(device)
|
super().__init__(device)
|
||||||
self._supported_features = self._determine_features()
|
self._supported_features = self._determine_features()
|
||||||
self._current_operation = None
|
self._hvac_mode = None
|
||||||
self._operations = None
|
self._hvac_modes = None
|
||||||
|
|
||||||
def _determine_features(self):
|
def _determine_features(self):
|
||||||
flags = SUPPORT_OPERATION_MODE \
|
flags = \
|
||||||
| SUPPORT_TARGET_TEMPERATURE \
|
SUPPORT_TARGET_TEMPERATURE \
|
||||||
| SUPPORT_TARGET_TEMPERATURE_LOW \
|
| SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||||
| SUPPORT_TARGET_TEMPERATURE_HIGH
|
|
||||||
if self._device.get_capability(
|
if self._device.get_capability(
|
||||||
Capability.thermostat_fan_mode, Capability.thermostat):
|
Capability.thermostat_fan_mode, Capability.thermostat):
|
||||||
flags |= SUPPORT_FAN_MODE
|
flags |= SUPPORT_FAN_MODE
|
||||||
@@ -160,9 +167,9 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||||||
# the entity state ahead of receiving the confirming push updates
|
# the entity state ahead of receiving the confirming push updates
|
||||||
self.async_schedule_update_ha_state(True)
|
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."""
|
"""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)
|
await self._device.set_thermostat_mode(mode, set_status=True)
|
||||||
|
|
||||||
# State is set optimistically in the command above, therefore update
|
# 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):
|
async def async_set_temperature(self, **kwargs):
|
||||||
"""Set new operation mode and target temperatures."""
|
"""Set new operation mode and target temperatures."""
|
||||||
# Operation state
|
# Operation state
|
||||||
operation_state = kwargs.get(ATTR_OPERATION_MODE)
|
operation_state = kwargs.get(ATTR_HVAC_MODE)
|
||||||
if operation_state:
|
if operation_state:
|
||||||
mode = STATE_TO_MODE[operation_state]
|
mode = STATE_TO_MODE[operation_state]
|
||||||
await self._device.set_thermostat_mode(mode, set_status=True)
|
await self._device.set_thermostat_mode(mode, set_status=True)
|
||||||
@@ -181,9 +188,9 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||||||
# Heat/cool setpoint
|
# Heat/cool setpoint
|
||||||
heating_setpoint = None
|
heating_setpoint = None
|
||||||
cooling_setpoint = None
|
cooling_setpoint = None
|
||||||
if self.current_operation == STATE_HEAT:
|
if self.hvac_mode == HVAC_MODE_HEAT:
|
||||||
heating_setpoint = kwargs.get(ATTR_TEMPERATURE)
|
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)
|
cooling_setpoint = kwargs.get(ATTR_TEMPERATURE)
|
||||||
else:
|
else:
|
||||||
heating_setpoint = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
heating_setpoint = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
||||||
@@ -204,10 +211,10 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Update the attributes of the climate device."""
|
"""Update the attributes of the climate device."""
|
||||||
thermostat_mode = self._device.status.thermostat_mode
|
thermostat_mode = self._device.status.thermostat_mode
|
||||||
self._current_operation = MODE_TO_STATE.get(thermostat_mode)
|
self._hvac_mode = MODE_TO_STATE.get(thermostat_mode)
|
||||||
if self._current_operation is None:
|
if self._hvac_mode is None:
|
||||||
_LOGGER.debug('Device %s (%s) returned an invalid'
|
_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)
|
self._device.device_id, thermostat_mode)
|
||||||
|
|
||||||
supported_modes = self._device.status.supported_thermostat_modes
|
supported_modes = self._device.status.supported_thermostat_modes
|
||||||
@@ -222,49 +229,47 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||||||
'supported thermostat mode: %s',
|
'supported thermostat mode: %s',
|
||||||
self._device.label, self._device.device_id,
|
self._device.label, self._device.device_id,
|
||||||
mode)
|
mode)
|
||||||
self._operations = operations
|
self._hvac_modes = operations
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug('Device %s (%s) returned invalid supported '
|
_LOGGER.debug('Device %s (%s) returned invalid supported '
|
||||||
'thermostat modes: %s', self._device.label,
|
'thermostat modes: %s', self._device.label,
|
||||||
self._device.device_id, supported_modes)
|
self._device.device_id, supported_modes)
|
||||||
|
|
||||||
@property
|
|
||||||
def current_fan_mode(self):
|
|
||||||
"""Return the fan setting."""
|
|
||||||
return self._device.status.thermostat_fan_mode
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_humidity(self):
|
def current_humidity(self):
|
||||||
"""Return the current humidity."""
|
"""Return the current humidity."""
|
||||||
return self._device.status.humidity
|
return self._device.status.humidity
|
||||||
|
|
||||||
@property
|
|
||||||
def current_operation(self):
|
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
|
||||||
return self._current_operation
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
return self._device.status.temperature
|
return self._device.status.temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def fan_mode(self):
|
||||||
"""Return device specific state attributes."""
|
"""Return the fan setting."""
|
||||||
return {
|
return self._device.status.thermostat_fan_mode
|
||||||
ATTR_OPERATION_STATE:
|
|
||||||
self._device.status.thermostat_operating_state
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""Return the list of available fan modes."""
|
||||||
return self._device.status.supported_thermostat_fan_modes
|
return self._device.status.supported_thermostat_fan_modes
|
||||||
|
|
||||||
@property
|
@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 the list of available operation modes."""
|
||||||
return self._operations
|
return self._hvac_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
@@ -274,23 +279,23 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
"""Return the temperature we try to reach."""
|
"""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
|
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 self._device.status.heating_setpoint
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_high(self):
|
def target_temperature_high(self):
|
||||||
"""Return the highbound target temperature we try to reach."""
|
"""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 self._device.status.cooling_setpoint
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_low(self):
|
def target_temperature_low(self):
|
||||||
"""Return the lowbound target temperature we try to reach."""
|
"""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 self._device.status.heating_setpoint
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -307,7 +312,7 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
|
|||||||
def __init__(self, device):
|
def __init__(self, device):
|
||||||
"""Init the class."""
|
"""Init the class."""
|
||||||
super().__init__(device)
|
super().__init__(device)
|
||||||
self._operations = None
|
self._hvac_modes = None
|
||||||
|
|
||||||
async def async_set_fan_mode(self, fan_mode):
|
async def async_set_fan_mode(self, fan_mode):
|
||||||
"""Set new target 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
|
# the entity state ahead of receiving the confirming push updates
|
||||||
self.async_schedule_update_ha_state()
|
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."""
|
"""Set new target operation mode."""
|
||||||
await self._device.set_air_conditioner_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
|
# State is set optimistically in the command above, therefore update
|
||||||
# the entity state ahead of receiving the confirming push updates
|
# the entity state ahead of receiving the confirming push updates
|
||||||
self.async_schedule_update_ha_state()
|
self.async_schedule_update_ha_state()
|
||||||
@@ -328,9 +333,9 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
|
|||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
tasks = []
|
tasks = []
|
||||||
# operation mode
|
# operation mode
|
||||||
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
|
operation_mode = kwargs.get(ATTR_HVAC_MODE)
|
||||||
if operation_mode:
|
if operation_mode:
|
||||||
tasks.append(self.async_set_operation_mode(operation_mode))
|
tasks.append(self.async_set_hvac_mode(operation_mode))
|
||||||
# temperature
|
# temperature
|
||||||
tasks.append(self._device.set_cooling_setpoint(
|
tasks.append(self._device.set_cooling_setpoint(
|
||||||
kwargs[ATTR_TEMPERATURE], set_status=True))
|
kwargs[ATTR_TEMPERATURE], set_status=True))
|
||||||
@@ -339,20 +344,6 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
|
|||||||
# the entity state ahead of receiving the confirming push updates
|
# the entity state ahead of receiving the confirming push updates
|
||||||
self.async_schedule_update_ha_state()
|
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):
|
async def async_update(self):
|
||||||
"""Update the calculated fields of the AC."""
|
"""Update the calculated fields of the AC."""
|
||||||
operations = set()
|
operations = set()
|
||||||
@@ -364,17 +355,7 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
|
|||||||
_LOGGER.debug('Device %s (%s) returned an invalid supported '
|
_LOGGER.debug('Device %s (%s) returned an invalid supported '
|
||||||
'AC mode: %s', self._device.label,
|
'AC mode: %s', self._device.label,
|
||||||
self._device.device_id, mode)
|
self._device.device_id, mode)
|
||||||
self._operations = operations
|
self._hvac_modes = 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)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
@@ -407,25 +388,30 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
|
|||||||
return state_attributes
|
return state_attributes
|
||||||
|
|
||||||
@property
|
@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 the list of available fan modes."""
|
||||||
return self._device.status.supported_ac_fan_modes
|
return self._device.status.supported_ac_fan_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def hvac_mode(self):
|
||||||
"""Return true if on."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return self._device.status.switch
|
return AC_MODE_TO_STATE.get(self._device.status.air_conditioner_mode)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return self._operations
|
return self._hvac_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the supported features."""
|
"""Return the supported features."""
|
||||||
return SUPPORT_OPERATION_MODE | SUPPORT_TARGET_TEMPERATURE \
|
return SUPPORT_TARGET_TEMPERATURE \
|
||||||
| SUPPORT_FAN_MODE | SUPPORT_ON_OFF
|
| SUPPORT_FAN_MODE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_FAN_MODE,
|
HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
|
|
||||||
from . import DOMAIN as SPIDER_DOMAIN
|
from . import DOMAIN as SPIDER_DOMAIN
|
||||||
|
|
||||||
FAN_LIST = [
|
SUPPORT_FAN = [
|
||||||
'Auto',
|
'Auto',
|
||||||
'Low',
|
'Low',
|
||||||
'Medium',
|
'Medium',
|
||||||
@@ -20,15 +20,15 @@ FAN_LIST = [
|
|||||||
'Boost 30',
|
'Boost 30',
|
||||||
]
|
]
|
||||||
|
|
||||||
OPERATION_LIST = [
|
SUPPORT_HVAC = [
|
||||||
STATE_HEAT,
|
HVAC_MODE_HEAT,
|
||||||
STATE_COOL,
|
HVAC_MODE_COOL,
|
||||||
]
|
]
|
||||||
|
|
||||||
HA_STATE_TO_SPIDER = {
|
HA_STATE_TO_SPIDER = {
|
||||||
STATE_COOL: 'Cool',
|
HVAC_MODE_COOL: 'Cool',
|
||||||
STATE_HEAT: 'Heat',
|
HVAC_MODE_HEAT: 'Heat',
|
||||||
STATE_IDLE: 'Idle',
|
HVAC_MODE_OFF: 'Idle',
|
||||||
}
|
}
|
||||||
|
|
||||||
SPIDER_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_SPIDER.items()}
|
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."""
|
"""Return the list of supported features."""
|
||||||
supports = SUPPORT_TARGET_TEMPERATURE
|
supports = SUPPORT_TARGET_TEMPERATURE
|
||||||
|
|
||||||
if self.thermostat.has_operation_mode:
|
|
||||||
supports |= SUPPORT_OPERATION_MODE
|
|
||||||
|
|
||||||
if self.thermostat.has_fan_mode:
|
if self.thermostat.has_fan_mode:
|
||||||
supports |= SUPPORT_FAN_MODE
|
supports |= SUPPORT_FAN_MODE
|
||||||
|
|
||||||
@@ -108,14 +105,14 @@ class SpiderThermostat(ClimateDevice):
|
|||||||
return self.thermostat.maximum_temperature
|
return self.thermostat.maximum_temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return SPIDER_STATE_TO_HA[self.thermostat.operation_mode]
|
return SPIDER_STATE_TO_HA[self.thermostat.operation_mode]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return OPERATION_LIST
|
return SUPPORT_HVAC
|
||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
@@ -125,13 +122,13 @@ class SpiderThermostat(ClimateDevice):
|
|||||||
|
|
||||||
self.thermostat.set_temperature(temperature)
|
self.thermostat.set_temperature(temperature)
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set new target operation mode."""
|
"""Set new target operation mode."""
|
||||||
self.thermostat.set_operation_mode(
|
self.thermostat.set_operation_mode(
|
||||||
HA_STATE_TO_SPIDER.get(operation_mode))
|
HA_STATE_TO_SPIDER.get(hvac_mode))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self.thermostat.current_fan_speed
|
return self.thermostat.current_fan_speed
|
||||||
|
|
||||||
@@ -140,9 +137,9 @@ class SpiderThermostat(ClimateDevice):
|
|||||||
self.thermostat.set_fan_speed(fan_mode)
|
self.thermostat.set_fan_speed(fan_mode)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""List of available fan modes."""
|
"""List of available fan modes."""
|
||||||
return FAN_LIST
|
return SUPPORT_FAN
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Get the latest data."""
|
"""Get the latest data."""
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_ECO, STATE_MANUAL, SUPPORT_OPERATION_MODE,
|
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_ECO,
|
||||||
SUPPORT_TARGET_TEMPERATURE)
|
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS)
|
|
||||||
|
|
||||||
from . import DOMAIN as STE_DOMAIN
|
from . import DOMAIN as STE_DOMAIN
|
||||||
|
|
||||||
@@ -14,21 +13,39 @@ DEPENDENCIES = ['stiebel_eltron']
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
PRESET_DAY = 'day'
|
||||||
|
PRESET_SETBACK = 'setback'
|
||||||
|
PRESET_EMERGENCY = 'emergency'
|
||||||
|
|
||||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
|
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||||
OPERATION_MODES = [STATE_AUTO, STATE_MANUAL, STATE_ECO, STATE_OFF]
|
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.
|
# Mapping STIEBEL ELTRON states to homeassistant states/preset.
|
||||||
STE_TO_HA_STATE = {'AUTOMATIC': STATE_AUTO,
|
STE_TO_HA_HVAC = {
|
||||||
'MANUAL MODE': STATE_MANUAL,
|
'AUTOMATIC': HVAC_MODE_AUTO,
|
||||||
'STANDBY': STATE_ECO,
|
'MANUAL MODE': HVAC_MODE_HEAT,
|
||||||
'DAY MODE': STATE_ON,
|
'STANDBY': HVAC_MODE_AUTO,
|
||||||
'SETBACK MODE': STATE_ON,
|
'DAY MODE': HVAC_MODE_AUTO,
|
||||||
'DHW': STATE_OFF,
|
'SETBACK MODE': HVAC_MODE_AUTO,
|
||||||
'EMERGENCY OPERATION': STATE_ON}
|
'DHW': HVAC_MODE_OFF,
|
||||||
|
'EMERGENCY OPERATION': HVAC_MODE_AUTO
|
||||||
|
}
|
||||||
|
|
||||||
# Mapping homeassistant states to STIEBEL ELTRON states.
|
STE_TO_HA_PRESET = {
|
||||||
HA_TO_STE_STATE = {value: key for key, value in STE_TO_HA_STATE.items()}
|
'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):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
@@ -48,8 +65,7 @@ class StiebelEltron(ClimateDevice):
|
|||||||
self._target_temperature = None
|
self._target_temperature = None
|
||||||
self._current_temperature = None
|
self._current_temperature = None
|
||||||
self._current_humidity = None
|
self._current_humidity = None
|
||||||
self._operation_modes = OPERATION_MODES
|
self._operation = None
|
||||||
self._current_operation = None
|
|
||||||
self._filter_alarm = None
|
self._filter_alarm = None
|
||||||
self._force_update = False
|
self._force_update = False
|
||||||
self._ste_data = ste_data
|
self._ste_data = ste_data
|
||||||
@@ -68,7 +84,7 @@ class StiebelEltron(ClimateDevice):
|
|||||||
self._current_temperature = self._ste_data.api.get_current_temp()
|
self._current_temperature = self._ste_data.api.get_current_temp()
|
||||||
self._current_humidity = self._ste_data.api.get_current_humidity()
|
self._current_humidity = self._ste_data.api.get_current_humidity()
|
||||||
self._filter_alarm = self._ste_data.api.get_filter_alarm_status()
|
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,
|
_LOGGER.debug("Update %s, current temp: %s", self._name,
|
||||||
self._current_temperature)
|
self._current_temperature)
|
||||||
@@ -116,6 +132,41 @@ class StiebelEltron(ClimateDevice):
|
|||||||
"""Return the maximum temperature."""
|
"""Return the maximum temperature."""
|
||||||
return 30.0
|
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):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
target_temperature = kwargs.get(ATTR_TEMPERATURE)
|
target_temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
@@ -124,26 +175,10 @@ class StiebelEltron(ClimateDevice):
|
|||||||
self._ste_data.api.set_target_temp(target_temperature)
|
self._ste_data.api.set_target_temp(target_temperature)
|
||||||
self._force_update = True
|
self._force_update = True
|
||||||
|
|
||||||
@property
|
def set_preset_mode(self, preset_mode: str):
|
||||||
def current_humidity(self):
|
"""Set new preset mode."""
|
||||||
"""Return the current humidity."""
|
new_mode = HA_TO_STE_PRESET.get(preset_mode)
|
||||||
return float("{0:.1f}".format(self._current_humidity))
|
_LOGGER.debug("set_hvac_mode: %s -> %s", self._operation,
|
||||||
|
|
||||||
# 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,
|
|
||||||
new_mode)
|
new_mode)
|
||||||
self._ste_data.api.set_operation(new_mode)
|
self._ste_data.api.set_operation(new_mode)
|
||||||
self._force_update = True
|
self._force_update = True
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
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 (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS)
|
ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS)
|
||||||
from homeassistant.util.temperature import convert as convert_temperature
|
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_MIDDLE = 'MIDDLE'
|
||||||
CONST_MODE_FAN_LOW = 'LOW'
|
CONST_MODE_FAN_LOW = 'LOW'
|
||||||
|
|
||||||
FAN_MODES_LIST = {
|
FAN_MAP_TADO = {
|
||||||
CONST_MODE_FAN_HIGH: 'High',
|
'HIGH': FAN_HIGH,
|
||||||
CONST_MODE_FAN_MIDDLE: 'Middle',
|
'MIDDLE': FAN_MIDDLE,
|
||||||
CONST_MODE_FAN_LOW: 'Low',
|
'LOW': FAN_LOW,
|
||||||
CONST_MODE_OFF: 'Off',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OPERATION_LIST = {
|
HVAC_MAP_TADO = {
|
||||||
CONST_OVERLAY_MANUAL: 'Manual',
|
'MANUAL': HVAC_MODE_HEAT,
|
||||||
CONST_OVERLAY_TIMER: 'Timer',
|
'TIMER': HVAC_MODE_AUTO,
|
||||||
CONST_OVERLAY_TADO_MODE: 'Tado mode',
|
'TADO_MODE': HVAC_MODE_AUTO,
|
||||||
CONST_MODE_SMART_SCHEDULE: 'Smart schedule',
|
'SMART_SCHEDULE': HVAC_MODE_AUTO,
|
||||||
CONST_MODE_OFF: 'Off',
|
'OFF': HVAC_MODE_OFF
|
||||||
}
|
}
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
|
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||||
SUPPORT_ON_OFF)
|
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):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
@@ -159,41 +162,62 @@ class TadoClimate(ClimateDevice):
|
|||||||
return self._cur_temp
|
return self._cur_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current readable operation mode."""
|
"""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:
|
if self._cooling:
|
||||||
return "Cooling"
|
return CURRENT_HVAC_COOL
|
||||||
return OPERATION_LIST.get(self._current_operation)
|
return CURRENT_HVAC_HEAT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def fan_mode(self):
|
||||||
"""Return the list of available operation modes (readable)."""
|
|
||||||
return list(OPERATION_LIST.values())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_fan_mode(self):
|
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
if self.ac_mode:
|
if self.ac_mode:
|
||||||
return FAN_MODES_LIST.get(self._current_fan)
|
return FAN_MAP_TADO.get(self._current_fan)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""List of available fan modes."""
|
"""List of available fan modes."""
|
||||||
if self.ac_mode:
|
if self.ac_mode:
|
||||||
return list(FAN_MODES_LIST.values())
|
return SUPPORT_FAN
|
||||||
return None
|
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
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
"""Return the unit of measurement used by the platform."""
|
"""Return the unit of measurement used by the platform."""
|
||||||
return self._unit
|
return self._unit
|
||||||
|
|
||||||
@property
|
|
||||||
def is_away_mode_on(self):
|
|
||||||
"""Return true if away mode is on."""
|
|
||||||
return self._is_away
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_step(self):
|
def target_temperature_step(self):
|
||||||
"""Return the supported step of target temperature."""
|
"""Return the supported step of target temperature."""
|
||||||
@@ -204,27 +228,6 @@ class TadoClimate(ClimateDevice):
|
|||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
return self._target_temp
|
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):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
@@ -236,20 +239,25 @@ class TadoClimate(ClimateDevice):
|
|||||||
self._target_temp = temperature
|
self._target_temp = temperature
|
||||||
self._control_heating()
|
self._control_heating()
|
||||||
|
|
||||||
# pylint: disable=arguments-differ
|
def set_hvac_mode(self, hvac_mode):
|
||||||
def set_operation_mode(self, readable_operation_mode):
|
"""Set new target hvac mode."""
|
||||||
"""Set new operation mode."""
|
mode = None
|
||||||
operation_mode = CONST_MODE_SMART_SCHEDULE
|
|
||||||
|
|
||||||
for mode, readable in OPERATION_LIST.items():
|
if hvac_mode == HVAC_MODE_OFF:
|
||||||
if readable == readable_operation_mode:
|
mode = CONST_MODE_OFF
|
||||||
operation_mode = mode
|
elif hvac_mode == HVAC_MODE_AUTO:
|
||||||
break
|
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._overlay_mode = None
|
||||||
self._control_heating()
|
self._control_heating()
|
||||||
|
|
||||||
|
def set_preset_mode(self, preset_mode):
|
||||||
|
"""Set new preset mode."""
|
||||||
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
"""Return the minimum temperature."""
|
"""Return the minimum temperature."""
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ class TeslaDevice(Entity):
|
|||||||
"""Return the name of the device."""
|
"""Return the name of the device."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Return a unique ID."""
|
||||||
|
return self.tesla_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""Return the polling state."""
|
"""Return the polling state."""
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
"""Support for Tesla binary sensor."""
|
"""Support for Tesla binary sensor."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
ENTITY_ID_FORMAT, BinarySensorDevice)
|
|
||||||
|
|
||||||
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
||||||
|
|
||||||
@@ -25,7 +24,6 @@ class TeslaBinarySensor(TeslaDevice, BinarySensorDevice):
|
|||||||
"""Initialise of a Tesla binary sensor."""
|
"""Initialise of a Tesla binary sensor."""
|
||||||
super().__init__(tesla_device, controller)
|
super().__init__(tesla_device, controller)
|
||||||
self._state = False
|
self._state = False
|
||||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
|
||||||
self._sensor_type = sensor_type
|
self._sensor_type = sensor_type
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
"""Support for Tesla HVAC system."""
|
"""Support for Tesla HVAC system."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
|
||||||
|
|
||||||
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
OPERATION_LIST = [STATE_ON, STATE_OFF]
|
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||||
|
|
||||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
@@ -29,27 +26,31 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
|
|||||||
def __init__(self, tesla_device, controller):
|
def __init__(self, tesla_device, controller):
|
||||||
"""Initialize the Tesla device."""
|
"""Initialize the Tesla device."""
|
||||||
super().__init__(tesla_device, controller)
|
super().__init__(tesla_device, controller)
|
||||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
|
||||||
self._target_temperature = None
|
self._target_temperature = None
|
||||||
self._temperature = None
|
self._temperature = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
return SUPPORT_FLAGS
|
return SUPPORT_TARGET_TEMPERATURE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. On or Off."""
|
"""Return hvac operation ie. heat, cool mode.
|
||||||
mode = self.tesla_device.is_hvac_enabled()
|
|
||||||
if mode:
|
Need to be one of HVAC_MODE_*.
|
||||||
return OPERATION_LIST[0] # On
|
"""
|
||||||
return OPERATION_LIST[1] # Off
|
if self.tesla_device.is_hvac_enabled():
|
||||||
|
return HVAC_MODE_HEAT
|
||||||
|
return HVAC_MODE_OFF
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""List of available operation modes."""
|
"""Return the list of available hvac operation modes.
|
||||||
return OPERATION_LIST
|
|
||||||
|
Need to be a subset of HVAC_MODES.
|
||||||
|
"""
|
||||||
|
return SUPPORT_HVAC
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Call by the Tesla device callback to update state."""
|
"""Call by the Tesla device callback to update state."""
|
||||||
@@ -84,10 +85,10 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
|
|||||||
if temperature:
|
if temperature:
|
||||||
self.tesla_device.set_temperature(temperature)
|
self.tesla_device.set_temperature(temperature)
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set HVAC mode (auto, cool, heat, off)."""
|
"""Set new target hvac mode."""
|
||||||
_LOGGER.debug("Setting mode for: %s", self._name)
|
_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)
|
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)
|
self.tesla_device.set_status(True)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Support for Tesla door locks."""
|
"""Support for Tesla door locks."""
|
||||||
import logging
|
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 homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
|
||||||
|
|
||||||
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
||||||
@@ -23,7 +23,6 @@ class TeslaLock(TeslaDevice, LockDevice):
|
|||||||
"""Initialise of the lock."""
|
"""Initialise of the lock."""
|
||||||
self._state = None
|
self._state = None
|
||||||
super().__init__(tesla_device, controller)
|
super().__init__(tesla_device, controller)
|
||||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
|
||||||
|
|
||||||
def lock(self, **kwargs):
|
def lock(self, **kwargs):
|
||||||
"""Send the lock command."""
|
"""Send the lock command."""
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.sensor import ENTITY_ID_FORMAT
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
LENGTH_KILOMETERS, LENGTH_MILES, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
LENGTH_KILOMETERS, LENGTH_MILES, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
@@ -41,10 +40,13 @@ class TeslaSensor(TeslaDevice, Entity):
|
|||||||
|
|
||||||
if self.type:
|
if self.type:
|
||||||
self._name = '{} ({})'.format(self.tesla_device.name, self.type)
|
self._name = '{} ({})'.format(self.tesla_device.name, self.type)
|
||||||
self.entity_id = ENTITY_ID_FORMAT.format(
|
|
||||||
'{}_{}'.format(self.tesla_id, self.type))
|
@property
|
||||||
else:
|
def unique_id(self) -> str:
|
||||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
"""Return a unique ID."""
|
||||||
|
if self.type:
|
||||||
|
return "{}_{}".format(self.tesla_id, self.type)
|
||||||
|
return self.tesla_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Support for Tesla charger switches."""
|
"""Support for Tesla charger switches."""
|
||||||
import logging
|
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 homeassistant.const import STATE_OFF, STATE_ON
|
||||||
|
|
||||||
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
|
||||||
@@ -28,7 +28,6 @@ class ChargerSwitch(TeslaDevice, SwitchDevice):
|
|||||||
"""Initialise of the switch."""
|
"""Initialise of the switch."""
|
||||||
self._state = None
|
self._state = None
|
||||||
super().__init__(tesla_device, controller)
|
super().__init__(tesla_device, controller)
|
||||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Send the on command."""
|
"""Send the on command."""
|
||||||
@@ -60,7 +59,6 @@ class RangeSwitch(TeslaDevice, SwitchDevice):
|
|||||||
"""Initialise of the switch."""
|
"""Initialise of the switch."""
|
||||||
self._state = None
|
self._state = None
|
||||||
super().__init__(tesla_device, controller)
|
super().__init__(tesla_device, controller)
|
||||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Send the on command."""
|
"""Send the on command."""
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ from concurrent import futures
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from pytfiac import Tfiac
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT,
|
FAN_AUTO, FAN_HIGH, FAN_LOW, FAN_MEDIUM, HVAC_MODE_AUTO, HVAC_MODE_COOL,
|
||||||
SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
|
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF,
|
||||||
SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE)
|
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
|
from homeassistant.const import ATTR_TEMPERATURE, CONF_HOST, TEMP_FAHRENHEIT
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
@@ -23,22 +25,23 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
MIN_TEMP = 61
|
MIN_TEMP = 61
|
||||||
MAX_TEMP = 88
|
MAX_TEMP = 88
|
||||||
OPERATION_MAP = {
|
|
||||||
STATE_HEAT: 'heat',
|
HVAC_MAP = {
|
||||||
STATE_AUTO: 'selfFeel',
|
HVAC_MODE_HEAT: 'heat',
|
||||||
STATE_DRY: 'dehumi',
|
HVAC_MODE_AUTO: 'selfFeel',
|
||||||
STATE_FAN_ONLY: 'fan',
|
HVAC_MODE_DRY: 'dehumi',
|
||||||
STATE_COOL: 'cool',
|
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()}
|
HVAC_MAP_REV = {v: k for k, v in HVAC_MAP.items()}
|
||||||
FAN_LIST = ['Auto', 'Low', 'Middle', 'High']
|
|
||||||
SWING_LIST = [
|
SUPPORT_FAN = [FAN_AUTO, FAN_HIGH, FAN_MEDIUM, FAN_LOW]
|
||||||
'Off',
|
SUPPORT_SWING = [SWING_OFF, SWING_HORIZONTAL, SWING_VERTICAL, SWING_BOTH]
|
||||||
'Vertical',
|
|
||||||
'Horizontal',
|
SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_SWING_MODE |
|
||||||
'Both',
|
SUPPORT_TARGET_TEMPERATURE)
|
||||||
]
|
|
||||||
|
|
||||||
CURR_TEMP = 'current_temp'
|
CURR_TEMP = 'current_temp'
|
||||||
TARGET_TEMP = 'target_temp'
|
TARGET_TEMP = 'target_temp'
|
||||||
@@ -51,8 +54,6 @@ ON_MODE = 'is_on'
|
|||||||
async def async_setup_platform(hass, config, async_add_devices,
|
async def async_setup_platform(hass, config, async_add_devices,
|
||||||
discovery_info=None):
|
discovery_info=None):
|
||||||
"""Set up the TFIAC climate device."""
|
"""Set up the TFIAC climate device."""
|
||||||
from pytfiac import Tfiac
|
|
||||||
|
|
||||||
tfiac_client = Tfiac(config[CONF_HOST])
|
tfiac_client = Tfiac(config[CONF_HOST])
|
||||||
try:
|
try:
|
||||||
await tfiac_client.update()
|
await tfiac_client.update()
|
||||||
@@ -86,8 +87,7 @@ class TfiacClimate(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
return (SUPPORT_FAN_MODE | SUPPORT_ON_OFF | SUPPORT_OPERATION_MODE
|
return SUPPORT_FLAGS
|
||||||
| SUPPORT_SWING_MODE | SUPPORT_TARGET_TEMPERATURE)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
@@ -120,64 +120,62 @@ class TfiacClimate(ClimateDevice):
|
|||||||
return self._client.status['current_temp']
|
return self._client.status['current_temp']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return hvac operation ie. heat, cool mode.
|
||||||
operation = self._client.status['operation']
|
|
||||||
return OPERATION_MAP_REV.get(operation, operation)
|
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
|
@property
|
||||||
def is_on(self):
|
def hvac_modes(self):
|
||||||
"""Return true if on."""
|
"""Return the list of available hvac operation modes.
|
||||||
return self._client.status[ON_MODE] == 'on'
|
|
||||||
|
Need to be a subset of HVAC_MODES.
|
||||||
|
"""
|
||||||
|
return list(HVAC_MAP)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def fan_mode(self):
|
||||||
"""Return the list of available operation modes."""
|
|
||||||
return sorted(OPERATION_MAP)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_fan_mode(self):
|
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self._client.status['fan_mode']
|
return self._client.status['fan_mode'].lower()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""Return the list of available fan modes."""
|
||||||
return FAN_LIST
|
return SUPPORT_FAN
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_swing_mode(self):
|
def swing_mode(self):
|
||||||
"""Return the swing setting."""
|
"""Return the swing setting."""
|
||||||
return self._client.status['swing_mode']
|
return self._client.status['swing_mode'].lower()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def swing_list(self):
|
def swing_modes(self):
|
||||||
"""List of available swing modes."""
|
"""List of available swing modes."""
|
||||||
return SWING_LIST
|
return SUPPORT_SWING
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs):
|
async def async_set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
if kwargs.get(ATTR_TEMPERATURE) is not None:
|
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||||
await self._client.set_state(TARGET_TEMP,
|
if temp is not None:
|
||||||
kwargs.get(ATTR_TEMPERATURE))
|
await self._client.set_state(TARGET_TEMP, temp)
|
||||||
|
|
||||||
async def async_set_operation_mode(self, operation_mode):
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
"""Set new operation mode."""
|
"""Set new target hvac mode."""
|
||||||
await self._client.set_state(OPERATION_MODE,
|
if hvac_mode == HVAC_MODE_OFF:
|
||||||
OPERATION_MAP[operation_mode])
|
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):
|
async def async_set_fan_mode(self, fan_mode):
|
||||||
"""Set new 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):
|
async def async_set_swing_mode(self, swing_mode):
|
||||||
"""Set new swing mode."""
|
"""Set new swing mode."""
|
||||||
await self._client.set_swing(swing_mode)
|
await self._client.set_swing(swing_mode.capitalize())
|
||||||
|
|
||||||
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')
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ from typing import Any, Dict, List
|
|||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_COOL, STATE_ECO, STATE_HEAT, SUPPORT_OPERATION_MODE,
|
HVAC_MODE_HEAT, PRESET_AWAY, PRESET_COMFORT, PRESET_HOME, PRESET_SLEEP,
|
||||||
SUPPORT_TARGET_TEMPERATURE)
|
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
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__)
|
_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)
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
|
||||||
SCAN_INTERVAL = timedelta(seconds=300)
|
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 def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry,
|
||||||
async_add_entities) -> None:
|
async_add_entities) -> None:
|
||||||
@@ -64,20 +56,36 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateDevice):
|
|||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
return SUPPORT_FLAGS
|
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
|
@property
|
||||||
def temperature_unit(self) -> str:
|
def temperature_unit(self) -> str:
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self) -> str:
|
def preset_mode(self) -> str:
|
||||||
"""Return current operation i.e. comfort, home, away."""
|
"""Return the current preset mode, e.g., home, away, temp."""
|
||||||
return TOON_HA.get(self._state)
|
return self._state.lower()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self) -> List[str]:
|
def preset_modes(self) -> List[str]:
|
||||||
"""Return a list of available operation modes."""
|
"""Return a list of available preset modes."""
|
||||||
return list(HA_TOON.keys())
|
return SUPPORT_PRESET
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self) -> float:
|
def current_temperature(self) -> float:
|
||||||
@@ -111,9 +119,13 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateDevice):
|
|||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
self.toon.thermostat = temperature
|
self.toon.thermostat = temperature
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode: str) -> None:
|
def set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set new operation mode."""
|
"""Set new preset mode."""
|
||||||
self.toon.thermostat_state = HA_TOON[operation_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:
|
def update(self) -> None:
|
||||||
"""Update local state."""
|
"""Update local state."""
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
"""Platform for Roth Touchline heat pump controller."""
|
"""Platform for Roth Touchline heat pump controller."""
|
||||||
import logging
|
import logging
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
|
||||||
from homeassistant.components.climate.const import (
|
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
|
from homeassistant.const import CONF_HOST, TEMP_CELSIUS, ATTR_TEMPERATURE
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
@@ -52,6 +53,22 @@ class Touchline(ClimateDevice):
|
|||||||
self._current_temperature = self.unit.get_current_temperature()
|
self._current_temperature = self.unit.get_current_temperature()
|
||||||
self._target_temperature = self.unit.get_target_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
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""Return the polling state."""
|
"""Return the polling state."""
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
"""Support for the Tuya climate devices."""
|
"""Support for the Tuya climate devices."""
|
||||||
from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice
|
from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_COOL, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
|
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT,
|
||||||
SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
|
SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF)
|
||||||
SUPPORT_TARGET_TEMPERATURE)
|
|
||||||
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
|
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||||
@@ -13,11 +12,10 @@ from . import DATA_TUYA, TuyaDevice
|
|||||||
DEVICE_TYPE = 'climate'
|
DEVICE_TYPE = 'climate'
|
||||||
|
|
||||||
HA_STATE_TO_TUYA = {
|
HA_STATE_TO_TUYA = {
|
||||||
STATE_AUTO: 'auto',
|
HVAC_MODE_AUTO: 'auto',
|
||||||
STATE_COOL: 'cold',
|
HVAC_MODE_COOL: 'cold',
|
||||||
STATE_ECO: 'eco',
|
HVAC_MODE_FAN_ONLY: 'wind',
|
||||||
STATE_FAN_ONLY: 'wind',
|
HVAC_MODE_HEAT: 'hot',
|
||||||
STATE_HEAT: 'hot',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TUYA_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_TUYA.items()}
|
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."""
|
"""Init climate device."""
|
||||||
super().__init__(tuya)
|
super().__init__(tuya)
|
||||||
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
|
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
|
||||||
self.operations = []
|
self.operations = [HVAC_MODE_OFF]
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Create operation list when add to hass."""
|
"""Create operation list when add to hass."""
|
||||||
@@ -55,15 +53,11 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
|
|||||||
modes = self.tuya.operation_list()
|
modes = self.tuya.operation_list()
|
||||||
if modes is None:
|
if modes is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
for mode in modes:
|
for mode in modes:
|
||||||
if mode in TUYA_STATE_TO_HA:
|
if mode in TUYA_STATE_TO_HA:
|
||||||
self.operations.append(TUYA_STATE_TO_HA[mode])
|
self.operations.append(TUYA_STATE_TO_HA[mode])
|
||||||
|
|
||||||
@property
|
|
||||||
def is_on(self):
|
|
||||||
"""Return true if climate is on."""
|
|
||||||
return self.tuya.state()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def precision(self):
|
def precision(self):
|
||||||
"""Return the precision of the system."""
|
"""Return the precision of the system."""
|
||||||
@@ -73,22 +67,23 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
|
|||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
"""Return the unit of measurement used by the platform."""
|
"""Return the unit of measurement used by the platform."""
|
||||||
unit = self.tuya.temperature_unit()
|
unit = self.tuya.temperature_unit()
|
||||||
if unit == 'CELSIUS':
|
|
||||||
return TEMP_CELSIUS
|
|
||||||
if unit == 'FAHRENHEIT':
|
if unit == 'FAHRENHEIT':
|
||||||
return TEMP_FAHRENHEIT
|
return TEMP_FAHRENHEIT
|
||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
|
if not self.tuya.state():
|
||||||
|
return HVAC_MODE_OFF
|
||||||
|
|
||||||
mode = self.tuya.current_operation()
|
mode = self.tuya.current_operation()
|
||||||
if mode is None:
|
if mode is None:
|
||||||
return None
|
return None
|
||||||
return TUYA_STATE_TO_HA.get(mode)
|
return TUYA_STATE_TO_HA.get(mode)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return self.operations
|
return self.operations
|
||||||
|
|
||||||
@@ -108,14 +103,14 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
|
|||||||
return self.tuya.target_temperature_step()
|
return self.tuya.target_temperature_step()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self.tuya.current_fan_mode()
|
return self.tuya.current_fan_mode()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""Return the list of available fan modes."""
|
||||||
return self.tuya.fan_list()
|
return self.tuya.fan_modes()
|
||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
@@ -126,26 +121,22 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
|
|||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
self.tuya.set_fan_mode(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."""
|
"""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):
|
if not self.tuya.state():
|
||||||
"""Turn device on."""
|
self.tuya.turn_on()
|
||||||
self.tuya.turn_on()
|
|
||||||
|
|
||||||
def turn_off(self):
|
self.tuya.set_operation_mode(HA_STATE_TO_TUYA.get(hvac_mode))
|
||||||
"""Turn device off."""
|
|
||||||
self.tuya.turn_off()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
supports = SUPPORT_ON_OFF
|
supports = 0
|
||||||
if self.tuya.support_target_temperature():
|
if self.tuya.support_target_temperature():
|
||||||
supports = supports | SUPPORT_TARGET_TEMPERATURE
|
supports = supports | SUPPORT_TARGET_TEMPERATURE
|
||||||
if self.tuya.support_mode():
|
|
||||||
supports = supports | SUPPORT_OPERATION_MODE
|
|
||||||
if self.tuya.support_wind_speed():
|
if self.tuya.support_wind_speed():
|
||||||
supports = supports | SUPPORT_FAN_MODE
|
supports = supports | SUPPORT_FAN_MODE
|
||||||
return supports
|
return supports
|
||||||
|
|||||||
@@ -3,15 +3,13 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
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 homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
|
|
||||||
from . import DOMAIN as VELBUS_DOMAIN, VelbusEntity
|
from . import DOMAIN as VELBUS_DOMAIN, VelbusEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass, config, async_add_entities, discovery_info=None):
|
hass, config, async_add_entities, discovery_info=None):
|
||||||
@@ -34,7 +32,7 @@ class VelbusClimate(VelbusEntity, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list off supported features."""
|
"""Return the list off supported features."""
|
||||||
return SUPPORT_FLAGS
|
return SUPPORT_TARGET_TEMPERATURE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
@@ -49,9 +47,20 @@ class VelbusClimate(VelbusEntity, ClimateDevice):
|
|||||||
return self._module.get_state(self._channel)
|
return self._module.get_state(self._channel)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation."""
|
"""Return hvac operation ie. heat, cool mode.
|
||||||
return STATE_HEAT
|
|
||||||
|
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
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
@@ -65,3 +74,7 @@ class VelbusClimate(VelbusEntity, ClimateDevice):
|
|||||||
return
|
return
|
||||||
self._module.set_temp(temp)
|
self._module.set_temp(temp)
|
||||||
self.schedule_update_ha_state()
|
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 import ClimateDevice, PLATFORM_SCHEMA
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
||||||
STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
|
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_HUMIDITY, SUPPORT_AWAY_MODE,
|
SUPPORT_TARGET_HUMIDITY, SUPPORT_PRESET_MODE,
|
||||||
SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW,
|
SUPPORT_TARGET_TEMPERATURE, PRESET_AWAY,
|
||||||
SUPPORT_HOLD_MODE, SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
|
HVAC_MODE_OFF)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_TIMEOUT,
|
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)
|
TEMP_FAHRENHEIT)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ATTR_FAN_STATE = 'fan_state'
|
ATTR_FAN_STATE = 'fan_state'
|
||||||
ATTR_HVAC_STATE = 'hvac_state'
|
ATTR_HVAC_STATE = 'hvac_mode'
|
||||||
|
|
||||||
CONF_HUMIDIFIER = 'humidifier'
|
CONF_HUMIDIFIER = 'humidifier'
|
||||||
|
|
||||||
DEFAULT_SSL = False
|
DEFAULT_SSL = False
|
||||||
|
|
||||||
VALID_FAN_STATES = [STATE_ON, STATE_AUTO]
|
VALID_FAN_STATES = [STATE_ON, HVAC_MODE_AUTO]
|
||||||
VALID_THERMOSTAT_MODES = [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_AUTO]
|
VALID_THERMOSTAT_MODES = [HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF,
|
||||||
|
HVAC_MODE_AUTO]
|
||||||
|
|
||||||
HOLD_MODE_OFF = 'off'
|
HOLD_MODE_OFF = 'off'
|
||||||
HOLD_MODE_TEMPERATURE = 'temperature'
|
HOLD_MODE_TEMPERATURE = 'temperature'
|
||||||
@@ -84,18 +85,14 @@ class VenstarThermostat(ClimateDevice):
|
|||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
features = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE |
|
features = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE |
|
||||||
SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE |
|
SUPPORT_PRESET_MODE)
|
||||||
SUPPORT_HOLD_MODE)
|
|
||||||
|
|
||||||
if self._client.mode == self._client.MODE_AUTO:
|
if self._client.mode == self._client.MODE_AUTO:
|
||||||
features |= (SUPPORT_TARGET_TEMPERATURE_HIGH |
|
features |= (SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||||
SUPPORT_TARGET_TEMPERATURE_LOW)
|
|
||||||
|
|
||||||
if (self._humidifier and
|
if (self._humidifier and
|
||||||
hasattr(self._client, 'hum_active')):
|
hasattr(self._client, 'hum_active')):
|
||||||
features |= (SUPPORT_TARGET_HUMIDITY |
|
features |= SUPPORT_TARGET_HUMIDITY
|
||||||
SUPPORT_TARGET_HUMIDITY_HIGH |
|
|
||||||
SUPPORT_TARGET_HUMIDITY_LOW)
|
|
||||||
|
|
||||||
return features
|
return features
|
||||||
|
|
||||||
@@ -121,12 +118,12 @@ class VenstarThermostat(ClimateDevice):
|
|||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""Return the list of available fan modes."""
|
||||||
return VALID_FAN_STATES
|
return VALID_FAN_STATES
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return VALID_THERMOSTAT_MODES
|
return VALID_THERMOSTAT_MODES
|
||||||
|
|
||||||
@@ -141,21 +138,21 @@ class VenstarThermostat(ClimateDevice):
|
|||||||
return self._client.get_indoor_humidity()
|
return self._client.get_indoor_humidity()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
if self._client.mode == self._client.MODE_HEAT:
|
if self._client.mode == self._client.MODE_HEAT:
|
||||||
return STATE_HEAT
|
return HVAC_MODE_HEAT
|
||||||
if self._client.mode == self._client.MODE_COOL:
|
if self._client.mode == self._client.MODE_COOL:
|
||||||
return STATE_COOL
|
return HVAC_MODE_COOL
|
||||||
if self._client.mode == self._client.MODE_AUTO:
|
if self._client.mode == self._client.MODE_AUTO:
|
||||||
return STATE_AUTO
|
return HVAC_MODE_AUTO
|
||||||
return STATE_OFF
|
return HVAC_MODE_OFF
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
if self._client.fan == self._client.FAN_AUTO:
|
if self._client.fan == self._client.FAN_AUTO:
|
||||||
return STATE_AUTO
|
return HVAC_MODE_AUTO
|
||||||
return STATE_ON
|
return STATE_ON
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -205,24 +202,28 @@ class VenstarThermostat(ClimateDevice):
|
|||||||
return 60
|
return 60
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_away_mode_on(self):
|
def preset_mode(self):
|
||||||
"""Return the status of away mode."""
|
"""Return current preset."""
|
||||||
return self._client.away == self._client.AWAY_AWAY
|
if self._client.away:
|
||||||
|
return PRESET_AWAY
|
||||||
@property
|
|
||||||
def current_hold_mode(self):
|
|
||||||
"""Return the status of hold mode."""
|
|
||||||
if self._client.schedule == 0:
|
if self._client.schedule == 0:
|
||||||
return HOLD_MODE_TEMPERATURE
|
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):
|
def _set_operation_mode(self, operation_mode):
|
||||||
"""Change the operation mode (internal)."""
|
"""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)
|
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)
|
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)
|
success = self._client.set_mode(self._client.MODE_AUTO)
|
||||||
else:
|
else:
|
||||||
success = self._client.set_mode(self._client.MODE_OFF)
|
success = self._client.set_mode(self._client.MODE_OFF)
|
||||||
@@ -234,7 +235,7 @@ class VenstarThermostat(ClimateDevice):
|
|||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set a new target temperature."""
|
"""Set a new target temperature."""
|
||||||
set_temp = True
|
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_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
||||||
temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
@@ -268,9 +269,9 @@ class VenstarThermostat(ClimateDevice):
|
|||||||
if not success:
|
if not success:
|
||||||
_LOGGER.error("Failed to change the fan mode")
|
_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."""
|
"""Set new target operation mode."""
|
||||||
self._set_operation_mode(operation_mode)
|
self._set_operation_mode(hvac_mode)
|
||||||
|
|
||||||
def set_humidity(self, humidity):
|
def set_humidity(self, humidity):
|
||||||
"""Set new target humidity."""
|
"""Set new target humidity."""
|
||||||
@@ -279,29 +280,21 @@ class VenstarThermostat(ClimateDevice):
|
|||||||
if not success:
|
if not success:
|
||||||
_LOGGER.error("Failed to change the target humidity level")
|
_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."""
|
"""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)
|
success = self._client.set_schedule(0)
|
||||||
elif hold_mode == HOLD_MODE_OFF:
|
elif preset_mode is None:
|
||||||
success = self._client.set_schedule(1)
|
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:
|
else:
|
||||||
_LOGGER.error("Unknown hold mode: %s", hold_mode)
|
_LOGGER.error("Unknown hold mode: %s", preset_mode)
|
||||||
success = False
|
success = False
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
_LOGGER.error("Failed to change the schedule/hold state")
|
_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 import ENTITY_ID_FORMAT, ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
|
FAN_AUTO, FAN_ON, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
HVAC_MODE_OFF, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.const import (
|
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 homeassistant.util import convert
|
||||||
|
|
||||||
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
|
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
OPERATION_LIST = [STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_OFF]
|
FAN_OPERATION_LIST = [FAN_ON, FAN_AUTO]
|
||||||
FAN_OPERATION_LIST = [STATE_ON, STATE_AUTO]
|
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
|
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||||
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):
|
def setup_platform(hass, config, add_entities_callback, discovery_info=None):
|
||||||
@@ -41,42 +42,44 @@ class VeraThermostat(VeraDevice, ClimateDevice):
|
|||||||
return SUPPORT_FLAGS
|
return SUPPORT_FLAGS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return hvac operation ie. heat, cool mode.
|
||||||
|
|
||||||
|
Need to be one of HVAC_MODE_*.
|
||||||
|
"""
|
||||||
mode = self.vera_device.get_hvac_mode()
|
mode = self.vera_device.get_hvac_mode()
|
||||||
if mode == 'HeatOn':
|
if mode == 'HeatOn':
|
||||||
return OPERATION_LIST[0] # Heat
|
return HVAC_MODE_HEAT
|
||||||
if mode == 'CoolOn':
|
if mode == 'CoolOn':
|
||||||
return OPERATION_LIST[1] # Cool
|
return HVAC_MODE_COOL
|
||||||
if mode == 'AutoChangeOver':
|
if mode == 'AutoChangeOver':
|
||||||
return OPERATION_LIST[2] # Auto
|
return HVAC_MODE_HEAT_COOL
|
||||||
if mode == 'Off':
|
return HVAC_MODE_OFF
|
||||||
return OPERATION_LIST[3] # Off
|
|
||||||
return 'Off'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available hvac operation modes.
|
||||||
return OPERATION_LIST
|
|
||||||
|
Need to be a subset of HVAC_MODES.
|
||||||
|
"""
|
||||||
|
return SUPPORT_HVAC
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
mode = self.vera_device.get_fan_mode()
|
mode = self.vera_device.get_fan_mode()
|
||||||
if mode == "ContinuousOn":
|
if mode == "ContinuousOn":
|
||||||
return FAN_OPERATION_LIST[0] # on
|
return FAN_ON
|
||||||
if mode == "Auto":
|
return FAN_AUTO
|
||||||
return FAN_OPERATION_LIST[1] # auto
|
|
||||||
return "Auto"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return a list of available fan modes."""
|
"""Return a list of available fan modes."""
|
||||||
return FAN_OPERATION_LIST
|
return FAN_OPERATION_LIST
|
||||||
|
|
||||||
def set_fan_mode(self, fan_mode):
|
def set_fan_mode(self, fan_mode):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
if fan_mode == FAN_OPERATION_LIST[0]:
|
if fan_mode == FAN_ON:
|
||||||
self.vera_device.fan_on()
|
self.vera_device.fan_on()
|
||||||
else:
|
else:
|
||||||
self.vera_device.fan_auto()
|
self.vera_device.fan_auto()
|
||||||
@@ -107,7 +110,7 @@ class VeraThermostat(VeraDevice, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def operation(self):
|
def operation(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return self.vera_device.get_hvac_state()
|
return self.vera_device.get_hvac_mode()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
@@ -119,21 +122,13 @@ class VeraThermostat(VeraDevice, ClimateDevice):
|
|||||||
if kwargs.get(ATTR_TEMPERATURE) is not None:
|
if kwargs.get(ATTR_TEMPERATURE) is not None:
|
||||||
self.vera_device.set_temperature(kwargs.get(ATTR_TEMPERATURE))
|
self.vera_device.set_temperature(kwargs.get(ATTR_TEMPERATURE))
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set HVAC mode (auto, cool, heat, off)."""
|
"""Set new target hvac mode."""
|
||||||
if operation_mode == OPERATION_LIST[3]: # off
|
if hvac_mode == HVAC_MODE_OFF:
|
||||||
self.vera_device.turn_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()
|
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()
|
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()
|
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."""
|
"""Support for Wink thermostats and Air Conditioners."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import pywink
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_CURRENT_HUMIDITY, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
|
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL,
|
||||||
STATE_AUTO, STATE_COOL, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
|
CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, FAN_AUTO, FAN_HIGH,
|
||||||
SUPPORT_AUX_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
|
FAN_LOW, FAN_MEDIUM, FAN_ON, HVAC_MODE_AUTO, HVAC_MODE_COOL,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
|
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_ECO,
|
||||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
|
SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE,
|
||||||
|
SUPPORT_TARGET_TEMPERATURE_RANGE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, PRECISION_TENTHS, STATE_OFF, STATE_ON, STATE_UNKNOWN,
|
ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS)
|
||||||
TEMP_CELSIUS)
|
|
||||||
from homeassistant.helpers.temperature import display_temp as show_temp
|
from homeassistant.helpers.temperature import display_temp as show_temp
|
||||||
|
|
||||||
from . import DOMAIN, WinkDevice
|
from . import DOMAIN, WinkDevice
|
||||||
@@ -23,36 +25,30 @@ ATTR_OCCUPIED = 'occupied'
|
|||||||
ATTR_SCHEDULE_ENABLED = 'schedule_enabled'
|
ATTR_SCHEDULE_ENABLED = 'schedule_enabled'
|
||||||
ATTR_SMART_TEMPERATURE = 'smart_temperature'
|
ATTR_SMART_TEMPERATURE = 'smart_temperature'
|
||||||
ATTR_TOTAL_CONSUMPTION = 'total_consumption'
|
ATTR_TOTAL_CONSUMPTION = 'total_consumption'
|
||||||
ATTR_HEAT_ON = 'heat_on'
|
|
||||||
ATTR_COOL_ON = 'cool_on'
|
|
||||||
|
|
||||||
SPEED_LOW = 'low'
|
HA_HVAC_TO_WINK = {
|
||||||
SPEED_MEDIUM = 'medium'
|
HVAC_MODE_AUTO: 'auto',
|
||||||
SPEED_HIGH = 'high'
|
HVAC_MODE_COOL: 'cool_only',
|
||||||
|
HVAC_MODE_FAN_ONLY: 'fan_only',
|
||||||
HA_STATE_TO_WINK = {
|
HVAC_MODE_HEAT: 'heat_only',
|
||||||
STATE_AUTO: 'auto',
|
HVAC_MODE_OFF: 'off',
|
||||||
STATE_COOL: 'cool_only',
|
|
||||||
STATE_ECO: 'eco',
|
|
||||||
STATE_FAN_ONLY: 'fan_only',
|
|
||||||
STATE_HEAT: 'heat_only',
|
|
||||||
STATE_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_FLAGS_THERMOSTAT = (
|
||||||
SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH |
|
SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_RANGE |
|
||||||
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_OPERATION_MODE |
|
SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT)
|
||||||
SUPPORT_AWAY_MODE | 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_FLAGS_AC = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||||
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):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up the Wink climate devices."""
|
"""Set up the Wink climate devices."""
|
||||||
import pywink
|
|
||||||
for climate in pywink.get_thermostats():
|
for climate in pywink.get_thermostats():
|
||||||
_id = climate.object_id() + climate.name()
|
_id = climate.object_id() + climate.name()
|
||||||
if _id not in hass.data[DOMAIN]['unique_ids']:
|
if _id not in hass.data[DOMAIN]['unique_ids']:
|
||||||
@@ -85,17 +81,6 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the optional device state attributes."""
|
"""Return the optional device state attributes."""
|
||||||
data = {}
|
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:
|
if self.external_temperature is not None:
|
||||||
data[ATTR_EXTERNAL_TEMPERATURE] = show_temp(
|
data[ATTR_EXTERNAL_TEMPERATURE] = show_temp(
|
||||||
self.hass, self.external_temperature, self.temperature_unit,
|
self.hass, self.external_temperature, self.temperature_unit,
|
||||||
@@ -110,16 +95,6 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||||||
if self.eco_target is not None:
|
if self.eco_target is not None:
|
||||||
data[ATTR_ECO_TARGET] = self.eco_target
|
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
|
return data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -160,27 +135,19 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||||||
return self.wink.occupied()
|
return self.wink.occupied()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def heat_on(self):
|
def preset_mode(self):
|
||||||
"""Return whether or not the heat is actually heating."""
|
"""Return the current preset mode, e.g., home, away, temp."""
|
||||||
return self.wink.heat_on()
|
mode = self.wink.current_mode()
|
||||||
|
if mode == "eco":
|
||||||
|
return PRESET_ECO
|
||||||
|
if self.wink.away():
|
||||||
|
return PRESET_AWAY
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cool_on(self):
|
def preset_modes(self):
|
||||||
"""Return whether or not the heat is actually heating."""
|
"""Return a list of available preset modes."""
|
||||||
return self.wink.cool_on()
|
return SUPPORT_PRESET_THERMOSTAT
|
||||||
|
|
||||||
@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
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_humidity(self):
|
def target_humidity(self):
|
||||||
@@ -199,51 +166,96 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
if self.current_operation != STATE_AUTO and not self.is_away_mode_on:
|
if self.hvac_mode != HVAC_MODE_AUTO and not self.wink.away():
|
||||||
if self.current_operation == STATE_COOL:
|
if self.hvac_mode == HVAC_MODE_COOL:
|
||||||
return self.wink.current_max_set_point()
|
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 self.wink.current_min_set_point()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_low(self):
|
def target_temperature_low(self):
|
||||||
"""Return the lower bound temperature we try to reach."""
|
"""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 self.wink.current_min_set_point()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_high(self):
|
def target_temperature_high(self):
|
||||||
"""Return the higher bound temperature we try to reach."""
|
"""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 self.wink.current_max_set_point()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_away_mode_on(self):
|
def is_aux_heat(self):
|
||||||
"""Return if away mode is on."""
|
|
||||||
return self.wink.away()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_aux_heat_on(self):
|
|
||||||
"""Return true if aux heater."""
|
"""Return true if aux heater."""
|
||||||
if 'aux' not in self.wink.hvac_modes():
|
if 'aux' not in self.wink.hvac_modes():
|
||||||
return None
|
return None
|
||||||
|
if self.wink.hvac_action_mode() == 'aux':
|
||||||
if self.wink.current_hvac_mode() == 'aux':
|
|
||||||
return True
|
return True
|
||||||
return False
|
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):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
target_temp = kwargs.get(ATTR_TEMPERATURE)
|
target_temp = kwargs.get(ATTR_TEMPERATURE)
|
||||||
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
||||||
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||||
if target_temp is not None:
|
if target_temp is not None:
|
||||||
if self.current_operation == STATE_COOL:
|
if self.hvac_mode == HVAC_MODE_COOL:
|
||||||
target_temp_high = target_temp
|
target_temp_high = target_temp
|
||||||
if self.current_operation == STATE_HEAT:
|
if self.hvac_mode == HVAC_MODE_HEAT:
|
||||||
target_temp_low = target_temp
|
target_temp_low = target_temp
|
||||||
if target_temp_low is not None:
|
if target_temp_low is not None:
|
||||||
target_temp_low = target_temp_low
|
target_temp_low = target_temp_low
|
||||||
@@ -251,54 +263,37 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||||||
target_temp_high = target_temp_high
|
target_temp_high = target_temp_high
|
||||||
self.wink.set_temperature(target_temp_low, target_temp_high)
|
self.wink.set_temperature(target_temp_low, target_temp_high)
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set operation mode."""
|
"""Set new target hvac mode."""
|
||||||
op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
|
hvac_mode_to_set = HA_HVAC_TO_WINK.get(hvac_mode)
|
||||||
# The only way to disable aux heat is with the toggle
|
self.wink.set_operation_mode(hvac_mode_to_set)
|
||||||
if self.is_aux_heat_on and op_mode_to_set == STATE_HEAT:
|
|
||||||
return
|
def set_preset_mode(self, preset_mode):
|
||||||
self.wink.set_operation_mode(op_mode_to_set)
|
"""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
|
@property
|
||||||
def operation_list(self):
|
def fan_mode(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):
|
|
||||||
"""Return whether the fan is on."""
|
"""Return whether the fan is on."""
|
||||||
if self.wink.current_fan_mode() == 'on':
|
if self.wink.current_fan_mode() == 'on':
|
||||||
return STATE_ON
|
return FAN_ON
|
||||||
if self.wink.current_fan_mode() == 'auto':
|
if self.wink.current_fan_mode() == 'auto':
|
||||||
return STATE_AUTO
|
return FAN_AUTO
|
||||||
# No Fan available so disable slider
|
# No Fan available so disable slider
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""List of available fan modes."""
|
"""List of available fan modes."""
|
||||||
if self.wink.has_fan():
|
if self.wink.has_fan():
|
||||||
return self.wink.fan_modes()
|
return SUPPORT_FAN_THERMOSTAT
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def set_fan_mode(self, fan_mode):
|
def set_fan_mode(self, fan_mode):
|
||||||
@@ -311,7 +306,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||||||
|
|
||||||
def turn_aux_heat_off(self):
|
def turn_aux_heat_off(self):
|
||||||
"""Turn auxiliary heater off."""
|
"""Turn auxiliary heater off."""
|
||||||
self.set_operation_mode(STATE_HEAT)
|
self.wink.set_operation_mode('heat_only')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
@@ -319,17 +314,17 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||||||
minimum = 7 # Default minimum
|
minimum = 7 # Default minimum
|
||||||
min_min = self.wink.min_min_set_point()
|
min_min = self.wink.min_min_set_point()
|
||||||
min_max = self.wink.min_max_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:
|
if min_min:
|
||||||
return_value = min_min
|
return_value = min_min
|
||||||
else:
|
else:
|
||||||
return_value = minimum
|
return_value = minimum
|
||||||
elif self.current_operation == STATE_COOL:
|
elif self.hvac_mode == HVAC_MODE_COOL:
|
||||||
if min_max:
|
if min_max:
|
||||||
return_value = min_max
|
return_value = min_max
|
||||||
else:
|
else:
|
||||||
return_value = minimum
|
return_value = minimum
|
||||||
elif self.current_operation == STATE_AUTO:
|
elif self.hvac_mode == HVAC_MODE_AUTO:
|
||||||
if min_min and min_max:
|
if min_min and min_max:
|
||||||
return_value = min(min_min, min_max)
|
return_value = min(min_min, min_max)
|
||||||
else:
|
else:
|
||||||
@@ -344,17 +339,17 @@ class WinkThermostat(WinkDevice, ClimateDevice):
|
|||||||
maximum = 35 # Default maximum
|
maximum = 35 # Default maximum
|
||||||
max_min = self.wink.max_min_set_point()
|
max_min = self.wink.max_min_set_point()
|
||||||
max_max = self.wink.max_max_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:
|
if max_min:
|
||||||
return_value = max_min
|
return_value = max_min
|
||||||
else:
|
else:
|
||||||
return_value = maximum
|
return_value = maximum
|
||||||
elif self.current_operation == STATE_COOL:
|
elif self.hvac_mode == HVAC_MODE_COOL:
|
||||||
if max_max:
|
if max_max:
|
||||||
return_value = max_max
|
return_value = max_max
|
||||||
else:
|
else:
|
||||||
return_value = maximum
|
return_value = maximum
|
||||||
elif self.current_operation == STATE_AUTO:
|
elif self.hvac_mode == HVAC_MODE_AUTO:
|
||||||
if max_min and max_max:
|
if max_min and max_max:
|
||||||
return_value = min(max_min, max_max)
|
return_value = min(max_min, max_max)
|
||||||
else:
|
else:
|
||||||
@@ -382,16 +377,6 @@ class WinkAC(WinkDevice, ClimateDevice):
|
|||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the optional device state attributes."""
|
"""Return the optional device state attributes."""
|
||||||
data = {}
|
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_TOTAL_CONSUMPTION] = self.wink.total_consumption()
|
||||||
data[ATTR_SCHEDULE_ENABLED] = self.wink.schedule_enabled()
|
data[ATTR_SCHEDULE_ENABLED] = self.wink.schedule_enabled()
|
||||||
|
|
||||||
@@ -403,47 +388,67 @@ class WinkAC(WinkDevice, ClimateDevice):
|
|||||||
return self.wink.current_temperature()
|
return self.wink.current_temperature()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def preset_mode(self):
|
||||||
"""Return current operation ie. auto_eco, cool_only, fan_only."""
|
"""Return the current preset mode, e.g., home, away, temp."""
|
||||||
if not self.wink.is_on():
|
mode = self.wink.current_mode()
|
||||||
current_op = STATE_OFF
|
if mode == "auto_eco":
|
||||||
else:
|
return PRESET_ECO
|
||||||
wink_mode = self.wink.current_mode()
|
return None
|
||||||
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
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def preset_modes(self):
|
||||||
"""List of available operation modes."""
|
"""Return a list of available preset modes."""
|
||||||
op_list = ['off']
|
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()
|
modes = self.wink.modes()
|
||||||
for mode in modes:
|
for mode in modes:
|
||||||
if mode == "auto_eco":
|
if mode == "auto_eco":
|
||||||
mode = "eco"
|
continue
|
||||||
ha_mode = WINK_STATE_TO_HA.get(mode)
|
try:
|
||||||
if ha_mode is not None:
|
ha_mode = WINK_HVAC_TO_HA[mode]
|
||||||
op_list.append(ha_mode)
|
hvac_list.append(ha_mode)
|
||||||
else:
|
except KeyError:
|
||||||
error = "Invalid operation mode mapping. " + mode + \
|
_LOGGER.error(
|
||||||
" doesn't map. Please report this."
|
"Invalid operation mode mapping. %s doesn't map. "
|
||||||
_LOGGER.error(error)
|
"Please report this.", mode)
|
||||||
return op_list
|
return hvac_list
|
||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
target_temp = kwargs.get(ATTR_TEMPERATURE)
|
target_temp = kwargs.get(ATTR_TEMPERATURE)
|
||||||
self.wink.set_temperature(target_temp)
|
self.wink.set_temperature(target_temp)
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set operation mode."""
|
"""Set new target hvac mode."""
|
||||||
op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
|
hvac_mode_to_set = HA_HVAC_TO_WINK.get(hvac_mode)
|
||||||
if op_mode_to_set == 'eco':
|
self.wink.set_operation_mode(hvac_mode_to_set)
|
||||||
op_mode_to_set = 'auto_eco'
|
|
||||||
self.wink.set_operation_mode(op_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
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
@@ -451,7 +456,7 @@ class WinkAC(WinkDevice, ClimateDevice):
|
|||||||
return self.wink.current_max_set_point()
|
return self.wink.current_max_set_point()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""
|
"""
|
||||||
Return the current fan mode.
|
Return the current fan mode.
|
||||||
|
|
||||||
@@ -460,15 +465,15 @@ class WinkAC(WinkDevice, ClimateDevice):
|
|||||||
"""
|
"""
|
||||||
speed = self.wink.current_fan_speed()
|
speed = self.wink.current_fan_speed()
|
||||||
if speed <= 0.33:
|
if speed <= 0.33:
|
||||||
return SPEED_LOW
|
return FAN_LOW
|
||||||
if speed <= 0.66:
|
if speed <= 0.66:
|
||||||
return SPEED_MEDIUM
|
return FAN_MEDIUM
|
||||||
return SPEED_HIGH
|
return FAN_HIGH
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return a list of available fan modes."""
|
"""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):
|
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]
|
The official Wink app only supports 3 modes [low, medium, high]
|
||||||
which are equal to [0.33, 0.66, 1.0] respectively.
|
which are equal to [0.33, 0.66, 1.0] respectively.
|
||||||
"""
|
"""
|
||||||
if fan_mode == SPEED_LOW:
|
if fan_mode == FAN_LOW:
|
||||||
speed = 0.33
|
speed = 0.33
|
||||||
elif fan_mode == SPEED_MEDIUM:
|
elif fan_mode == FAN_MEDIUM:
|
||||||
speed = 0.66
|
speed = 0.66
|
||||||
elif fan_mode == SPEED_HIGH:
|
elif fan_mode == FAN_HIGH:
|
||||||
speed = 1.0
|
speed = 1.0
|
||||||
self.wink.set_ac_fan_speed(speed)
|
self.wink.set_ac_fan_speed(speed)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"""Support for the EZcontrol XS1 gateway."""
|
"""Support for the EZcontrol XS1 gateway."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from functools import partial
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
import xs1_api_client
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_USERNAME)
|
CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_USERNAME)
|
||||||
@@ -40,20 +40,7 @@ XS1_COMPONENTS = [
|
|||||||
UPDATE_LOCK = asyncio.Lock()
|
UPDATE_LOCK = asyncio.Lock()
|
||||||
|
|
||||||
|
|
||||||
def _create_controller_api(host, port, ssl, user, password):
|
def setup(hass, config):
|
||||||
"""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):
|
|
||||||
"""Set up XS1 Component."""
|
"""Set up XS1 Component."""
|
||||||
_LOGGER.debug("Initializing XS1")
|
_LOGGER.debug("Initializing XS1")
|
||||||
|
|
||||||
@@ -64,9 +51,12 @@ async def async_setup(hass, config):
|
|||||||
password = config[DOMAIN].get(CONF_PASSWORD)
|
password = config[DOMAIN].get(CONF_PASSWORD)
|
||||||
|
|
||||||
# initialize XS1 API
|
# initialize XS1 API
|
||||||
xs1 = await hass.async_add_executor_job(
|
try:
|
||||||
partial(_create_controller_api, host, port, ssl, user, password))
|
xs1 = xs1_api_client.XS1(
|
||||||
if xs1 is None:
|
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
|
return False
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
@@ -74,10 +64,8 @@ async def async_setup(hass, config):
|
|||||||
|
|
||||||
hass.data[DOMAIN] = {}
|
hass.data[DOMAIN] = {}
|
||||||
|
|
||||||
actuators = await hass.async_add_executor_job(
|
actuators = xs1.get_all_actuators(enabled=True)
|
||||||
partial(xs1.get_all_actuators, enabled=True))
|
sensors = xs1.get_all_sensors(enabled=True)
|
||||||
sensors = await hass.async_add_executor_job(
|
|
||||||
partial(xs1.get_all_sensors, enabled=True))
|
|
||||||
|
|
||||||
hass.data[DOMAIN][ACTUATORS] = actuators
|
hass.data[DOMAIN][ACTUATORS] = actuators
|
||||||
hass.data[DOMAIN][SENSORS] = sensors
|
hass.data[DOMAIN][SENSORS] = sensors
|
||||||
@@ -85,9 +73,7 @@ async def async_setup(hass, config):
|
|||||||
_LOGGER.debug("Loading components for XS1 platform...")
|
_LOGGER.debug("Loading components for XS1 platform...")
|
||||||
# Load components for supported devices
|
# Load components for supported devices
|
||||||
for component in XS1_COMPONENTS:
|
for component in XS1_COMPONENTS:
|
||||||
hass.async_create_task(
|
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
||||||
discovery.async_load_platform(
|
|
||||||
hass, component, DOMAIN, {}, config))
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -102,5 +88,4 @@ class XS1DeviceEntity(Entity):
|
|||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Retrieve latest device state."""
|
"""Retrieve latest device state."""
|
||||||
async with UPDATE_LOCK:
|
async with UPDATE_LOCK:
|
||||||
await self.hass.async_add_executor_job(
|
await self.hass.async_add_executor_job(self.device.update)
|
||||||
partial(self.device.update))
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
"""Support for XS1 climate devices."""
|
"""Support for XS1 climate devices."""
|
||||||
from functools import partial
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from xs1_api_client.api_constants import ActuatorType
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
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 homeassistant.const import ATTR_TEMPERATURE
|
||||||
|
|
||||||
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity
|
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity
|
||||||
@@ -13,12 +15,11 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
MIN_TEMP = 8
|
MIN_TEMP = 8
|
||||||
MAX_TEMP = 25
|
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."""
|
"""Set up the XS1 thermostat platform."""
|
||||||
from xs1_api_client.api_constants import ActuatorType
|
|
||||||
|
|
||||||
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
|
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
|
||||||
sensors = hass.data[COMPONENT_DOMAIN][SENSORS]
|
sensors = hass.data[COMPONENT_DOMAIN][SENSORS]
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ async def async_setup_platform(
|
|||||||
thermostat_entities.append(
|
thermostat_entities.append(
|
||||||
XS1ThermostatEntity(actuator, matching_sensor))
|
XS1ThermostatEntity(actuator, matching_sensor))
|
||||||
|
|
||||||
async_add_entities(thermostat_entities)
|
add_entities(thermostat_entities)
|
||||||
|
|
||||||
|
|
||||||
class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice):
|
class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice):
|
||||||
@@ -58,6 +59,22 @@ class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice):
|
|||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return SUPPORT_TARGET_TEMPERATURE
|
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
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
@@ -95,9 +112,12 @@ class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice):
|
|||||||
if self.sensor is not None:
|
if self.sensor is not None:
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
def set_hvac_mode(self, hvac_mode):
|
||||||
|
"""Set new target hvac mode."""
|
||||||
|
pass
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Also update the sensor when available."""
|
"""Also update the sensor when available."""
|
||||||
await super().async_update()
|
await super().async_update()
|
||||||
if self.sensor is not None:
|
if self.sensor is None:
|
||||||
await self.hass.async_add_executor_job(
|
await self.hass.async_add_executor_job(self.sensor.update)
|
||||||
partial(self.sensor.update))
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
"""Support for XS1 sensors."""
|
"""Support for XS1 sensors."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from xs1_api_client.api_constants import ActuatorType
|
||||||
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
hass, config, async_add_entities, discovery_info=None):
|
|
||||||
"""Set up the XS1 sensor platform."""
|
"""Set up the XS1 sensor platform."""
|
||||||
from xs1_api_client.api_constants import ActuatorType
|
|
||||||
|
|
||||||
sensors = hass.data[COMPONENT_DOMAIN][SENSORS]
|
sensors = hass.data[COMPONENT_DOMAIN][SENSORS]
|
||||||
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
|
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ async def async_setup_platform(
|
|||||||
if not belongs_to_climate_actuator:
|
if not belongs_to_climate_actuator:
|
||||||
sensor_entities.append(XS1Sensor(sensor))
|
sensor_entities.append(XS1Sensor(sensor))
|
||||||
|
|
||||||
async_add_entities(sensor_entities)
|
add_entities(sensor_entities)
|
||||||
|
|
||||||
|
|
||||||
class XS1Sensor(XS1DeviceEntity, Entity):
|
class XS1Sensor(XS1DeviceEntity, Entity):
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
"""Support for XS1 switches."""
|
"""Support for XS1 switches."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from xs1_api_client.api_constants import ActuatorType
|
||||||
|
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
|
|
||||||
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity
|
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity
|
||||||
@@ -8,11 +10,8 @@ from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
hass, config, async_add_entities, discovery_info=None):
|
|
||||||
"""Set up the XS1 switch platform."""
|
"""Set up the XS1 switch platform."""
|
||||||
from xs1_api_client.api_constants import ActuatorType
|
|
||||||
|
|
||||||
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
|
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
|
||||||
|
|
||||||
switch_entities = []
|
switch_entities = []
|
||||||
@@ -21,7 +20,7 @@ async def async_setup_platform(
|
|||||||
(actuator.type() == ActuatorType.DIMMER):
|
(actuator.type() == ActuatorType.DIMMER):
|
||||||
switch_entities.append(XS1SwitchEntity(actuator))
|
switch_entities.append(XS1SwitchEntity(actuator))
|
||||||
|
|
||||||
async_add_entities(switch_entities)
|
add_entities(switch_entities)
|
||||||
|
|
||||||
|
|
||||||
class XS1SwitchEntity(XS1DeviceEntity, ToggleEntity):
|
class XS1SwitchEntity(XS1DeviceEntity, ToggleEntity):
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ import logging
|
|||||||
|
|
||||||
import voluptuous as vol
|
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 (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_OPERATION_MODE, STATE_COOL, STATE_DRY,
|
ATTR_HVAC_MODE, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
|
||||||
STATE_FAN_ONLY, STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF,
|
HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.const import (ATTR_TEMPERATURE, CONF_HOST, CONF_PORT,
|
from homeassistant.const import (
|
||||||
EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS)
|
ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP,
|
||||||
|
TEMP_CELSIUS)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.dispatcher import (async_dispatcher_connect,
|
from homeassistant.helpers.dispatcher import (
|
||||||
async_dispatcher_send)
|
async_dispatcher_connect, async_dispatcher_send)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -31,6 +32,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
cv.positive_int,
|
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):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up the ZhongHong HVAC platform."""
|
"""Set up the ZhongHong HVAC platform."""
|
||||||
@@ -86,7 +90,6 @@ class ZhongHongClimate(ClimateDevice):
|
|||||||
self._current_temperature = None
|
self._current_temperature = None
|
||||||
self._target_temperature = None
|
self._target_temperature = None
|
||||||
self._current_fan_mode = None
|
self._current_fan_mode = None
|
||||||
self._is_on = None
|
|
||||||
self.is_initialized = False
|
self.is_initialized = False
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
@@ -106,7 +109,6 @@ class ZhongHongClimate(ClimateDevice):
|
|||||||
self._current_fan_mode = self._device.current_fan_mode
|
self._current_fan_mode = self._device.current_fan_mode
|
||||||
if self._device.target_temperature:
|
if self._device.target_temperature:
|
||||||
self._target_temperature = self._device.target_temperature
|
self._target_temperature = self._device.target_temperature
|
||||||
self._is_on = self._device.is_on
|
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -128,8 +130,7 @@ class ZhongHongClimate(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
return (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
|
||||||
| SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
@@ -137,14 +138,14 @@ class ZhongHongClimate(ClimateDevice):
|
|||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
return self._current_operation
|
return self._current_operation
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return [STATE_COOL, STATE_HEAT, STATE_DRY, STATE_FAN_ONLY]
|
return SUPPORT_HVAC
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
@@ -167,12 +168,12 @@ class ZhongHongClimate(ClimateDevice):
|
|||||||
return self._device.is_on
|
return self._device.is_on
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self._current_fan_mode
|
return self._current_fan_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return the list of available fan modes."""
|
"""Return the list of available fan modes."""
|
||||||
return self._device.fan_list
|
return self._device.fan_list
|
||||||
|
|
||||||
@@ -200,13 +201,13 @@ class ZhongHongClimate(ClimateDevice):
|
|||||||
if temperature is not None:
|
if temperature is not None:
|
||||||
self._device.set_temperature(temperature)
|
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:
|
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."""
|
"""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):
|
def set_fan_mode(self, fan_mode):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
"""Support for Z-Wave climate devices."""
|
"""Support for Z-Wave climate devices."""
|
||||||
# Because we do not compile openzwave on CI
|
# Because we do not compile openzwave on CI
|
||||||
import logging
|
import logging
|
||||||
from homeassistant.core import callback
|
|
||||||
from homeassistant.components.climate import ClimateDevice
|
from homeassistant.components.climate import ClimateDevice
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
DOMAIN, STATE_AUTO, STATE_COOL, STATE_HEAT,
|
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF,
|
||||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
|
DOMAIN, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE)
|
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
from . import ZWaveDeviceEntity
|
from . import ZWaveDeviceEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -29,13 +30,21 @@ DEVICE_MAPPINGS = {
|
|||||||
REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120
|
REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120
|
||||||
}
|
}
|
||||||
|
|
||||||
STATE_MAPPINGS = {
|
HVAC_STATE_MAPPINGS = {
|
||||||
'Off': STATE_OFF,
|
'Off': HVAC_MODE_OFF,
|
||||||
'Heat': STATE_HEAT,
|
'Heat': HVAC_MODE_HEAT,
|
||||||
'Heat Mode': STATE_HEAT,
|
'Heat Mode': HVAC_MODE_HEAT,
|
||||||
'Heat (Default)': STATE_HEAT,
|
'Heat (Default)': HVAC_MODE_HEAT,
|
||||||
'Cool': STATE_COOL,
|
'Cool': HVAC_MODE_COOL,
|
||||||
'Auto': STATE_AUTO,
|
'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)
|
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
|
||||||
self._target_temperature = None
|
self._target_temperature = None
|
||||||
self._current_temperature = None
|
self._current_temperature = None
|
||||||
self._current_operation = None
|
self._hvac_action = None
|
||||||
self._operation_list = None
|
self._hvac_list = None
|
||||||
self._operation_mapping = None
|
self._hvac_mapping = None
|
||||||
self._operating_state = None
|
self._hvac_mode = None
|
||||||
self._current_fan_mode = None
|
self._current_fan_mode = None
|
||||||
self._fan_list = None
|
self._fan_modes = None
|
||||||
self._fan_state = None
|
self._fan_state = None
|
||||||
self._current_swing_mode = None
|
self._current_swing_mode = None
|
||||||
self._swing_list = None
|
self._swing_modes = None
|
||||||
self._unit = temp_unit
|
self._unit = temp_unit
|
||||||
_LOGGER.debug("temp_unit is %s", self._unit)
|
_LOGGER.debug("temp_unit is %s", self._unit)
|
||||||
self._zxt_120 = None
|
self._zxt_120 = None
|
||||||
@@ -100,8 +109,6 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
support = SUPPORT_TARGET_TEMPERATURE
|
support = SUPPORT_TARGET_TEMPERATURE
|
||||||
if self.values.fan_mode:
|
if self.values.fan_mode:
|
||||||
support |= SUPPORT_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:
|
if self._zxt_120 == 1 and self.values.zxt_120_swing_mode:
|
||||||
support |= SUPPORT_SWING_MODE
|
support |= SUPPORT_SWING_MODE
|
||||||
return support
|
return support
|
||||||
@@ -110,23 +117,23 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
"""Handle the data changes for node values."""
|
"""Handle the data changes for node values."""
|
||||||
# Operation Mode
|
# Operation Mode
|
||||||
if self.values.mode:
|
if self.values.mode:
|
||||||
self._operation_list = []
|
self._hvac_list = []
|
||||||
self._operation_mapping = {}
|
self._hvac_mapping = {}
|
||||||
operation_list = self.values.mode.data_items
|
hvac_list = self.values.mode.data_items
|
||||||
if operation_list:
|
if hvac_list:
|
||||||
for mode in operation_list:
|
for mode in hvac_list:
|
||||||
ha_mode = STATE_MAPPINGS.get(mode)
|
ha_mode = HVAC_STATE_MAPPINGS.get(mode)
|
||||||
if ha_mode and ha_mode not in self._operation_mapping:
|
if ha_mode and ha_mode not in self._hvac_mapping:
|
||||||
self._operation_mapping[ha_mode] = mode
|
self._hvac_mapping[ha_mode] = mode
|
||||||
self._operation_list.append(ha_mode)
|
self._hvac_list.append(ha_mode)
|
||||||
continue
|
continue
|
||||||
self._operation_list.append(mode)
|
self._hvac_list.append(mode)
|
||||||
current_mode = self.values.mode.data
|
current_mode = self.values.mode.data
|
||||||
self._current_operation = next(
|
self._hvac_mode = next(
|
||||||
(key for key, value in self._operation_mapping.items()
|
(key for key, value in self._hvac_mapping.items()
|
||||||
if value == current_mode), current_mode)
|
if value == current_mode), current_mode)
|
||||||
_LOGGER.debug("self._operation_list=%s", self._operation_list)
|
_LOGGER.debug("self._hvac_list=%s", self._hvac_list)
|
||||||
_LOGGER.debug("self._current_operation=%s", self._current_operation)
|
_LOGGER.debug("self._hvac_action=%s", self._hvac_action)
|
||||||
|
|
||||||
# Current Temp
|
# Current Temp
|
||||||
if self.values.temperature:
|
if self.values.temperature:
|
||||||
@@ -138,20 +145,20 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
# Fan Mode
|
# Fan Mode
|
||||||
if self.values.fan_mode:
|
if self.values.fan_mode:
|
||||||
self._current_fan_mode = self.values.fan_mode.data
|
self._current_fan_mode = self.values.fan_mode.data
|
||||||
fan_list = self.values.fan_mode.data_items
|
fan_modes = self.values.fan_mode.data_items
|
||||||
if fan_list:
|
if fan_modes:
|
||||||
self._fan_list = list(fan_list)
|
self._fan_modes = list(fan_modes)
|
||||||
_LOGGER.debug("self._fan_list=%s", self._fan_list)
|
_LOGGER.debug("self._fan_modes=%s", self._fan_modes)
|
||||||
_LOGGER.debug("self._current_fan_mode=%s",
|
_LOGGER.debug("self._current_fan_mode=%s",
|
||||||
self._current_fan_mode)
|
self._current_fan_mode)
|
||||||
# Swing mode
|
# Swing mode
|
||||||
if self._zxt_120 == 1:
|
if self._zxt_120 == 1:
|
||||||
if self.values.zxt_120_swing_mode:
|
if self.values.zxt_120_swing_mode:
|
||||||
self._current_swing_mode = self.values.zxt_120_swing_mode.data
|
self._current_swing_mode = self.values.zxt_120_swing_mode.data
|
||||||
swing_list = self.values.zxt_120_swing_mode.data_items
|
swing_modes = self.values.zxt_120_swing_mode.data_items
|
||||||
if swing_list:
|
if swing_modes:
|
||||||
self._swing_list = list(swing_list)
|
self._swing_modes = list(swing_modes)
|
||||||
_LOGGER.debug("self._swing_list=%s", self._swing_list)
|
_LOGGER.debug("self._swing_modes=%s", self._swing_modes)
|
||||||
_LOGGER.debug("self._current_swing_mode=%s",
|
_LOGGER.debug("self._current_swing_mode=%s",
|
||||||
self._current_swing_mode)
|
self._current_swing_mode)
|
||||||
# Set point
|
# Set point
|
||||||
@@ -168,31 +175,32 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
|
|
||||||
# Operating state
|
# Operating state
|
||||||
if self.values.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
|
# Fan operating state
|
||||||
if self.values.fan_state:
|
if self.values.fan_state:
|
||||||
self._fan_state = self.values.fan_state.data
|
self._fan_state = self.values.fan_state.data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_fan_mode(self):
|
def fan_mode(self):
|
||||||
"""Return the fan speed set."""
|
"""Return the fan speed set."""
|
||||||
return self._current_fan_mode
|
return self._current_fan_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_list(self):
|
def fan_modes(self):
|
||||||
"""Return a list of available fan modes."""
|
"""Return a list of available fan modes."""
|
||||||
return self._fan_list
|
return self._fan_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_swing_mode(self):
|
def swing_mode(self):
|
||||||
"""Return the swing mode set."""
|
"""Return the swing mode set."""
|
||||||
return self._current_swing_mode
|
return self._current_swing_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def swing_list(self):
|
def swing_modes(self):
|
||||||
"""Return a list of available swing modes."""
|
"""Return a list of available swing modes."""
|
||||||
return self._swing_list
|
return self._swing_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
@@ -209,14 +217,30 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
return self._current_temperature
|
return self._current_temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def hvac_mode(self):
|
||||||
"""Return the current operation mode."""
|
"""Return hvac operation ie. heat, cool mode.
|
||||||
return self._current_operation
|
|
||||||
|
Need to be one of HVAC_MODE_*.
|
||||||
|
"""
|
||||||
|
if self.values.mode:
|
||||||
|
return self._hvac_mode
|
||||||
|
return HVAC_MODE_HEAT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_list(self):
|
def hvac_modes(self):
|
||||||
"""Return a list of available operation modes."""
|
"""Return the list of available hvac operation modes.
|
||||||
return self._operation_list
|
|
||||||
|
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
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
@@ -225,36 +249,24 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
if kwargs.get(ATTR_TEMPERATURE) is not None:
|
if kwargs.get(ATTR_TEMPERATURE) is None:
|
||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
|
||||||
else:
|
|
||||||
return
|
return
|
||||||
|
self.values.primary.data = kwargs.get(ATTR_TEMPERATURE)
|
||||||
self.values.primary.data = temperature
|
|
||||||
|
|
||||||
def set_fan_mode(self, fan_mode):
|
def set_fan_mode(self, fan_mode):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
if self.values.fan_mode:
|
if not self.values.fan_mode:
|
||||||
self.values.fan_mode.data = fan_mode
|
return
|
||||||
|
self.values.fan_mode.data = fan_mode
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set new target operation mode."""
|
"""Set new target hvac mode."""
|
||||||
if self.values.mode:
|
if not self.values.mode:
|
||||||
self.values.mode.data = self._operation_mapping.get(
|
return
|
||||||
operation_mode, operation_mode)
|
self.values.mode.data = self._hvac_mapping.get(hvac_mode, hvac_mode)
|
||||||
|
|
||||||
def set_swing_mode(self, swing_mode):
|
def set_swing_mode(self, swing_mode):
|
||||||
"""Set new target swing mode."""
|
"""Set new target swing mode."""
|
||||||
if self._zxt_120 == 1:
|
if self._zxt_120 == 1:
|
||||||
if self.values.zxt_120_swing_mode:
|
if self.values.zxt_120_swing_mode:
|
||||||
self.values.zxt_120_swing_mode.data = 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.
|
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,
|
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
|
return 1
|
||||||
if state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
|
if state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
|
||||||
STATE_BELOW_HORIZON, STATE_CLOSED, STATE_NOT_HOME,
|
STATE_BELOW_HORIZON, STATE_CLOSED, STATE_NOT_HOME):
|
||||||
STATE_IDLE):
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return float(state.state)
|
return float(state.state)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ certifi>=2019.6.16
|
|||||||
cryptography==2.7
|
cryptography==2.7
|
||||||
distro==1.4.0
|
distro==1.4.0
|
||||||
hass-nabucasa==0.15
|
hass-nabucasa==0.15
|
||||||
home-assistant-frontend==20190702.0
|
home-assistant-frontend==20190705.0
|
||||||
importlib-metadata==0.18
|
importlib-metadata==0.18
|
||||||
jinja2>=2.10.1
|
jinja2>=2.10.1
|
||||||
netdisco==2.6.0
|
netdisco==2.6.0
|
||||||
|
|||||||
@@ -442,8 +442,7 @@ eternalegypt==0.0.7
|
|||||||
# evdev==0.6.1
|
# evdev==0.6.1
|
||||||
|
|
||||||
# homeassistant.components.evohome
|
# homeassistant.components.evohome
|
||||||
# homeassistant.components.honeywell
|
evohomeclient==0.3.3
|
||||||
evohomeclient==0.3.2
|
|
||||||
|
|
||||||
# homeassistant.components.dlib_face_detect
|
# homeassistant.components.dlib_face_detect
|
||||||
# homeassistant.components.dlib_face_identify
|
# homeassistant.components.dlib_face_identify
|
||||||
@@ -602,7 +601,7 @@ hole==0.3.0
|
|||||||
holidays==0.9.10
|
holidays==0.9.10
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20190702.0
|
home-assistant-frontend==20190705.0
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.4
|
homeassistant-pyozw==0.1.4
|
||||||
@@ -611,7 +610,7 @@ homeassistant-pyozw==0.1.4
|
|||||||
homekit[IP]==0.14.0
|
homekit[IP]==0.14.0
|
||||||
|
|
||||||
# homeassistant.components.homematicip_cloud
|
# homeassistant.components.homematicip_cloud
|
||||||
homematicip==0.10.7
|
homematicip==0.10.9
|
||||||
|
|
||||||
# homeassistant.components.horizon
|
# homeassistant.components.horizon
|
||||||
horimote==0.4.1
|
horimote==0.4.1
|
||||||
|
|||||||
@@ -109,8 +109,7 @@ enocean==0.50
|
|||||||
ephem==3.7.6.0
|
ephem==3.7.6.0
|
||||||
|
|
||||||
# homeassistant.components.evohome
|
# homeassistant.components.evohome
|
||||||
# homeassistant.components.honeywell
|
evohomeclient==0.3.3
|
||||||
evohomeclient==0.3.2
|
|
||||||
|
|
||||||
# homeassistant.components.feedreader
|
# homeassistant.components.feedreader
|
||||||
feedparser-homeassistant==5.2.2.dev1
|
feedparser-homeassistant==5.2.2.dev1
|
||||||
@@ -160,13 +159,13 @@ hdate==0.8.8
|
|||||||
holidays==0.9.10
|
holidays==0.9.10
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20190702.0
|
home-assistant-frontend==20190705.0
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
homekit[IP]==0.14.0
|
homekit[IP]==0.14.0
|
||||||
|
|
||||||
# homeassistant.components.homematicip_cloud
|
# homeassistant.components.homematicip_cloud
|
||||||
homematicip==0.10.7
|
homematicip==0.10.9
|
||||||
|
|
||||||
# homeassistant.components.google
|
# homeassistant.components.google
|
||||||
# homeassistant.components.remember_the_milk
|
# homeassistant.components.remember_the_milk
|
||||||
|
|||||||
@@ -823,14 +823,15 @@ async def test_thermostat(hass):
|
|||||||
'climate.test_thermostat',
|
'climate.test_thermostat',
|
||||||
'cool',
|
'cool',
|
||||||
{
|
{
|
||||||
'operation_mode': 'cool',
|
|
||||||
'temperature': 70.0,
|
'temperature': 70.0,
|
||||||
'target_temp_high': 80.0,
|
'target_temp_high': 80.0,
|
||||||
'target_temp_low': 60.0,
|
'target_temp_low': 60.0,
|
||||||
'current_temperature': 75.0,
|
'current_temperature': 75.0,
|
||||||
'friendly_name': "Test Thermostat",
|
'friendly_name': "Test Thermostat",
|
||||||
'supported_features': 1 | 2 | 4 | 128,
|
'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,
|
'min_temp': 50,
|
||||||
'max_temp': 90,
|
'max_temp': 90,
|
||||||
}
|
}
|
||||||
@@ -948,22 +949,22 @@ async def test_thermostat(hass):
|
|||||||
# Setting mode, the payload can be an object with a value attribute...
|
# Setting mode, the payload can be an object with a value attribute...
|
||||||
call, msg = await assert_request_calls_service(
|
call, msg = await assert_request_calls_service(
|
||||||
'Alexa.ThermostatController', 'SetThermostatMode',
|
'Alexa.ThermostatController', 'SetThermostatMode',
|
||||||
'climate#test_thermostat', 'climate.set_operation_mode',
|
'climate#test_thermostat', 'climate.set_hvac_mode',
|
||||||
hass,
|
hass,
|
||||||
payload={'thermostatMode': {'value': 'HEAT'}}
|
payload={'thermostatMode': {'value': 'HEAT'}}
|
||||||
)
|
)
|
||||||
assert call.data['operation_mode'] == 'heat'
|
assert call.data['hvac_mode'] == 'heat'
|
||||||
properties = ReportedProperties(msg['context']['properties'])
|
properties = ReportedProperties(msg['context']['properties'])
|
||||||
properties.assert_equal(
|
properties.assert_equal(
|
||||||
'Alexa.ThermostatController', 'thermostatMode', 'HEAT')
|
'Alexa.ThermostatController', 'thermostatMode', 'HEAT')
|
||||||
|
|
||||||
call, msg = await assert_request_calls_service(
|
call, msg = await assert_request_calls_service(
|
||||||
'Alexa.ThermostatController', 'SetThermostatMode',
|
'Alexa.ThermostatController', 'SetThermostatMode',
|
||||||
'climate#test_thermostat', 'climate.set_operation_mode',
|
'climate#test_thermostat', 'climate.set_hvac_mode',
|
||||||
hass,
|
hass,
|
||||||
payload={'thermostatMode': {'value': 'COOL'}}
|
payload={'thermostatMode': {'value': 'COOL'}}
|
||||||
)
|
)
|
||||||
assert call.data['operation_mode'] == 'cool'
|
assert call.data['hvac_mode'] == 'cool'
|
||||||
properties = ReportedProperties(msg['context']['properties'])
|
properties = ReportedProperties(msg['context']['properties'])
|
||||||
properties.assert_equal(
|
properties.assert_equal(
|
||||||
'Alexa.ThermostatController', 'thermostatMode', 'COOL')
|
'Alexa.ThermostatController', 'thermostatMode', 'COOL')
|
||||||
@@ -971,18 +972,18 @@ async def test_thermostat(hass):
|
|||||||
# ...it can also be just the mode.
|
# ...it can also be just the mode.
|
||||||
call, msg = await assert_request_calls_service(
|
call, msg = await assert_request_calls_service(
|
||||||
'Alexa.ThermostatController', 'SetThermostatMode',
|
'Alexa.ThermostatController', 'SetThermostatMode',
|
||||||
'climate#test_thermostat', 'climate.set_operation_mode',
|
'climate#test_thermostat', 'climate.set_hvac_mode',
|
||||||
hass,
|
hass,
|
||||||
payload={'thermostatMode': 'HEAT'}
|
payload={'thermostatMode': 'HEAT'}
|
||||||
)
|
)
|
||||||
assert call.data['operation_mode'] == 'heat'
|
assert call.data['hvac_mode'] == 'heat'
|
||||||
properties = ReportedProperties(msg['context']['properties'])
|
properties = ReportedProperties(msg['context']['properties'])
|
||||||
properties.assert_equal(
|
properties.assert_equal(
|
||||||
'Alexa.ThermostatController', 'thermostatMode', 'HEAT')
|
'Alexa.ThermostatController', 'thermostatMode', 'HEAT')
|
||||||
|
|
||||||
msg = await assert_request_fails(
|
msg = await assert_request_fails(
|
||||||
'Alexa.ThermostatController', 'SetThermostatMode',
|
'Alexa.ThermostatController', 'SetThermostatMode',
|
||||||
'climate#test_thermostat', 'climate.set_operation_mode',
|
'climate#test_thermostat', 'climate.set_hvac_mode',
|
||||||
hass,
|
hass,
|
||||||
payload={'thermostatMode': {'value': 'INVALID'}}
|
payload={'thermostatMode': {'value': 'INVALID'}}
|
||||||
)
|
)
|
||||||
@@ -991,11 +992,20 @@ async def test_thermostat(hass):
|
|||||||
|
|
||||||
call, _ = await assert_request_calls_service(
|
call, _ = await assert_request_calls_service(
|
||||||
'Alexa.ThermostatController', 'SetThermostatMode',
|
'Alexa.ThermostatController', 'SetThermostatMode',
|
||||||
'climate#test_thermostat', 'climate.set_operation_mode',
|
'climate#test_thermostat', 'climate.set_hvac_mode',
|
||||||
hass,
|
hass,
|
||||||
payload={'thermostatMode': 'OFF'}
|
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):
|
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 import _LOGGER
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_FAN_MODE, ATTR_HOLD_MODE,
|
ATTR_AUX_HEAT, ATTR_FAN_MODE, ATTR_HUMIDITY, ATTR_HVAC_MODE,
|
||||||
ATTR_HUMIDITY, ATTR_OPERATION_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
|
ATTR_PRESET_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
|
||||||
ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AWAY_MODE, SERVICE_SET_HOLD_MODE,
|
ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE,
|
||||||
SERVICE_SET_AUX_HEAT, SERVICE_SET_TEMPERATURE, SERVICE_SET_HUMIDITY,
|
SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE,
|
||||||
SERVICE_SET_FAN_MODE, SERVICE_SET_OPERATION_MODE, SERVICE_SET_SWING_MODE)
|
SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE
|
||||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE)
|
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
|
|
||||||
|
|
||||||
async def async_set_away_mode(hass, away_mode, entity_id=None):
|
async def async_set_preset_mode(hass, preset_mode, entity_id=None):
|
||||||
"""Turn all or specified climate devices away mode on."""
|
"""Set new preset mode."""
|
||||||
data = {
|
data = {
|
||||||
ATTR_AWAY_MODE: away_mode
|
ATTR_PRESET_MODE: preset_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
if entity_id:
|
if entity_id:
|
||||||
data[ATTR_ENTITY_ID] = entity_id
|
data[ATTR_ENTITY_ID] = entity_id
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN, SERVICE_SET_AWAY_MODE, data, blocking=True)
|
DOMAIN, SERVICE_SET_PRESET_MODE, data, blocking=True)
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
def set_away_mode(hass, away_mode, entity_id=None):
|
def set_preset_mode(hass, preset_mode, entity_id=None):
|
||||||
"""Turn all or specified climate devices away mode on."""
|
"""Set new preset mode."""
|
||||||
data = {
|
data = {
|
||||||
ATTR_AWAY_MODE: away_mode
|
ATTR_PRESET_MODE: preset_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
if entity_id:
|
if entity_id:
|
||||||
data[ATTR_ENTITY_ID] = entity_id
|
data[ATTR_ENTITY_ID] = entity_id
|
||||||
|
|
||||||
hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
|
hass.services.call(DOMAIN, SERVICE_SET_PRESET_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)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_set_aux_heat(hass, aux_heat, entity_id=None):
|
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,
|
async def async_set_temperature(hass, temperature=None, entity_id=None,
|
||||||
target_temp_high=None, target_temp_low=None,
|
target_temp_high=None, target_temp_low=None,
|
||||||
operation_mode=None):
|
hvac_mode=None):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
kwargs = {
|
kwargs = {
|
||||||
key: value for key, value in [
|
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_HIGH, target_temp_high),
|
||||||
(ATTR_TARGET_TEMP_LOW, target_temp_low),
|
(ATTR_TARGET_TEMP_LOW, target_temp_low),
|
||||||
(ATTR_ENTITY_ID, entity_id),
|
(ATTR_ENTITY_ID, entity_id),
|
||||||
(ATTR_OPERATION_MODE, operation_mode)
|
(ATTR_HVAC_MODE, hvac_mode)
|
||||||
] if value is not None
|
] if value is not None
|
||||||
}
|
}
|
||||||
_LOGGER.debug("set_temperature start data=%s", kwargs)
|
_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
|
@bind_hass
|
||||||
def set_temperature(hass, temperature=None, entity_id=None,
|
def set_temperature(hass, temperature=None, entity_id=None,
|
||||||
target_temp_high=None, target_temp_low=None,
|
target_temp_high=None, target_temp_low=None,
|
||||||
operation_mode=None):
|
hvac_mode=None):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
kwargs = {
|
kwargs = {
|
||||||
key: value for key, value in [
|
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_HIGH, target_temp_high),
|
||||||
(ATTR_TARGET_TEMP_LOW, target_temp_low),
|
(ATTR_TARGET_TEMP_LOW, target_temp_low),
|
||||||
(ATTR_ENTITY_ID, entity_id),
|
(ATTR_ENTITY_ID, entity_id),
|
||||||
(ATTR_OPERATION_MODE, operation_mode)
|
(ATTR_HVAC_MODE, hvac_mode)
|
||||||
] if value is not None
|
] if value is not None
|
||||||
}
|
}
|
||||||
_LOGGER.debug("set_temperature start data=%s", kwargs)
|
_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)
|
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."""
|
"""Set new target operation mode."""
|
||||||
data = {ATTR_OPERATION_MODE: operation_mode}
|
data = {ATTR_HVAC_MODE: hvac_mode}
|
||||||
|
|
||||||
if entity_id is not None:
|
if entity_id is not None:
|
||||||
data[ATTR_ENTITY_ID] = entity_id
|
data[ATTR_ENTITY_ID] = entity_id
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN, SERVICE_SET_OPERATION_MODE, data, blocking=True)
|
DOMAIN, SERVICE_SET_HVAC_MODE, data, blocking=True)
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@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."""
|
"""Set new target operation mode."""
|
||||||
data = {ATTR_OPERATION_MODE: operation_mode}
|
data = {ATTR_HVAC_MODE: hvac_mode}
|
||||||
|
|
||||||
if entity_id is not None:
|
if entity_id is not None:
|
||||||
data[ATTR_ENTITY_ID] = entity_id
|
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):
|
async def async_set_swing_mode(hass, swing_mode, entity_id=None):
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"""The tests for the climate component."""
|
"""The tests for the climate component."""
|
||||||
import asyncio
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -8,24 +7,22 @@ from homeassistant.components.climate import SET_TEMPERATURE_SCHEMA
|
|||||||
from tests.common import async_mock_service
|
from tests.common import async_mock_service
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def test_set_temp_schema_no_req(hass, caplog):
|
||||||
def test_set_temp_schema_no_req(hass, caplog):
|
|
||||||
"""Test the set temperature schema with missing required data."""
|
"""Test the set temperature schema with missing required data."""
|
||||||
domain = 'climate'
|
domain = 'climate'
|
||||||
service = 'test_set_temperature'
|
service = 'test_set_temperature'
|
||||||
schema = SET_TEMPERATURE_SCHEMA
|
schema = SET_TEMPERATURE_SCHEMA
|
||||||
calls = async_mock_service(hass, domain, service, 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):
|
with pytest.raises(vol.Invalid):
|
||||||
yield from hass.services.async_call(domain, service, data)
|
await hass.services.async_call(domain, service, data)
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(calls) == 0
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def test_set_temp_schema(hass, caplog):
|
||||||
def test_set_temp_schema(hass, caplog):
|
|
||||||
"""Test the set temperature schema with ok required data."""
|
"""Test the set temperature schema with ok required data."""
|
||||||
domain = 'climate'
|
domain = 'climate'
|
||||||
service = 'test_set_temperature'
|
service = 'test_set_temperature'
|
||||||
@@ -33,10 +30,10 @@ def test_set_temp_schema(hass, caplog):
|
|||||||
calls = async_mock_service(hass, domain, service, schema)
|
calls = async_mock_service(hass, domain, service, schema)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'temperature': 20.0, 'operation_mode': 'test',
|
'temperature': 20.0, 'hvac_mode': 'heat',
|
||||||
'entity_id': ['climate.test_id']}
|
'entity_id': ['climate.test_id']}
|
||||||
yield from hass.services.async_call(domain, service, data)
|
await hass.services.async_call(domain, service, data)
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
assert calls[-1].data == data
|
assert calls[-1].data == data
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant.components.climate import async_reproduce_states
|
from homeassistant.components.climate import async_reproduce_states
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_HOLD_MODE, ATTR_HUMIDITY,
|
ATTR_AUX_HEAT, ATTR_HUMIDITY, ATTR_PRESET_MODE, ATTR_SWING_MODE,
|
||||||
ATTR_OPERATION_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
|
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, HVAC_MODE_AUTO,
|
||||||
ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AUX_HEAT, SERVICE_SET_AWAY_MODE,
|
HVAC_MODE_HEAT, HVAC_MODE_OFF, SERVICE_SET_AUX_HEAT, SERVICE_SET_HUMIDITY,
|
||||||
SERVICE_SET_HOLD_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_OPERATION_MODE,
|
SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_SWING_MODE,
|
||||||
SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, STATE_HEAT)
|
SERVICE_SET_TEMPERATURE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_TEMPERATURE
|
||||||
ATTR_TEMPERATURE, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, STATE_ON)
|
|
||||||
from homeassistant.core import Context, State
|
from homeassistant.core import Context, State
|
||||||
|
|
||||||
from tests.common import async_mock_service
|
from tests.common import async_mock_service
|
||||||
@@ -20,13 +19,11 @@ ENTITY_2 = 'climate.test2'
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'service,state', [
|
'state', [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||||
(SERVICE_TURN_ON, STATE_ON),
|
)
|
||||||
(SERVICE_TURN_OFF, STATE_OFF),
|
async def test_with_hvac_mode(hass, state):
|
||||||
])
|
"""Test that state different hvac states."""
|
||||||
async def test_state(hass, service, state):
|
calls = async_mock_service(hass, DOMAIN, SERVICE_SET_HVAC_MODE)
|
||||||
"""Test that we can turn a state into a service call."""
|
|
||||||
calls_1 = async_mock_service(hass, DOMAIN, service)
|
|
||||||
|
|
||||||
await async_reproduce_states(hass, [
|
await async_reproduce_states(hass, [
|
||||||
State(ENTITY_1, state)
|
State(ENTITY_1, state)
|
||||||
@@ -34,110 +31,66 @@ async def test_state(hass, service, state):
|
|||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(calls_1) == 1
|
assert len(calls) == 1
|
||||||
assert calls_1[0].data == {'entity_id': ENTITY_1}
|
assert calls[0].data == {'entity_id': ENTITY_1, 'hvac_mode': state}
|
||||||
|
|
||||||
|
|
||||||
async def test_turn_on_with_mode(hass):
|
async def test_multiple_state(hass):
|
||||||
"""Test that state with additional attributes call multiple services."""
|
"""Test that multiple states gets calls."""
|
||||||
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
|
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_SET_HVAC_MODE)
|
||||||
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_SET_OPERATION_MODE)
|
|
||||||
|
|
||||||
await async_reproduce_states(hass, [
|
await async_reproduce_states(hass, [
|
||||||
State(ENTITY_1, 'on',
|
State(ENTITY_1, HVAC_MODE_HEAT),
|
||||||
{ATTR_OPERATION_MODE: STATE_HEAT})
|
State(ENTITY_2, HVAC_MODE_AUTO),
|
||||||
])
|
|
||||||
|
|
||||||
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'),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(calls_1) == 2
|
assert len(calls_1) == 2
|
||||||
# order is not guaranteed
|
# order is not guaranteed
|
||||||
assert any(call.data == {'entity_id': ENTITY_1} for call in calls_1)
|
assert any(
|
||||||
assert any(call.data == {'entity_id': ENTITY_2} for call in calls_1)
|
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):
|
async def test_state_with_none(hass):
|
||||||
"""Test that multiple states with different state gets calls."""
|
"""Test that none is not a hvac state."""
|
||||||
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
|
calls = async_mock_service(hass, DOMAIN, SERVICE_SET_HVAC_MODE)
|
||||||
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF)
|
|
||||||
|
|
||||||
await async_reproduce_states(hass, [
|
await async_reproduce_states(hass, [
|
||||||
State(ENTITY_1, 'on'),
|
State(ENTITY_1, None)
|
||||||
State(ENTITY_2, 'off'),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(calls_1) == 1
|
assert len(calls) == 0
|
||||||
assert calls_1[0].data == {'entity_id': ENTITY_1}
|
|
||||||
assert len(calls_2) == 1
|
|
||||||
assert calls_2[0].data == {'entity_id': ENTITY_2}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_state_with_context(hass):
|
async def test_state_with_context(hass):
|
||||||
"""Test that context is forwarded."""
|
"""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()
|
context = Context()
|
||||||
|
|
||||||
await async_reproduce_states(hass, [
|
await async_reproduce_states(hass, [
|
||||||
State(ENTITY_1, 'on')
|
State(ENTITY_1, HVAC_MODE_HEAT)
|
||||||
], context)
|
], context)
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(calls) == 1
|
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
|
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(
|
@pytest.mark.parametrize(
|
||||||
'service,attribute', [
|
'service,attribute', [
|
||||||
(SERVICE_SET_OPERATION_MODE, ATTR_OPERATION_MODE),
|
|
||||||
(SERVICE_SET_AUX_HEAT, ATTR_AUX_HEAT),
|
(SERVICE_SET_AUX_HEAT, ATTR_AUX_HEAT),
|
||||||
(SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE),
|
(SERVICE_SET_PRESET_MODE, ATTR_PRESET_MODE),
|
||||||
(SERVICE_SET_HOLD_MODE, ATTR_HOLD_MODE),
|
|
||||||
(SERVICE_SET_SWING_MODE, ATTR_SWING_MODE),
|
(SERVICE_SET_SWING_MODE, ATTR_SWING_MODE),
|
||||||
(SERVICE_SET_HUMIDITY, ATTR_HUMIDITY),
|
(SERVICE_SET_HUMIDITY, ATTR_HUMIDITY),
|
||||||
(SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE),
|
(SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE),
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ async def test_climate_devices(hass):
|
|||||||
{'state': {'on': False}})
|
{'state': {'on': False}})
|
||||||
|
|
||||||
await hass.services.async_call(
|
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
|
blocking=True
|
||||||
)
|
)
|
||||||
gateway.api.session.put.assert_called_with(
|
gateway.api.session.put.assert_called_with(
|
||||||
@@ -116,7 +117,8 @@ async def test_climate_devices(hass):
|
|||||||
)
|
)
|
||||||
|
|
||||||
await hass.services.async_call(
|
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
|
blocking=True
|
||||||
)
|
)
|
||||||
gateway.api.session.put.assert_called_with(
|
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
|
assert "climate.climate_1_name" in gateway.deconz_ids
|
||||||
|
|
||||||
thermostat = hass.states.get('climate.climate_1_name')
|
thermostat = hass.states.get('climate.climate_1_name')
|
||||||
assert thermostat.state == 'on'
|
assert thermostat.state == 'off'
|
||||||
|
|
||||||
state_update = {
|
state_update = {
|
||||||
"t": "event",
|
"t": "event",
|
||||||
|
|||||||
@@ -1,284 +1,281 @@
|
|||||||
"""The tests for the demo climate component."""
|
"""The tests for the demo climate component."""
|
||||||
import unittest
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.util.unit_system import (
|
from homeassistant.components.climate.const import (
|
||||||
METRIC_SYSTEM
|
ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_HVAC_ACTIONS,
|
||||||
)
|
ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, ATTR_HUMIDITY, ATTR_HVAC_MODES,
|
||||||
from homeassistant.setup import setup_component
|
ATTR_MAX_HUMIDITY, ATTR_MAX_TEMP, ATTR_MIN_HUMIDITY, ATTR_MIN_TEMP,
|
||||||
from homeassistant.components.climate import (
|
ATTR_PRESET_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
|
||||||
DOMAIN, SERVICE_TURN_OFF, SERVICE_TURN_ON)
|
ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, DOMAIN,
|
||||||
from homeassistant.const import (ATTR_ENTITY_ID)
|
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
|
from tests.components.climate import common
|
||||||
|
|
||||||
|
|
||||||
ENTITY_CLIMATE = 'climate.hvac'
|
ENTITY_CLIMATE = 'climate.hvac'
|
||||||
ENTITY_ECOBEE = 'climate.ecobee'
|
ENTITY_ECOBEE = 'climate.ecobee'
|
||||||
ENTITY_HEATPUMP = 'climate.heatpump'
|
ENTITY_HEATPUMP = 'climate.heatpump'
|
||||||
|
|
||||||
|
|
||||||
class TestDemoClimate(unittest.TestCase):
|
@pytest.fixture(autouse=True)
|
||||||
"""Test the demo climate hvac."""
|
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
|
def test_setup_params(hass):
|
||||||
"""Stop down everything that was started."""
|
"""Test the initial parameters."""
|
||||||
self.hass.stop()
|
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):
|
def test_default_setup_params(hass):
|
||||||
"""Test the setup with default parameters."""
|
"""Test the setup with default parameters."""
|
||||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
assert 7 == state.attributes.get('min_temp')
|
assert 7 == state.attributes.get(ATTR_MIN_TEMP)
|
||||||
assert 35 == state.attributes.get('max_temp')
|
assert 35 == state.attributes.get(ATTR_MAX_TEMP)
|
||||||
assert 30 == state.attributes.get('min_humidity')
|
assert 30 == state.attributes.get(ATTR_MIN_HUMIDITY)
|
||||||
assert 99 == state.attributes.get('max_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):
|
async def test_set_only_target_temp_bad_attr(hass):
|
||||||
"""Test the setting of the target temperature."""
|
"""Test setting the target temperature without required attribute."""
|
||||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
assert 21 == state.attributes.get('temperature')
|
assert 21 == state.attributes.get(ATTR_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')
|
|
||||||
|
|
||||||
def test_set_only_target_temp_with_convert(self):
|
with pytest.raises(vol.Invalid):
|
||||||
"""Test the setting of the target temperature."""
|
await common.async_set_temperature(hass, None, ENTITY_CLIMATE)
|
||||||
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')
|
|
||||||
|
|
||||||
def test_set_target_temp_range(self):
|
await hass.async_block_till_done()
|
||||||
"""Test the setting of the target temperature with range."""
|
assert 21 == state.attributes.get(ATTR_TEMPERATURE)
|
||||||
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')
|
|
||||||
|
|
||||||
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):
|
async def test_set_only_target_temp(hass):
|
||||||
"""Test setting the target humidity without required attribute."""
|
"""Test the setting of the target temperature."""
|
||||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
assert 67 == state.attributes.get('humidity')
|
assert 21 == state.attributes.get(ATTR_TEMPERATURE)
|
||||||
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')
|
|
||||||
|
|
||||||
def test_set_target_humidity(self):
|
await common.async_set_temperature(hass, 30, ENTITY_CLIMATE)
|
||||||
"""Test the setting of the target humidity."""
|
await hass.async_block_till_done()
|
||||||
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')
|
|
||||||
|
|
||||||
def test_set_fan_mode_bad_attr(self):
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
"""Test setting fan mode without required attribute."""
|
assert 30.0 == state.attributes.get(ATTR_TEMPERATURE)
|
||||||
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')
|
|
||||||
|
|
||||||
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):
|
async def test_set_only_target_temp_with_convert(hass):
|
||||||
"""Test setting swing mode without required attribute."""
|
"""Test the setting of the target temperature."""
|
||||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
state = hass.states.get(ENTITY_HEATPUMP)
|
||||||
assert "Off" == state.attributes.get('swing_mode')
|
assert 20 == state.attributes.get(ATTR_TEMPERATURE)
|
||||||
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')
|
|
||||||
|
|
||||||
def test_set_swing(self):
|
await common.async_set_temperature(hass, 21, ENTITY_HEATPUMP)
|
||||||
"""Test setting of new swing mode."""
|
await hass.async_block_till_done()
|
||||||
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')
|
|
||||||
|
|
||||||
def test_set_operation_bad_attr_and_state(self):
|
state = hass.states.get(ENTITY_HEATPUMP)
|
||||||
"""Test setting operation mode without required attribute.
|
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):
|
async def test_set_target_temp_range(hass):
|
||||||
"""Test setting of new operation mode."""
|
"""Test the setting of the target temperature with range."""
|
||||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
state = hass.states.get(ENTITY_ECOBEE)
|
||||||
assert "cool" == state.attributes.get('operation_mode')
|
assert state.attributes.get(ATTR_TEMPERATURE) is None
|
||||||
assert "cool" == state.state
|
assert 21.0 == state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||||
common.set_operation_mode(self.hass, "heat", ENTITY_CLIMATE)
|
assert 24.0 == state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||||
self.hass.block_till_done()
|
|
||||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
|
||||||
assert "heat" == state.attributes.get('operation_mode')
|
|
||||||
assert "heat" == state.state
|
|
||||||
|
|
||||||
def test_set_away_mode_bad_attr(self):
|
await common.async_set_temperature(
|
||||||
"""Test setting the away mode without required attribute."""
|
hass, target_temp_high=25, target_temp_low=20, entity_id=ENTITY_ECOBEE)
|
||||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
await hass.async_block_till_done()
|
||||||
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')
|
|
||||||
|
|
||||||
def test_set_away_mode_on(self):
|
state = hass.states.get(ENTITY_ECOBEE)
|
||||||
"""Test setting the away mode on/true."""
|
assert state.attributes.get(ATTR_TEMPERATURE) is None
|
||||||
common.set_away_mode(self.hass, True, ENTITY_CLIMATE)
|
assert 20.0 == state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||||
self.hass.block_till_done()
|
assert 25.0 == state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
|
||||||
assert 'on' == state.attributes.get('away_mode')
|
|
||||||
|
|
||||||
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):
|
async def test_set_target_temp_range_bad_attr(hass):
|
||||||
"""Test setting the hold mode home."""
|
"""Test setting the target temperature range without attribute."""
|
||||||
common.set_hold_mode(self.hass, 'home', ENTITY_ECOBEE)
|
state = hass.states.get(ENTITY_ECOBEE)
|
||||||
self.hass.block_till_done()
|
assert state.attributes.get(ATTR_TEMPERATURE) is None
|
||||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
assert 21.0 == state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||||
assert 'home' == state.attributes.get('hold_mode')
|
assert 24.0 == state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||||
|
|
||||||
def test_set_hold_mode_away(self):
|
with pytest.raises(vol.Invalid):
|
||||||
"""Test setting the hold mode away."""
|
await common.async_set_temperature(
|
||||||
common.set_hold_mode(self.hass, 'away', ENTITY_ECOBEE)
|
hass, temperature=None, entity_id=ENTITY_ECOBEE,
|
||||||
self.hass.block_till_done()
|
target_temp_low=None, target_temp_high=None)
|
||||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
await hass.async_block_till_done()
|
||||||
assert 'away' == state.attributes.get('hold_mode')
|
|
||||||
|
|
||||||
def test_set_hold_mode_none(self):
|
state = hass.states.get(ENTITY_ECOBEE)
|
||||||
"""Test setting the hold mode off/false."""
|
assert state.attributes.get(ATTR_TEMPERATURE) is None
|
||||||
common.set_hold_mode(self.hass, 'off', ENTITY_ECOBEE)
|
assert 21.0 == state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||||
self.hass.block_till_done()
|
assert 24.0 == state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
|
||||||
assert 'off' == state.attributes.get('hold_mode')
|
|
||||||
|
|
||||||
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):
|
async def test_set_target_humidity_bad_attr(hass):
|
||||||
"""Test setting the axillary heater on/true."""
|
"""Test setting the target humidity without required attribute."""
|
||||||
common.set_aux_heat(self.hass, True, ENTITY_CLIMATE)
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
self.hass.block_till_done()
|
assert 67 == state.attributes.get(ATTR_HUMIDITY)
|
||||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
|
||||||
assert 'on' == state.attributes.get('aux_heat')
|
|
||||||
|
|
||||||
def test_set_aux_heat_off(self):
|
with pytest.raises(vol.Invalid):
|
||||||
"""Test setting the auxiliary heater off/false."""
|
await common.async_set_humidity(hass, None, ENTITY_CLIMATE)
|
||||||
common.set_aux_heat(self.hass, False, ENTITY_CLIMATE)
|
await hass.async_block_till_done()
|
||||||
self.hass.block_till_done()
|
|
||||||
state = self.hass.states.get(ENTITY_CLIMATE)
|
|
||||||
assert 'off' == state.attributes.get('aux_heat')
|
|
||||||
|
|
||||||
def test_set_on_off(self):
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
"""Test on/off service."""
|
assert 67 == state.attributes.get(ATTR_HUMIDITY)
|
||||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
|
||||||
assert 'auto' == state.state
|
|
||||||
|
|
||||||
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,
|
async def test_set_target_humidity(hass):
|
||||||
{ATTR_ENTITY_ID: ENTITY_ECOBEE})
|
"""Test the setting of the target humidity."""
|
||||||
self.hass.block_till_done()
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
state = self.hass.states.get(ENTITY_ECOBEE)
|
assert 67 == state.attributes.get(ATTR_HUMIDITY)
|
||||||
assert 'auto' == state.state
|
|
||||||
|
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)
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||||
assert not entity.should_poll
|
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 = device.set_configuration
|
||||||
set_config.assert_called_with(focus_mode=FocusMode.FOCUS_ON)
|
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 = device.set_configuration
|
||||||
set_config.assert_called_with(focus_mode=FocusMode.FOCUS_OFF)
|
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."""
|
"""Test get fan list."""
|
||||||
device = _get_device_heat_on()
|
device = _get_device_heat_on()
|
||||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||||
assert len(entity.fan_list) == 2
|
assert len(entity.fan_modes) == 2
|
||||||
assert dyson.STATE_FOCUS in entity.fan_list
|
assert dyson.FAN_FOCUS in entity.fan_modes
|
||||||
assert dyson.STATE_DIFFUSE in entity.fan_list
|
assert dyson.FAN_DIFFUSE in entity.fan_modes
|
||||||
|
|
||||||
def test_dyson_fan_mode_focus(self):
|
def test_dyson_fan_mode_focus(self):
|
||||||
"""Test fan focus mode."""
|
"""Test fan focus mode."""
|
||||||
device = _get_device_focus()
|
device = _get_device_focus()
|
||||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
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):
|
def test_dyson_fan_mode_diffuse(self):
|
||||||
"""Test fan diffuse mode."""
|
"""Test fan diffuse mode."""
|
||||||
device = _get_device_diffuse()
|
device = _get_device_diffuse()
|
||||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
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."""
|
"""Test set operation mode."""
|
||||||
device = _get_device_heat_on()
|
device = _get_device_heat_on()
|
||||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||||
assert not entity.should_poll
|
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 = device.set_configuration
|
||||||
set_config.assert_called_with(heat_mode=HeatMode.HEAT_ON)
|
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 = device.set_configuration
|
||||||
set_config.assert_called_with(heat_mode=HeatMode.HEAT_OFF)
|
set_config.assert_called_with(heat_mode=HeatMode.HEAT_OFF)
|
||||||
|
|
||||||
@@ -276,15 +276,15 @@ class DysonTest(unittest.TestCase):
|
|||||||
"""Test get operation list."""
|
"""Test get operation list."""
|
||||||
device = _get_device_heat_on()
|
device = _get_device_heat_on()
|
||||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
||||||
assert len(entity.operation_list) == 2
|
assert len(entity.hvac_modes) == 2
|
||||||
assert dyson.STATE_HEAT in entity.operation_list
|
assert dyson.HVAC_MODE_HEAT in entity.hvac_modes
|
||||||
assert dyson.STATE_COOL in entity.operation_list
|
assert dyson.HVAC_MODE_COOL in entity.hvac_modes
|
||||||
|
|
||||||
def test_dyson_heat_off(self):
|
def test_dyson_heat_off(self):
|
||||||
"""Test turn off heat."""
|
"""Test turn off heat."""
|
||||||
device = _get_device_heat_off()
|
device = _get_device_heat_off()
|
||||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
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 = device.set_configuration
|
||||||
set_config.assert_called_with(heat_mode=HeatMode.HEAT_OFF)
|
set_config.assert_called_with(heat_mode=HeatMode.HEAT_OFF)
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ class DysonTest(unittest.TestCase):
|
|||||||
"""Test turn on heat."""
|
"""Test turn on heat."""
|
||||||
device = _get_device_heat_on()
|
device = _get_device_heat_on()
|
||||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
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 = device.set_configuration
|
||||||
set_config.assert_called_with(heat_mode=HeatMode.HEAT_ON)
|
set_config.assert_called_with(heat_mode=HeatMode.HEAT_ON)
|
||||||
|
|
||||||
@@ -300,19 +300,20 @@ class DysonTest(unittest.TestCase):
|
|||||||
"""Test get heat value on."""
|
"""Test get heat value on."""
|
||||||
device = _get_device_heat_on()
|
device = _get_device_heat_on()
|
||||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
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):
|
def test_dyson_heat_value_off(self):
|
||||||
"""Test get heat value off."""
|
"""Test get heat value off."""
|
||||||
device = _get_device_cool()
|
device = _get_device_cool()
|
||||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
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):
|
def test_dyson_heat_value_idle(self):
|
||||||
"""Test get heat value idle."""
|
"""Test get heat value idle."""
|
||||||
device = _get_device_heat_off()
|
device = _get_device_heat_off()
|
||||||
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
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):
|
def test_on_message(self):
|
||||||
"""Test when message is received."""
|
"""Test when message is received."""
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user