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:
Pascal Vizeli 2019-07-08 14:00:24 +02:00 committed by GitHub
parent c2f1c4b981
commit 84cf76ba36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
119 changed files with 5240 additions and 5525 deletions

View File

@ -18,6 +18,7 @@
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"files.trimTrailingWhitespace": true,
"editor.rulers": [80]
"editor.rulers": [80],
"terminal.integrated.shell.linux": "/bin/bash"
}
}

5
.gitignore vendored
View File

@ -94,7 +94,10 @@ virtualization/vagrant/.vagrant
virtualization/vagrant/config
# Visual Studio Code
.vscode
.vscode/*
!.vscode/cSpell.json
!.vscode/extensions.json
!.vscode/tasks.json
# Built docs
docs/build

92
.vscode/tasks.json vendored Normal file
View 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": []
}
]
}

View File

@ -23,6 +23,7 @@ import homeassistant.util.color as color_util
from .const import (
API_TEMP_UNITS,
API_THERMOSTAT_MODES,
API_THERMOSTAT_PRESETS,
DATE_FORMAT,
PERCENTAGE_FAN_MAP,
)
@ -180,9 +181,13 @@ class AlexaPowerController(AlexaCapibility):
if name != 'powerState':
raise UnsupportedProperty(name)
if self.entity.state == STATE_OFF:
return 'OFF'
return 'ON'
if self.entity.domain == climate.DOMAIN:
is_on = self.entity.state != climate.HVAC_MODE_OFF
else:
is_on = self.entity.state != STATE_OFF
return 'ON' if is_on else 'OFF'
class AlexaLockController(AlexaCapibility):
@ -546,16 +551,13 @@ class AlexaThermostatController(AlexaCapibility):
def properties_supported(self):
"""Return what properties this entity supports."""
properties = []
properties = [{'name': 'thermostatMode'}]
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & climate.SUPPORT_TARGET_TEMPERATURE:
properties.append({'name': 'targetSetpoint'})
if supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW:
if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE:
properties.append({'name': 'lowerSetpoint'})
if supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH:
properties.append({'name': 'upperSetpoint'})
if supported & climate.SUPPORT_OPERATION_MODE:
properties.append({'name': 'thermostatMode'})
return properties
def properties_proactively_reported(self):
@ -569,13 +571,18 @@ class AlexaThermostatController(AlexaCapibility):
def get_property(self, name):
"""Read and return a property."""
if name == 'thermostatMode':
ha_mode = self.entity.attributes.get(climate.ATTR_OPERATION_MODE)
mode = API_THERMOSTAT_MODES.get(ha_mode)
if mode is None:
_LOGGER.error("%s (%s) has unsupported %s value '%s'",
self.entity.entity_id, type(self.entity),
climate.ATTR_OPERATION_MODE, ha_mode)
raise UnsupportedProperty(name)
preset = self.entity.attributes.get(climate.ATTR_PRESET_MODE)
if preset in API_THERMOSTAT_PRESETS:
mode = API_THERMOSTAT_PRESETS[preset]
else:
mode = API_THERMOSTAT_MODES.get(self.entity.state)
if mode is None:
_LOGGER.error(
"%s (%s) has unsupported state value '%s'",
self.entity.entity_id, type(self.entity),
self.entity.state)
raise UnsupportedProperty(name)
return mode
unit = self.hass.config.units.temperature_unit

View File

@ -2,7 +2,6 @@
from collections import OrderedDict
from homeassistant.const import (
STATE_OFF,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
@ -57,16 +56,17 @@ API_TEMP_UNITS = {
# reverse mapping of this dict and we want to map the first occurrance of OFF
# back to HA state.
API_THERMOSTAT_MODES = OrderedDict([
(climate.STATE_HEAT, 'HEAT'),
(climate.STATE_COOL, 'COOL'),
(climate.STATE_AUTO, 'AUTO'),
(climate.STATE_ECO, 'ECO'),
(climate.STATE_MANUAL, 'AUTO'),
(STATE_OFF, 'OFF'),
(climate.STATE_IDLE, 'OFF'),
(climate.STATE_FAN_ONLY, 'OFF'),
(climate.STATE_DRY, 'OFF'),
(climate.HVAC_MODE_HEAT, 'HEAT'),
(climate.HVAC_MODE_COOL, 'COOL'),
(climate.HVAC_MODE_HEAT_COOL, 'AUTO'),
(climate.HVAC_MODE_AUTO, 'AUTO'),
(climate.HVAC_MODE_OFF, 'OFF'),
(climate.HVAC_MODE_FAN_ONLY, 'OFF'),
(climate.HVAC_MODE_DRY, 'OFF'),
])
API_THERMOSTAT_PRESETS = {
climate.PRESET_ECO: 'ECO'
}
PERCENTAGE_FAN_MAP = {
fan.SPEED_LOW: 33,

View File

@ -248,9 +248,11 @@ class ClimateCapabilities(AlexaEntity):
def interfaces(self):
"""Yield the supported interfaces."""
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & climate.SUPPORT_ON_OFF:
# If we support two modes, one being off, we allow turning on too.
if len([v for v in self.entity.attributes[climate.ATTR_HVAC_MODES]
if v != climate.HVAC_MODE_OFF]) == 1:
yield AlexaPowerController(self.entity)
yield AlexaThermostatController(self.hass, self.entity)
yield AlexaTemperatureSensor(self.hass, self.entity)
yield AlexaEndpointHealth(self.hass, self.entity)

View File

@ -33,6 +33,7 @@ from homeassistant.util.temperature import convert as convert_temperature
from .const import (
API_TEMP_UNITS,
API_THERMOSTAT_MODES,
API_THERMOSTAT_PRESETS,
Cause,
)
from .entities import async_get_entities
@ -686,23 +687,45 @@ async def async_api_set_thermostat_mode(hass, config, directive, context):
mode = directive.payload['thermostatMode']
mode = mode if isinstance(mode, str) else mode['value']
operation_list = entity.attributes.get(climate.ATTR_OPERATION_LIST)
ha_mode = next(
(k for k, v in API_THERMOSTAT_MODES.items() if v == mode),
None
)
if ha_mode not in operation_list:
msg = 'The requested thermostat mode {} is not supported'.format(mode)
raise AlexaUnsupportedThermostatModeError(msg)
data = {
ATTR_ENTITY_ID: entity.entity_id,
climate.ATTR_OPERATION_MODE: ha_mode,
}
ha_preset = next(
(k for k, v in API_THERMOSTAT_PRESETS.items() if v == mode),
None
)
if ha_preset:
presets = entity.attributes.get(climate.ATTR_PRESET_MODES, [])
if ha_preset not in presets:
msg = 'The requested thermostat mode {} is not supported'.format(
ha_preset
)
raise AlexaUnsupportedThermostatModeError(msg)
service = climate.SERVICE_SET_PRESET_MODE
data[climate.ATTR_PRESET_MODE] = climate.PRESET_ECO
else:
operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES)
ha_mode = next(
(k for k, v in API_THERMOSTAT_MODES.items() if v == mode),
None
)
if ha_mode not in operation_list:
msg = 'The requested thermostat mode {} is not supported'.format(
mode
)
raise AlexaUnsupportedThermostatModeError(msg)
service = climate.SERVICE_SET_HVAC_MODE
data[climate.ATTR_HVAC_MODE] = ha_mode
response = directive.response()
await hass.services.async_call(
entity.domain, climate.SERVICE_SET_OPERATION_MODE, data,
climate.DOMAIN, service, data,
blocking=False, context=context)
response.add_context_property({
'name': 'thermostatMode',

View File

@ -7,11 +7,8 @@ import voluptuous as vol
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE,
SUPPORT_ON_OFF, STATE_HEAT)
from homeassistant.const import ATTR_NAME
from homeassistant.const import (ATTR_TEMPERATURE,
STATE_OFF, TEMP_CELSIUS)
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, HVAC_MODE_HEAT)
from homeassistant.const import ATTR_NAME, ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import (ATTR_VALUE, CONF_CLIENT_ID, CONF_CLIENT_SECRET,
@ -20,8 +17,7 @@ from .const import (ATTR_VALUE, CONF_CLIENT_ID, CONF_CLIENT_SECRET,
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_ON_OFF)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
SEND_COMFORT_FEEDBACK_SCHEMA = vol.Schema({
vol.Required(ATTR_NAME): cv.string,
@ -177,11 +173,6 @@ class AmbiclimateEntity(ClimateDevice):
"""Return the current humidity."""
return self._data.get('humidity')
@property
def is_on(self):
"""Return true if heater is on."""
return self._data.get('power', '').lower() == 'on'
@property
def min_temp(self):
"""Return the minimum temperature."""
@ -198,9 +189,12 @@ class AmbiclimateEntity(ClimateDevice):
return SUPPORT_FLAGS
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation."""
return STATE_HEAT if self.is_on else STATE_OFF
if self._data.get('power', '').lower() == 'on':
return HVAC_MODE_HEAT
return HVAC_MODE_OFF
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
@ -209,13 +203,13 @@ class AmbiclimateEntity(ClimateDevice):
return
await self._heater.set_target_temperature(temperature)
async def async_turn_on(self):
"""Turn device on."""
await self._heater.turn_on()
async def async_turn_off(self):
"""Turn device off."""
await self._heater.turn_off()
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if hvac_mode == HVAC_MODE_HEAT:
await self._heater.turn_on()
return
if hvac_mode == HVAC_MODE_OFF:
await self._heater.turn_off()
async def async_update(self):
"""Retrieve latest state."""

View File

@ -1,68 +1,41 @@
"""Provides functionality to interact with climate devices."""
from datetime import timedelta
import logging
import functools as ft
import logging
from typing import Any, Awaitable, Dict, List, Optional
import voluptuous as vol
from homeassistant.helpers.temperature import display_temp as show_temp
from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, PRECISION_TENTHS, PRECISION_WHOLE,
STATE_OFF, STATE_ON, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import ( # noqa
PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE)
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF,
STATE_ON, STATE_OFF, TEMP_CELSIUS, PRECISION_WHOLE,
PRECISION_TENTHS)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.temperature import display_temp as show_temp
from homeassistant.helpers.typing import (
ConfigType, HomeAssistantType, ServiceDataType)
from homeassistant.util.temperature import convert as convert_temperature
from .const import (
ATTR_AUX_HEAT,
ATTR_AWAY_MODE,
ATTR_CURRENT_HUMIDITY,
ATTR_CURRENT_TEMPERATURE,
ATTR_FAN_LIST,
ATTR_FAN_MODE,
ATTR_HOLD_MODE,
ATTR_HUMIDITY,
ATTR_MAX_HUMIDITY,
ATTR_MAX_TEMP,
ATTR_MIN_HUMIDITY,
ATTR_MIN_TEMP,
ATTR_OPERATION_LIST,
ATTR_OPERATION_MODE,
ATTR_SWING_LIST,
ATTR_SWING_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
ATTR_TARGET_TEMP_STEP,
DOMAIN,
SERVICE_SET_AUX_HEAT,
SERVICE_SET_AWAY_MODE,
SERVICE_SET_FAN_MODE,
SERVICE_SET_HOLD_MODE,
SERVICE_SET_HUMIDITY,
SERVICE_SET_OPERATION_MODE,
SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW,
SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_HUMIDITY_HIGH,
SUPPORT_TARGET_HUMIDITY_LOW,
SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE,
SUPPORT_HOLD_MODE,
SUPPORT_SWING_MODE,
SUPPORT_AWAY_MODE,
SUPPORT_AUX_HEAT,
)
ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE,
ATTR_FAN_MODE, ATTR_FAN_MODES, ATTR_HUMIDITY, ATTR_HVAC_ACTIONS,
ATTR_HVAC_MODE, ATTR_HVAC_MODES, ATTR_MAX_HUMIDITY, ATTR_MAX_TEMP,
ATTR_MIN_HUMIDITY, ATTR_MIN_TEMP, ATTR_PRESET_MODE, ATTR_PRESET_MODES,
ATTR_SWING_MODE, ATTR_SWING_MODES, ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_STEP, DOMAIN, HVAC_MODES,
SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY,
SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE,
SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_TEMPERATURE_RANGE)
from .reproduce_state import async_reproduce_states # noqa
DEFAULT_MIN_TEMP = 7
DEFAULT_MAX_TEMP = 35
DEFAULT_MIN_HUMITIDY = 30
DEFAULT_MIN_HUMIDITY = 30
DEFAULT_MAX_HUMIDITY = 99
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@ -76,14 +49,6 @@ CONVERTIBLE_ATTRIBUTE = [
_LOGGER = logging.getLogger(__name__)
ON_OFF_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})
SET_AWAY_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_AWAY_MODE): cv.boolean,
})
SET_AUX_HEAT_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_AUX_HEAT): cv.boolean,
@ -96,20 +61,20 @@ SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All(
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Optional(ATTR_OPERATION_MODE): cv.string,
vol.Optional(ATTR_HVAC_MODE): vol.In(HVAC_MODES),
}
))
SET_FAN_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_FAN_MODE): cv.string,
})
SET_HOLD_MODE_SCHEMA = vol.Schema({
SET_PRESET_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_HOLD_MODE): cv.string,
vol.Required(ATTR_PRESET_MODE): vol.Maybe(cv.string),
})
SET_OPERATION_MODE_SCHEMA = vol.Schema({
SET_HVAC_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_OPERATION_MODE): cv.string,
vol.Required(ATTR_HVAC_MODE): vol.In(HVAC_MODES),
})
SET_HUMIDITY_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
@ -121,19 +86,19 @@ SET_SWING_MODE_SCHEMA = vol.Schema({
})
async def async_setup(hass, config):
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
"""Set up climate devices."""
component = hass.data[DOMAIN] = \
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config)
component.async_register_entity_service(
SERVICE_SET_AWAY_MODE, SET_AWAY_MODE_SCHEMA,
async_service_away_mode
SERVICE_SET_HVAC_MODE, SET_HVAC_MODE_SCHEMA,
'async_set_hvac_mode'
)
component.async_register_entity_service(
SERVICE_SET_HOLD_MODE, SET_HOLD_MODE_SCHEMA,
'async_set_hold_mode'
SERVICE_SET_PRESET_MODE, SET_PRESET_MODE_SCHEMA,
'async_set_preset_mode'
)
component.async_register_entity_service(
SERVICE_SET_AUX_HEAT, SET_AUX_HEAT_SCHEMA,
@ -151,32 +116,20 @@ async def async_setup(hass, config):
SERVICE_SET_FAN_MODE, SET_FAN_MODE_SCHEMA,
'async_set_fan_mode'
)
component.async_register_entity_service(
SERVICE_SET_OPERATION_MODE, SET_OPERATION_MODE_SCHEMA,
'async_set_operation_mode'
)
component.async_register_entity_service(
SERVICE_SET_SWING_MODE, SET_SWING_MODE_SCHEMA,
'async_set_swing_mode'
)
component.async_register_entity_service(
SERVICE_TURN_OFF, ON_OFF_SERVICE_SCHEMA,
'async_turn_off'
)
component.async_register_entity_service(
SERVICE_TURN_ON, ON_OFF_SERVICE_SCHEMA,
'async_turn_on'
)
return True
async def async_setup_entry(hass, entry):
async def async_setup_entry(hass: HomeAssistantType, entry):
"""Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
async def async_unload_entry(hass, entry):
async def async_unload_entry(hass: HomeAssistantType, entry):
"""Unload a config entry."""
return await hass.data[DOMAIN].async_unload_entry(entry)
@ -185,27 +138,23 @@ class ClimateDevice(Entity):
"""Representation of a climate device."""
@property
def state(self):
def state(self) -> str:
"""Return the current state."""
if self.is_on is False:
return STATE_OFF
if self.current_operation:
return self.current_operation
if self.is_on:
return STATE_ON
return None
return self.hvac_mode
@property
def precision(self):
def precision(self) -> float:
"""Return the precision of the system."""
if self.hass.config.units.temperature_unit == TEMP_CELSIUS:
return PRECISION_TENTHS
return PRECISION_WHOLE
@property
def state_attributes(self):
def state_attributes(self) -> Dict[str, Any]:
"""Return the optional state attributes."""
supported_features = self.supported_features
data = {
ATTR_HVAC_MODES: self.hvac_modes,
ATTR_CURRENT_TEMPERATURE: show_temp(
self.hass, self.current_temperature, self.temperature_unit,
self.precision),
@ -220,16 +169,13 @@ class ClimateDevice(Entity):
self.precision),
}
supported_features = self.supported_features
if self.target_temperature_step is not None:
if self.target_temperature_step:
data[ATTR_TARGET_TEMP_STEP] = self.target_temperature_step
if supported_features & SUPPORT_TARGET_TEMPERATURE_HIGH:
if supported_features & SUPPORT_TARGET_TEMPERATURE_RANGE:
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.hass, self.target_temperature_high, self.temperature_unit,
self.precision)
if supported_features & SUPPORT_TARGET_TEMPERATURE_LOW:
data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit,
self.precision)
@ -239,136 +185,160 @@ class ClimateDevice(Entity):
if supported_features & SUPPORT_TARGET_HUMIDITY:
data[ATTR_HUMIDITY] = self.target_humidity
if supported_features & SUPPORT_TARGET_HUMIDITY_LOW:
data[ATTR_MIN_HUMIDITY] = self.min_humidity
if supported_features & SUPPORT_TARGET_HUMIDITY_HIGH:
data[ATTR_MAX_HUMIDITY] = self.max_humidity
data[ATTR_MIN_HUMIDITY] = self.min_humidity
data[ATTR_MAX_HUMIDITY] = self.max_humidity
if supported_features & SUPPORT_FAN_MODE:
data[ATTR_FAN_MODE] = self.current_fan_mode
if self.fan_list:
data[ATTR_FAN_LIST] = self.fan_list
data[ATTR_FAN_MODE] = self.fan_mode
data[ATTR_FAN_MODES] = self.fan_modes
if supported_features & SUPPORT_OPERATION_MODE:
data[ATTR_OPERATION_MODE] = self.current_operation
if self.operation_list:
data[ATTR_OPERATION_LIST] = self.operation_list
if self.hvac_action:
data[ATTR_HVAC_ACTIONS] = self.hvac_action
if supported_features & SUPPORT_HOLD_MODE:
data[ATTR_HOLD_MODE] = self.current_hold_mode
if supported_features & SUPPORT_PRESET_MODE:
data[ATTR_PRESET_MODE] = self.preset_mode
data[ATTR_PRESET_MODES] = self.preset_modes
if supported_features & SUPPORT_SWING_MODE:
data[ATTR_SWING_MODE] = self.current_swing_mode
if self.swing_list:
data[ATTR_SWING_LIST] = self.swing_list
if supported_features & SUPPORT_AWAY_MODE:
is_away = self.is_away_mode_on
data[ATTR_AWAY_MODE] = STATE_ON if is_away else STATE_OFF
data[ATTR_SWING_MODE] = self.swing_mode
data[ATTR_SWING_MODES] = self.swing_modes
if supported_features & SUPPORT_AUX_HEAT:
is_aux_heat = self.is_aux_heat_on
data[ATTR_AUX_HEAT] = STATE_ON if is_aux_heat else STATE_OFF
data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF
return data
@property
def temperature_unit(self):
def temperature_unit(self) -> str:
"""Return the unit of measurement used by the platform."""
raise NotImplementedError
raise NotImplementedError()
@property
def current_humidity(self):
def current_humidity(self) -> Optional[int]:
"""Return the current humidity."""
return None
@property
def target_humidity(self):
def target_humidity(self) -> Optional[int]:
"""Return the humidity we try to reach."""
return None
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
raise NotImplementedError()
@property
def hvac_modes(self) -> List[str]:
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
raise NotImplementedError()
@property
def hvac_action(self) -> Optional[str]:
"""Return the current running hvac operation if supported.
Need to be one of CURRENT_HVAC_*.
"""
return None
@property
def operation_list(self):
"""Return the list of available operation modes."""
return None
@property
def current_temperature(self):
def current_temperature(self) -> Optional[float]:
"""Return the current temperature."""
return None
@property
def target_temperature(self):
def target_temperature(self) -> Optional[float]:
"""Return the temperature we try to reach."""
return None
@property
def target_temperature_step(self):
def target_temperature_step(self) -> Optional[float]:
"""Return the supported step of target temperature."""
return None
@property
def target_temperature_high(self):
"""Return the highbound target temperature we try to reach."""
return None
def target_temperature_high(self) -> Optional[float]:
"""Return the highbound target temperature we try to reach.
Requires SUPPORT_TARGET_TEMPERATURE_RANGE.
"""
raise NotImplementedError
@property
def target_temperature_low(self):
"""Return the lowbound target temperature we try to reach."""
return None
def target_temperature_low(self) -> Optional[float]:
"""Return the lowbound target temperature we try to reach.
Requires SUPPORT_TARGET_TEMPERATURE_RANGE.
"""
raise NotImplementedError
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return None
def preset_mode(self) -> Optional[str]:
"""Return the current preset mode, e.g., home, away, temp.
Requires SUPPORT_PRESET_MODE.
"""
raise NotImplementedError
@property
def current_hold_mode(self):
"""Return the current hold mode, e.g., home, away, temp."""
return None
def preset_modes(self) -> Optional[List[str]]:
"""Return a list of available preset modes.
Requires SUPPORT_PRESET_MODE.
"""
raise NotImplementedError
@property
def is_on(self):
"""Return true if on."""
return None
def is_aux_heat(self) -> Optional[str]:
"""Return true if aux heater.
Requires SUPPORT_AUX_HEAT.
"""
raise NotImplementedError
@property
def is_aux_heat_on(self):
"""Return true if aux heater."""
return None
def fan_mode(self) -> Optional[str]:
"""Return the fan setting.
Requires SUPPORT_FAN_MODE.
"""
raise NotImplementedError
@property
def current_fan_mode(self):
"""Return the fan setting."""
return None
def fan_modes(self) -> Optional[List[str]]:
"""Return the list of available fan modes.
Requires SUPPORT_FAN_MODE.
"""
raise NotImplementedError
@property
def fan_list(self):
"""Return the list of available fan modes."""
return None
def swing_mode(self) -> Optional[str]:
"""Return the swing setting.
Requires SUPPORT_SWING_MODE.
"""
raise NotImplementedError
@property
def current_swing_mode(self):
"""Return the fan setting."""
return None
def swing_modes(self) -> Optional[List[str]]:
"""Return the list of available swing modes.
@property
def swing_list(self):
"""Return the list of available swing modes."""
return None
Requires SUPPORT_SWING_MODE.
"""
raise NotImplementedError
def set_temperature(self, **kwargs):
def set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
raise NotImplementedError()
def async_set_temperature(self, **kwargs):
def async_set_temperature(self, **kwargs) -> Awaitable[None]:
"""Set new target temperature.
This method must be run in the event loop and returns a coroutine.
@ -376,164 +346,114 @@ class ClimateDevice(Entity):
return self.hass.async_add_job(
ft.partial(self.set_temperature, **kwargs))
def set_humidity(self, humidity):
def set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
raise NotImplementedError()
def async_set_humidity(self, humidity):
def async_set_humidity(self, humidity: int) -> Awaitable[None]:
"""Set new target humidity.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_humidity, humidity)
def set_fan_mode(self, fan_mode):
def set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
raise NotImplementedError()
def async_set_fan_mode(self, fan_mode):
def async_set_fan_mode(self, fan_mode: str) -> Awaitable[None]:
"""Set new target fan mode.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_fan_mode, fan_mode)
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
def set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
raise NotImplementedError()
def async_set_operation_mode(self, operation_mode):
"""Set new target operation mode.
def async_set_hvac_mode(self, hvac_mode: str) -> Awaitable[None]:
"""Set new target hvac mode.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_operation_mode, operation_mode)
return self.hass.async_add_job(self.set_hvac_mode, hvac_mode)
def set_swing_mode(self, swing_mode):
def set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation."""
raise NotImplementedError()
def async_set_swing_mode(self, swing_mode):
def async_set_swing_mode(self, swing_mode: str) -> Awaitable[None]:
"""Set new target swing operation.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_swing_mode, swing_mode)
def turn_away_mode_on(self):
"""Turn away mode on."""
def set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
raise NotImplementedError()
def async_turn_away_mode_on(self):
"""Turn away mode on.
def async_set_preset_mode(self, preset_mode: str) -> Awaitable[None]:
"""Set new preset mode.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_away_mode_on)
return self.hass.async_add_job(self.set_preset_mode, preset_mode)
def turn_away_mode_off(self):
"""Turn away mode off."""
raise NotImplementedError()
def async_turn_away_mode_off(self):
"""Turn away mode off.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_away_mode_off)
def set_hold_mode(self, hold_mode):
"""Set new target hold mode."""
raise NotImplementedError()
def async_set_hold_mode(self, hold_mode):
"""Set new target hold mode.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_hold_mode, hold_mode)
def turn_aux_heat_on(self):
def turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on."""
raise NotImplementedError()
def async_turn_aux_heat_on(self):
def async_turn_aux_heat_on(self) -> Awaitable[None]:
"""Turn auxiliary heater on.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_aux_heat_on)
def turn_aux_heat_off(self):
def turn_aux_heat_off(self) -> None:
"""Turn auxiliary heater off."""
raise NotImplementedError()
def async_turn_aux_heat_off(self):
def async_turn_aux_heat_off(self) -> Awaitable[None]:
"""Turn auxiliary heater off.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_aux_heat_off)
def turn_on(self):
"""Turn device on."""
raise NotImplementedError()
def async_turn_on(self):
"""Turn device on.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_on)
def turn_off(self):
"""Turn device off."""
raise NotImplementedError()
def async_turn_off(self):
"""Turn device off.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_off)
@property
def supported_features(self):
def supported_features(self) -> int:
"""Return the list of supported features."""
raise NotImplementedError()
@property
def min_temp(self):
def min_temp(self) -> float:
"""Return the minimum temperature."""
return convert_temperature(DEFAULT_MIN_TEMP, TEMP_CELSIUS,
self.temperature_unit)
@property
def max_temp(self):
def max_temp(self) -> float:
"""Return the maximum temperature."""
return convert_temperature(DEFAULT_MAX_TEMP, TEMP_CELSIUS,
self.temperature_unit)
@property
def min_humidity(self):
def min_humidity(self) -> int:
"""Return the minimum humidity."""
return DEFAULT_MIN_HUMITIDY
return DEFAULT_MIN_HUMIDITY
@property
def max_humidity(self):
def max_humidity(self) -> int:
"""Return the maximum humidity."""
return DEFAULT_MAX_HUMIDITY
async def async_service_away_mode(entity, service):
"""Handle away mode service."""
if service.data[ATTR_AWAY_MODE]:
await entity.async_turn_away_mode_on()
else:
await entity.async_turn_away_mode_off()
async def async_service_aux_heat(entity, service):
async def async_service_aux_heat(
entity: ClimateDevice, service: ServiceDataType
) -> None:
"""Handle aux heat service."""
if service.data[ATTR_AUX_HEAT]:
await entity.async_turn_aux_heat_on()
@ -541,7 +461,9 @@ async def async_service_aux_heat(entity, service):
await entity.async_turn_aux_heat_off()
async def async_service_temperature_set(entity, service):
async def async_service_temperature_set(
entity: ClimateDevice, service: ServiceDataType
) -> None:
"""Handle set temperature service."""
hass = entity.hass
kwargs = {}

View File

@ -1,20 +1,103 @@
"""Provides the constants needed for component."""
# All activity disabled / Device is off/standby
HVAC_MODE_OFF = 'off'
# Heating
HVAC_MODE_HEAT = 'heat'
# Cooling
HVAC_MODE_COOL = 'cool'
# The device supports heating/cooling to a range
HVAC_MODE_HEAT_COOL = 'heat_cool'
# The temperature is set based on a schedule, learned behavior, AI or some
# other related mechanism. User is not able to adjust the temperature
HVAC_MODE_AUTO = 'auto'
# Device is in Dry/Humidity mode
HVAC_MODE_DRY = 'dry'
# Only the fan is on, not fan and another mode like cool
HVAC_MODE_FAN_ONLY = 'fan_only'
HVAC_MODES = [
HVAC_MODE_OFF,
HVAC_MODE_HEAT,
HVAC_MODE_COOL,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_AUTO,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
]
# Device is running an energy-saving mode
PRESET_ECO = 'eco'
# Device is in away mode
PRESET_AWAY = 'away'
# Device turn all valve full up
PRESET_BOOST = 'boost'
# Device is in comfort mode
PRESET_COMFORT = 'comfort'
# Device is in home mode
PRESET_HOME = 'home'
# Device is prepared for sleep
PRESET_SLEEP = 'sleep'
# Device is reacting to activity (e.g. movement sensors)
PRESET_ACTIVITY = 'activity'
# Possible fan state
FAN_ON = "on"
FAN_OFF = "off"
FAN_AUTO = "auto"
FAN_LOW = "low"
FAN_MEDIUM = "medium"
FAN_HIGH = "high"
FAN_MIDDLE = "middle"
FAN_FOCUS = "focus"
FAN_DIFFUSE = "diffuse"
# Possible swing state
SWING_OFF = "off"
SWING_BOTH = "both"
SWING_VERTICAL = "vertical"
SWING_HORIZONTAL = "horizontal"
# This are support current states of HVAC
CURRENT_HVAC_OFF = 'off'
CURRENT_HVAC_HEAT = 'heating'
CURRENT_HVAC_COOL = 'cooling'
CURRENT_HVAC_DRY = 'drying'
CURRENT_HVAC_IDLE = 'idle'
ATTR_AUX_HEAT = 'aux_heat'
ATTR_AWAY_MODE = 'away_mode'
ATTR_CURRENT_HUMIDITY = 'current_humidity'
ATTR_CURRENT_TEMPERATURE = 'current_temperature'
ATTR_FAN_LIST = 'fan_list'
ATTR_FAN_MODES = 'fan_modes'
ATTR_FAN_MODE = 'fan_mode'
ATTR_HOLD_MODE = 'hold_mode'
ATTR_PRESET_MODE = 'preset_mode'
ATTR_PRESET_MODES = 'preset_modes'
ATTR_HUMIDITY = 'humidity'
ATTR_MAX_HUMIDITY = 'max_humidity'
ATTR_MAX_TEMP = 'max_temp'
ATTR_MIN_HUMIDITY = 'min_humidity'
ATTR_MAX_TEMP = 'max_temp'
ATTR_MIN_TEMP = 'min_temp'
ATTR_OPERATION_LIST = 'operation_list'
ATTR_OPERATION_MODE = 'operation_mode'
ATTR_SWING_LIST = 'swing_list'
ATTR_HVAC_ACTIONS = 'hvac_action'
ATTR_HVAC_MODES = 'hvac_modes'
ATTR_HVAC_MODE = 'hvac_mode'
ATTR_SWING_MODES = 'swing_modes'
ATTR_SWING_MODE = 'swing_mode'
ATTR_TARGET_TEMP_HIGH = 'target_temp_high'
ATTR_TARGET_TEMP_LOW = 'target_temp_low'
@ -28,33 +111,17 @@ DEFAULT_MAX_HUMIDITY = 99
DOMAIN = 'climate'
SERVICE_SET_AUX_HEAT = 'set_aux_heat'
SERVICE_SET_AWAY_MODE = 'set_away_mode'
SERVICE_SET_FAN_MODE = 'set_fan_mode'
SERVICE_SET_HOLD_MODE = 'set_hold_mode'
SERVICE_SET_PRESET_MODE = 'set_preset_mode'
SERVICE_SET_HUMIDITY = 'set_humidity'
SERVICE_SET_OPERATION_MODE = 'set_operation_mode'
SERVICE_SET_HVAC_MODE = 'set_hvac_mode'
SERVICE_SET_SWING_MODE = 'set_swing_mode'
SERVICE_SET_TEMPERATURE = 'set_temperature'
STATE_HEAT = 'heat'
STATE_COOL = 'cool'
STATE_IDLE = 'idle'
STATE_AUTO = 'auto'
STATE_MANUAL = 'manual'
STATE_DRY = 'dry'
STATE_FAN_ONLY = 'fan_only'
STATE_ECO = 'eco'
SUPPORT_TARGET_TEMPERATURE = 1
SUPPORT_TARGET_TEMPERATURE_HIGH = 2
SUPPORT_TARGET_TEMPERATURE_LOW = 4
SUPPORT_TARGET_HUMIDITY = 8
SUPPORT_TARGET_HUMIDITY_HIGH = 16
SUPPORT_TARGET_HUMIDITY_LOW = 32
SUPPORT_FAN_MODE = 64
SUPPORT_OPERATION_MODE = 128
SUPPORT_HOLD_MODE = 256
SUPPORT_SWING_MODE = 512
SUPPORT_AWAY_MODE = 1024
SUPPORT_AUX_HEAT = 2048
SUPPORT_ON_OFF = 4096
SUPPORT_TARGET_TEMPERATURE_RANGE = 2
SUPPORT_TARGET_HUMIDITY = 4
SUPPORT_FAN_MODE = 8
SUPPORT_PRESET_MODE = 16
SUPPORT_SWING_MODE = 32
SUPPORT_AUX_HEAT = 64

View File

@ -2,27 +2,24 @@
import asyncio
from typing import Iterable, Optional
from homeassistant.const import (
ATTR_TEMPERATURE, SERVICE_TURN_OFF,
SERVICE_TURN_ON, STATE_OFF, STATE_ON)
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.core import Context, State
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.loader import bind_hass
from .const import (
ATTR_AUX_HEAT,
ATTR_AWAY_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
ATTR_HOLD_MODE,
ATTR_OPERATION_MODE,
ATTR_PRESET_MODE,
ATTR_HVAC_MODE,
ATTR_SWING_MODE,
ATTR_HUMIDITY,
SERVICE_SET_AWAY_MODE,
HVAC_MODES,
SERVICE_SET_AUX_HEAT,
SERVICE_SET_TEMPERATURE,
SERVICE_SET_HOLD_MODE,
SERVICE_SET_OPERATION_MODE,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_HVAC_MODE,
SERVICE_SET_SWING_MODE,
SERVICE_SET_HUMIDITY,
DOMAIN,
@ -33,9 +30,9 @@ async def _async_reproduce_states(hass: HomeAssistantType,
state: State,
context: Optional[Context] = None) -> None:
"""Reproduce component states."""
async def call_service(service: str, keys: Iterable):
async def call_service(service: str, keys: Iterable, data=None):
"""Call service with set of attributes given."""
data = {}
data = data or {}
data['entity_id'] = state.entity_id
for key in keys:
if key in state.attributes:
@ -45,17 +42,13 @@ async def _async_reproduce_states(hass: HomeAssistantType,
DOMAIN, service, data,
blocking=True, context=context)
if state.state == STATE_ON:
await call_service(SERVICE_TURN_ON, [])
elif state.state == STATE_OFF:
await call_service(SERVICE_TURN_OFF, [])
if state.state in HVAC_MODES:
await call_service(
SERVICE_SET_HVAC_MODE, [], {ATTR_HVAC_MODE: state.state})
if ATTR_AUX_HEAT in state.attributes:
await call_service(SERVICE_SET_AUX_HEAT, [ATTR_AUX_HEAT])
if ATTR_AWAY_MODE in state.attributes:
await call_service(SERVICE_SET_AWAY_MODE, [ATTR_AWAY_MODE])
if (ATTR_TEMPERATURE in state.attributes) or \
(ATTR_TARGET_TEMP_HIGH in state.attributes) or \
(ATTR_TARGET_TEMP_LOW in state.attributes):
@ -64,21 +57,14 @@ async def _async_reproduce_states(hass: HomeAssistantType,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW])
if ATTR_HOLD_MODE in state.attributes:
await call_service(SERVICE_SET_HOLD_MODE,
[ATTR_HOLD_MODE])
if ATTR_OPERATION_MODE in state.attributes:
await call_service(SERVICE_SET_OPERATION_MODE,
[ATTR_OPERATION_MODE])
if ATTR_PRESET_MODE in state.attributes:
await call_service(SERVICE_SET_PRESET_MODE, [ATTR_PRESET_MODE])
if ATTR_SWING_MODE in state.attributes:
await call_service(SERVICE_SET_SWING_MODE,
[ATTR_SWING_MODE])
await call_service(SERVICE_SET_SWING_MODE, [ATTR_SWING_MODE])
if ATTR_HUMIDITY in state.attributes:
await call_service(SERVICE_SET_HUMIDITY,
[ATTR_HUMIDITY])
await call_service(SERVICE_SET_HUMIDITY, [ATTR_HUMIDITY])
@bind_hass

View File

@ -9,23 +9,14 @@ set_aux_heat:
aux_heat:
description: New value of axillary heater.
example: true
set_away_mode:
description: Turn away mode on/off for climate device.
set_preset_mode:
description: Set preset mode for climate device.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
away_mode:
description: New value of away mode.
example: true
set_hold_mode:
description: Turn hold mode for climate device.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
hold_mode:
description: New value of hold mode
preset_mode:
description: New value of preset mode
example: 'away'
set_temperature:
description: Set target temperature of climate device.
@ -42,9 +33,9 @@ set_temperature:
target_temp_low:
description: New target low temperature for HVAC.
example: 20
operation_mode:
description: Operation mode to set temperature to. This defaults to current_operation mode if not set, or set incorrectly.
example: 'Heat'
hvac_mode:
description: HVAC operation mode to set temperature to.
example: 'heat'
set_humidity:
description: Set target humidity of climate device.
fields:
@ -63,15 +54,15 @@ set_fan_mode:
fan_mode:
description: New value of fan mode.
example: On Low
set_operation_mode:
description: Set operation mode for climate device.
set_hvac_mode:
description: Set HVAC operation mode for climate device.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.nest'
operation_mode:
hvac_mode:
description: New value of operation mode.
example: Heat
example: heat
set_swing_mode:
description: Set swing operation for climate device.
fields:
@ -81,20 +72,6 @@ set_swing_mode:
swing_mode:
description: New value of swing mode.
turn_on:
description: Turn climate device on.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
turn_off:
description: Turn climate device off.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
ecobee_set_fan_min_on_time:
description: Set the minimum fan on time.
fields:
@ -137,13 +114,3 @@ nuheat_resume_program:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
sensibo_assume_state:
description: Set Sensibo device to external state.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
state:
description: State to set.
example: 'idle'

View File

@ -6,27 +6,26 @@ import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY,
STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
HVAC_MODE_OFF, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE)
DEFAULT_PORT = 10102
AVAILABLE_MODES = [STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_DRY,
STATE_FAN_ONLY]
AVAILABLE_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL,
HVAC_MODE_DRY, HVAC_MODE_AUTO, HVAC_MODE_FAN_ONLY]
CM_TO_HA_STATE = {
'heat': STATE_HEAT,
'cool': STATE_COOL,
'auto': STATE_AUTO,
'dry': STATE_DRY,
'fan': STATE_FAN_ONLY,
'heat': HVAC_MODE_HEAT,
'cool': HVAC_MODE_COOL,
'auto': HVAC_MODE_AUTO,
'dry': HVAC_MODE_DRY,
'fan': HVAC_MODE_FAN_ONLY,
}
HA_STATE_TO_CM = {value: key for key, value in CM_TO_HA_STATE.items()}
@ -72,7 +71,8 @@ class CoolmasterClimate(ClimateDevice):
"""Initialize the climate device."""
self._device = device
self._uid = device.uid
self._operation_list = supported_modes
self._hvac_modes = supported_modes
self._hvac_mode = None
self._target_temperature = None
self._current_temperature = None
self._current_fan_mode = None
@ -89,7 +89,10 @@ class CoolmasterClimate(ClimateDevice):
self._on = status['is_on']
device_mode = status['mode']
self._current_operation = CM_TO_HA_STATE[device_mode]
if self._on:
self._hvac_mode = CM_TO_HA_STATE[device_mode]
else:
self._hvac_mode = HVAC_MODE_OFF
if status['unit'] == 'celsius':
self._unit = TEMP_CELSIUS
@ -127,27 +130,22 @@ class CoolmasterClimate(ClimateDevice):
return self._target_temperature
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
def hvac_mode(self):
"""Return hvac target hvac state."""
return self._hvac_mode
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return self._operation_list
return self._hvac_modes
@property
def is_on(self):
"""Return true if the device is on."""
return self._on
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self._current_fan_mode
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return FAN_MODES
@ -165,18 +163,13 @@ class CoolmasterClimate(ClimateDevice):
fan_mode)
self._device.set_fan_speed(fan_mode)
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set new operation mode."""
_LOGGER.debug("Setting operation mode of %s to %s", self.unique_id,
operation_mode)
self._device.set_mode(HA_STATE_TO_CM[operation_mode])
hvac_mode)
def turn_on(self):
"""Turn on."""
_LOGGER.debug("Turning %s on", self.unique_id)
self._device.turn_on()
def turn_off(self):
"""Turn off."""
_LOGGER.debug("Turning %s off", self.unique_id)
self._device.turn_off()
if hvac_mode == HVAC_MODE_OFF:
self._device.turn_off()
else:
self._device.set_mode(HA_STATE_TO_CM[hvac_mode])
self._device.turn_on()

View File

@ -5,14 +5,17 @@ import re
import voluptuous as vol
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
ATTR_AWAY_MODE, ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE,
ATTR_OPERATION_MODE, ATTR_SWING_MODE, STATE_AUTO, STATE_COOL, STATE_DRY,
STATE_FAN_ONLY, STATE_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, STATE_OFF, TEMP_CELSIUS)
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS)
from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE,
SUPPORT_SWING_MODE,
HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT_COOL,
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
PRESET_AWAY, PRESET_HOME,
ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE,
ATTR_HVAC_MODE, ATTR_SWING_MODE,
ATTR_PRESET_MODE)
import homeassistant.helpers.config_validation as cv
from . import DOMAIN as DAIKIN_DOMAIN
@ -27,26 +30,31 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
HA_STATE_TO_DAIKIN = {
STATE_FAN_ONLY: 'fan',
STATE_DRY: 'dry',
STATE_COOL: 'cool',
STATE_HEAT: 'hot',
STATE_AUTO: 'auto',
STATE_OFF: 'off',
HVAC_MODE_FAN_ONLY: 'fan',
HVAC_MODE_DRY: 'dry',
HVAC_MODE_COOL: 'cool',
HVAC_MODE_HEAT: 'hot',
HVAC_MODE_HEAT_COOL: 'auto',
HVAC_MODE_OFF: 'off',
}
DAIKIN_TO_HA_STATE = {
'fan': STATE_FAN_ONLY,
'dry': STATE_DRY,
'cool': STATE_COOL,
'hot': STATE_HEAT,
'auto': STATE_AUTO,
'off': STATE_OFF,
'fan': HVAC_MODE_FAN_ONLY,
'dry': HVAC_MODE_DRY,
'cool': HVAC_MODE_COOL,
'hot': HVAC_MODE_HEAT,
'auto': HVAC_MODE_HEAT_COOL,
'off': HVAC_MODE_OFF,
}
HA_PRESET_TO_DAIKIN = {
PRESET_AWAY: 'on',
PRESET_HOME: 'off'
}
HA_ATTR_TO_DAIKIN = {
ATTR_AWAY_MODE: 'en_hol',
ATTR_OPERATION_MODE: 'mode',
ATTR_PRESET_MODE: 'en_hol',
ATTR_HVAC_MODE: 'mode',
ATTR_FAN_MODE: 'f_rate',
ATTR_SWING_MODE: 'f_dir',
ATTR_INSIDE_TEMPERATURE: 'htemp',
@ -80,7 +88,7 @@ class DaikinClimate(ClimateDevice):
self._api = api
self._list = {
ATTR_OPERATION_MODE: list(HA_STATE_TO_DAIKIN),
ATTR_HVAC_MODE: list(HA_STATE_TO_DAIKIN),
ATTR_FAN_MODE: self._api.device.fan_rate,
ATTR_SWING_MODE: list(
map(
@ -90,12 +98,10 @@ class DaikinClimate(ClimateDevice):
),
}
self._supported_features = (SUPPORT_ON_OFF
| SUPPORT_OPERATION_MODE
| SUPPORT_TARGET_TEMPERATURE)
self._supported_features = SUPPORT_TARGET_TEMPERATURE
if self._api.device.support_away_mode:
self._supported_features |= SUPPORT_AWAY_MODE
self._supported_features |= SUPPORT_PRESET_MODE
if self._api.device.support_fan_rate:
self._supported_features |= SUPPORT_FAN_MODE
@ -127,7 +133,7 @@ class DaikinClimate(ClimateDevice):
value = self._api.device.represent(daikin_attr)[1].title()
elif key == ATTR_SWING_MODE:
value = self._api.device.represent(daikin_attr)[1].title()
elif key == ATTR_OPERATION_MODE:
elif key == ATTR_HVAC_MODE:
# Daikin can return also internal states auto-1 or auto-7
# and we need to translate them as AUTO
daikin_mode = re.sub(
@ -135,6 +141,10 @@ class DaikinClimate(ClimateDevice):
self._api.device.represent(daikin_attr)[1])
ha_mode = DAIKIN_TO_HA_STATE.get(daikin_mode)
value = ha_mode
elif key == ATTR_PRESET_MODE:
away = (self._api.device.represent(daikin_attr)[1]
!= HA_STATE_TO_DAIKIN[HVAC_MODE_OFF])
value = PRESET_AWAY if away else PRESET_HOME
if value is None:
_LOGGER.error("Invalid value requested for key %s", key)
@ -154,15 +164,17 @@ class DaikinClimate(ClimateDevice):
values = {}
for attr in [ATTR_TEMPERATURE, ATTR_FAN_MODE, ATTR_SWING_MODE,
ATTR_OPERATION_MODE]:
ATTR_HVAC_MODE, ATTR_PRESET_MODE]:
value = settings.get(attr)
if value is None:
continue
daikin_attr = HA_ATTR_TO_DAIKIN.get(attr)
if daikin_attr is not None:
if attr == ATTR_OPERATION_MODE:
if attr == ATTR_HVAC_MODE:
values[daikin_attr] = HA_STATE_TO_DAIKIN[value]
elif attr == ATTR_PRESET_MODE:
values[daikin_attr] = HA_PRESET_TO_DAIKIN[value]
elif value in self._list[attr]:
values[daikin_attr] = value.lower()
else:
@ -218,21 +230,21 @@ class DaikinClimate(ClimateDevice):
await self._set(kwargs)
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self.get(ATTR_OPERATION_MODE)
return self.get(ATTR_HVAC_MODE)
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return self._list.get(ATTR_OPERATION_MODE)
return self._list.get(ATTR_HVAC_MODE)
async def async_set_operation_mode(self, operation_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set HVAC mode."""
await self._set({ATTR_OPERATION_MODE: operation_mode})
await self._set({ATTR_HVAC_MODE: hvac_mode})
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self.get(ATTR_FAN_MODE)
@ -241,12 +253,12 @@ class DaikinClimate(ClimateDevice):
await self._set({ATTR_FAN_MODE: fan_mode})
@property
def fan_list(self):
def fan_modes(self):
"""List of available fan modes."""
return self._list.get(ATTR_FAN_MODE)
@property
def current_swing_mode(self):
def swing_mode(self):
"""Return the fan setting."""
return self.get(ATTR_SWING_MODE)
@ -255,10 +267,24 @@ class DaikinClimate(ClimateDevice):
await self._set({ATTR_SWING_MODE: swing_mode})
@property
def swing_list(self):
def swing_modes(self):
"""List of available swing modes."""
return self._list.get(ATTR_SWING_MODE)
@property
def preset_mode(self):
"""Return the fan setting."""
return self.get(ATTR_PRESET_MODE)
async def async_set_preset_mode(self, preset_mode):
"""Set new target temperature."""
await self._set({ATTR_PRESET_MODE: preset_mode})
@property
def preset_modes(self):
"""List of available swing modes."""
return list(HA_PRESET_TO_DAIKIN)
async def async_update(self):
"""Retrieve latest state."""
await self._api.async_update()
@ -267,36 +293,3 @@ class DaikinClimate(ClimateDevice):
def device_info(self):
"""Return a device description for device registry."""
return self._api.device_info
@property
def is_on(self):
"""Return true if on."""
return self._api.device.represent(
HA_ATTR_TO_DAIKIN[ATTR_OPERATION_MODE]
)[1] != HA_STATE_TO_DAIKIN[STATE_OFF]
async def async_turn_on(self):
"""Turn device on."""
await self._api.device.set({})
async def async_turn_off(self):
"""Turn device off."""
await self._api.device.set({
HA_ATTR_TO_DAIKIN[ATTR_OPERATION_MODE]:
HA_STATE_TO_DAIKIN[STATE_OFF]
})
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._api.device.represent(
HA_ATTR_TO_DAIKIN[ATTR_AWAY_MODE]
)[1] != HA_STATE_TO_DAIKIN[STATE_OFF]
async def async_turn_away_mode_on(self):
"""Turn away mode on."""
await self._api.device.set({HA_ATTR_TO_DAIKIN[ATTR_AWAY_MODE]: '1'})
async def async_turn_away_mode_off(self):
"""Turn away mode off."""
await self._api.device.set({HA_ATTR_TO_DAIKIN[ATTR_AWAY_MODE]: '0'})

View File

@ -3,7 +3,7 @@ from pydeconz.sensor import Thermostat
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
SUPPORT_ON_OFF, SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, TEMP_CELSIUS)
from homeassistant.core import callback
@ -13,6 +13,8 @@ from .const import ATTR_OFFSET, ATTR_VALVE, NEW_SENSOR
from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
@ -51,32 +53,28 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class DeconzThermostat(DeconzDevice, ClimateDevice):
"""Representation of a deCONZ thermostat."""
def __init__(self, device, gateway):
"""Set up thermostat device."""
super().__init__(device, gateway)
self._features = SUPPORT_ON_OFF
self._features |= SUPPORT_TARGET_TEMPERATURE
@property
def supported_features(self):
"""Return the list of supported features."""
return self._features
return SUPPORT_TARGET_TEMPERATURE
@property
def is_on(self):
"""Return true if on."""
return self._device.state_on
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
async def async_turn_on(self):
"""Turn on switch."""
data = {'mode': 'auto'}
await self._device.async_set_config(data)
Need to be one of HVAC_MODE_*.
"""
if self._device.on:
return HVAC_MODE_HEAT
return HVAC_MODE_OFF
async def async_turn_off(self):
"""Turn off switch."""
data = {'mode': 'off'}
await self._device.async_set_config(data)
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return SUPPORT_HVAC
@property
def current_temperature(self):
@ -97,6 +95,15 @@ class DeconzThermostat(DeconzDevice, ClimateDevice):
await self._device.async_set_config(data)
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if hvac_mode == HVAC_MODE_HEAT:
data = {'mode': 'auto'}
elif hvac_mode == HVAC_MODE_OFF:
data = {'mode': 'off'}
await self._device.async_set_config(data)
@property
def temperature_unit(self):
"""Return the unit of measurement."""

View File

@ -1,85 +1,138 @@
"""Demo platform that offers a fake climate device."""
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, SUPPORT_AUX_HEAT,
SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, SUPPORT_HOLD_MODE, SUPPORT_ON_OFF,
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW)
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL,
CURRENT_HVAC_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, HVAC_MODES, SUPPORT_AUX_HEAT,
SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE,
SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE, HVAC_MODE_AUTO)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
SUPPORT_FLAGS = SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH
SUPPORT_FLAGS = 0
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Demo climate devices."""
add_entities([
DemoClimate('HeatPump', 68, TEMP_FAHRENHEIT, None, None, 77,
None, None, None, None, 'heat', None, None,
None, True),
DemoClimate('Hvac', 21, TEMP_CELSIUS, True, None, 22, 'On High',
67, 54, 'Off', 'cool', False, None, None, None),
DemoClimate('Ecobee', None, TEMP_CELSIUS, None, 'home', 23, 'Auto Low',
None, None, 'Auto', 'auto', None, 24, 21, None)
DemoClimate(
name='HeatPump',
target_temperature=68,
unit_of_measurement=TEMP_FAHRENHEIT,
preset=None,
current_temperature=77,
fan_mode=None,
target_humidity=None,
current_humidity=None,
swing_mode=None,
hvac_mode=HVAC_MODE_HEAT,
hvac_action=CURRENT_HVAC_HEAT,
aux=None,
target_temp_high=None,
target_temp_low=None,
hvac_modes=[HVAC_MODE_HEAT, HVAC_MODE_OFF]
),
DemoClimate(
name='Hvac',
target_temperature=21,
unit_of_measurement=TEMP_CELSIUS,
preset=None,
current_temperature=22,
fan_mode='On High',
target_humidity=67,
current_humidity=54,
swing_mode='Off',
hvac_mode=HVAC_MODE_COOL,
hvac_action=CURRENT_HVAC_COOL,
aux=False,
target_temp_high=None,
target_temp_low=None,
hvac_modes=[mode for mode in HVAC_MODES
if mode != HVAC_MODE_HEAT_COOL]
),
DemoClimate(
name='Ecobee',
target_temperature=None,
unit_of_measurement=TEMP_CELSIUS,
preset='home',
preset_modes=['home', 'eco'],
current_temperature=23,
fan_mode='Auto Low',
target_humidity=None,
current_humidity=None,
swing_mode='Auto',
hvac_mode=HVAC_MODE_HEAT_COOL,
hvac_action=None,
aux=None,
target_temp_high=24,
target_temp_low=21,
hvac_modes=[HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL,
HVAC_MODE_HEAT])
])
class DemoClimate(ClimateDevice):
"""Representation of a demo climate device."""
def __init__(self, name, target_temperature, unit_of_measurement,
away, hold, current_temperature, current_fan_mode,
target_humidity, current_humidity, current_swing_mode,
current_operation, aux, target_temp_high, target_temp_low,
is_on):
def __init__(
self,
name,
target_temperature,
unit_of_measurement,
preset,
current_temperature,
fan_mode,
target_humidity,
current_humidity,
swing_mode,
hvac_mode,
hvac_action,
aux,
target_temp_high,
target_temp_low,
hvac_modes,
preset_modes=None,
):
"""Initialize the climate device."""
self._name = name
self._support_flags = SUPPORT_FLAGS
if target_temperature is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_TEMPERATURE
if away is not None:
self._support_flags = self._support_flags | SUPPORT_AWAY_MODE
if hold is not None:
self._support_flags = self._support_flags | SUPPORT_HOLD_MODE
if current_fan_mode is not None:
if preset is not None:
self._support_flags = self._support_flags | SUPPORT_PRESET_MODE
if fan_mode is not None:
self._support_flags = self._support_flags | SUPPORT_FAN_MODE
if target_humidity is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_HUMIDITY
if current_swing_mode is not None:
if swing_mode is not None:
self._support_flags = self._support_flags | SUPPORT_SWING_MODE
if current_operation is not None:
self._support_flags = self._support_flags | SUPPORT_OPERATION_MODE
if hvac_action is not None:
self._support_flags = self._support_flags
if aux is not None:
self._support_flags = self._support_flags | SUPPORT_AUX_HEAT
if target_temp_high is not None:
if (HVAC_MODE_HEAT_COOL in hvac_modes or
HVAC_MODE_AUTO in hvac_modes):
self._support_flags = \
self._support_flags | SUPPORT_TARGET_TEMPERATURE_HIGH
if target_temp_low is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_TEMPERATURE_LOW
if is_on is not None:
self._support_flags = self._support_flags | SUPPORT_ON_OFF
self._support_flags | SUPPORT_TARGET_TEMPERATURE_RANGE
self._target_temperature = target_temperature
self._target_humidity = target_humidity
self._unit_of_measurement = unit_of_measurement
self._away = away
self._hold = hold
self._preset = preset
self._preset_modes = preset_modes
self._current_temperature = current_temperature
self._current_humidity = current_humidity
self._current_fan_mode = current_fan_mode
self._current_operation = current_operation
self._current_fan_mode = fan_mode
self._hvac_action = hvac_action
self._hvac_mode = hvac_mode
self._aux = aux
self._current_swing_mode = current_swing_mode
self._fan_list = ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off']
self._operation_list = ['heat', 'cool', 'auto', 'off']
self._swing_list = ['Auto', '1', '2', '3', 'Off']
self._current_swing_mode = swing_mode
self._fan_modes = ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off']
self._hvac_modes = hvac_modes
self._swing_modes = ['Auto', '1', '2', '3', 'Off']
self._target_temperature_high = target_temp_high
self._target_temperature_low = target_temp_low
self._on = is_on
@property
def supported_features(self):
@ -132,46 +185,56 @@ class DemoClimate(ClimateDevice):
return self._target_humidity
@property
def current_operation(self):
def hvac_action(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
return self._hvac_action
@property
def operation_list(self):
def hvac_mode(self):
"""Return hvac target hvac state."""
return self._hvac_mode
@property
def hvac_modes(self):
"""Return the list of available operation modes."""
return self._operation_list
return self._hvac_modes
@property
def is_away_mode_on(self):
"""Return if away mode is on."""
return self._away
def preset_mode(self):
"""Return preset mode."""
return self._preset
@property
def current_hold_mode(self):
"""Return hold mode setting."""
return self._hold
def preset_modes(self):
"""Return preset modes."""
return self._preset_modes
@property
def is_aux_heat_on(self):
def is_aux_heat(self):
"""Return true if aux heat is on."""
return self._aux
@property
def is_on(self):
"""Return true if the device is on."""
return self._on
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self._current_fan_mode
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return self._fan_list
return self._fan_modes
def set_temperature(self, **kwargs):
@property
def swing_mode(self):
"""Return the swing setting."""
return self._current_swing_mode
@property
def swing_modes(self):
"""List of available swing modes."""
return self._swing_modes
async def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
@ -179,69 +242,39 @@ class DemoClimate(ClimateDevice):
kwargs.get(ATTR_TARGET_TEMP_LOW) is not None:
self._target_temperature_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
self._target_temperature_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
self.schedule_update_ha_state()
self.async_write_ha_state()
def set_humidity(self, humidity):
async def async_set_humidity(self, humidity):
"""Set new humidity level."""
self._target_humidity = humidity
self.schedule_update_ha_state()
self.async_write_ha_state()
def set_swing_mode(self, swing_mode):
async def async_set_swing_mode(self, swing_mode):
"""Set new swing mode."""
self._current_swing_mode = swing_mode
self.schedule_update_ha_state()
self.async_write_ha_state()
def set_fan_mode(self, fan_mode):
async def async_set_fan_mode(self, fan_mode):
"""Set new fan mode."""
self._current_fan_mode = fan_mode
self.schedule_update_ha_state()
self.async_write_ha_state()
def set_operation_mode(self, operation_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set new operation mode."""
self._current_operation = operation_mode
self.schedule_update_ha_state()
self._hvac_mode = hvac_mode
self.async_write_ha_state()
@property
def current_swing_mode(self):
"""Return the swing setting."""
return self._current_swing_mode
@property
def swing_list(self):
"""List of available swing modes."""
return self._swing_list
def turn_away_mode_on(self):
"""Turn away mode on."""
self._away = True
self.schedule_update_ha_state()
def turn_away_mode_off(self):
"""Turn away mode off."""
self._away = False
self.schedule_update_ha_state()
def set_hold_mode(self, hold_mode):
"""Update hold_mode on."""
self._hold = hold_mode
self.schedule_update_ha_state()
async def async_set_preset_mode(self, preset_mode):
"""Update preset_mode on."""
self._preset = preset_mode
self.async_write_ha_state()
def turn_aux_heat_on(self):
"""Turn auxiliary heater on."""
self._aux = True
self.schedule_update_ha_state()
self.async_write_ha_state()
def turn_aux_heat_off(self):
"""Turn auxiliary heater off."""
self._aux = False
self.schedule_update_ha_state()
def turn_on(self):
"""Turn on."""
self._on = True
self.schedule_update_ha_state()
def turn_off(self):
"""Turn off."""
self._on = False
self.schedule_update_ha_state()
self.async_write_ha_state()

View File

@ -1,22 +1,24 @@
"""Support for Dyson Pure Hot+Cool link fan."""
import logging
from libpurecool.const import HeatMode, HeatState, FocusMode, HeatTarget
from libpurecool.dyson_pure_state import DysonPureHotCoolState
from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, HVAC_MODE_COOL,
HVAC_MODE_HEAT, SUPPORT_FAN_MODE, FAN_FOCUS,
FAN_DIFFUSE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import DYSON_DEVICES
_LOGGER = logging.getLogger(__name__)
STATE_DIFFUSE = "Diffuse Mode"
STATE_FOCUS = "Focus Mode"
FAN_LIST = [STATE_FOCUS, STATE_DIFFUSE]
OPERATION_LIST = [STATE_HEAT, STATE_COOL]
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
| SUPPORT_OPERATION_MODE)
SUPPORT_FAN = [FAN_FOCUS, FAN_DIFFUSE]
SUPPORT_HVAG = [HVAC_MODE_COOL, HVAC_MODE_HEAT]
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -24,7 +26,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None:
return
from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink
# Get Dyson Devices from parent component.
add_devices(
[DysonPureHotCoolLinkDevice(device)
@ -43,17 +44,17 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.async_add_job(self._device.add_message_listener,
self.on_message)
self.hass.async_add_job(
self._device.add_message_listener, self.on_message)
def on_message(self, message):
"""Call when new messages received from the climate."""
from libpurecool.dyson_pure_state import DysonPureHotCoolState
if not isinstance(message, DysonPureHotCoolState):
return
if isinstance(message, DysonPureHotCoolState):
_LOGGER.debug("Message received for climate device %s : %s",
self.name, message)
self.schedule_update_ha_state()
_LOGGER.debug(
"Message received for climate device %s : %s", self.name, message)
self.schedule_update_ha_state()
@property
def should_poll(self):
@ -101,32 +102,46 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
return None
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
from libpurecool.const import HeatMode, HeatState
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if self._device.state.heat_mode == HeatMode.HEAT_ON.value:
return HVAC_MODE_HEAT
return HVAC_MODE_COOL
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return SUPPORT_HVAG
@property
def hvac_action(self):
"""Return the current running hvac operation if supported.
Need to be one of CURRENT_HVAC_*.
"""
if self._device.state.heat_mode == HeatMode.HEAT_ON.value:
if self._device.state.heat_state == HeatState.HEAT_STATE_ON.value:
return STATE_HEAT
return STATE_IDLE
return STATE_COOL
return CURRENT_HVAC_HEAT
return CURRENT_HVAC_IDLE
return CURRENT_HVAC_COOL
@property
def operation_list(self):
"""Return the list of available operation modes."""
return OPERATION_LIST
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
from libpurecool.const import FocusMode
if self._device.state.focus_mode == FocusMode.FOCUS_ON.value:
return STATE_FOCUS
return STATE_DIFFUSE
return FAN_FOCUS
return FAN_DIFFUSE
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return FAN_LIST
return SUPPORT_FAN
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@ -138,7 +153,6 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
# Limit the target temperature into acceptable range.
target_temp = min(self.max_temp, target_temp)
target_temp = max(self.min_temp, target_temp)
from libpurecool.const import HeatTarget, HeatMode
self._device.set_configuration(
heat_target=HeatTarget.celsius(target_temp),
heat_mode=HeatMode.HEAT_ON)
@ -146,19 +160,17 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
def set_fan_mode(self, fan_mode):
"""Set new fan mode."""
_LOGGER.debug("Set %s focus mode %s", self.name, fan_mode)
from libpurecool.const import FocusMode
if fan_mode == STATE_FOCUS:
if fan_mode == FAN_FOCUS:
self._device.set_configuration(focus_mode=FocusMode.FOCUS_ON)
elif fan_mode == STATE_DIFFUSE:
elif fan_mode == FAN_DIFFUSE:
self._device.set_configuration(focus_mode=FocusMode.FOCUS_OFF)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
_LOGGER.debug("Set %s heat mode %s", self.name, operation_mode)
from libpurecool.const import HeatMode
if operation_mode == STATE_HEAT:
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
_LOGGER.debug("Set %s heat mode %s", self.name, hvac_mode)
if hvac_mode == HVAC_MODE_HEAT:
self._device.set_configuration(heat_mode=HeatMode.HEAT_ON)
elif operation_mode == STATE_COOL:
elif hvac_mode == HVAC_MODE_COOL:
self._device.set_configuration(heat_mode=HeatMode.HEAT_OFF)
@property

View File

@ -1,19 +1,20 @@
"""Support for Ecobee Thermostats."""
import logging
from typing import Optional
import voluptuous as vol
from homeassistant.components import ecobee
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
DOMAIN, STATE_COOL, STATE_HEAT, STATE_AUTO, STATE_IDLE,
DOMAIN, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE_LOW)
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_RANGE, SUPPORT_FAN_MODE,
PRESET_AWAY, FAN_AUTO, FAN_ON, CURRENT_HVAC_OFF, CURRENT_HVAC_HEAT,
CURRENT_HVAC_COOL
)
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
ATTR_ENTITY_ID, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
_CONFIGURING = {}
@ -23,10 +24,33 @@ ATTR_FAN_MIN_ON_TIME = 'fan_min_on_time'
ATTR_RESUME_ALL = 'resume_all'
DEFAULT_RESUME_ALL = False
TEMPERATURE_HOLD = 'temp'
VACATION_HOLD = 'vacation'
PRESET_TEMPERATURE = 'temp'
PRESET_VACATION = 'vacation'
PRESET_AUX_HEAT_ONLY = 'aux_heat_only'
PRESET_HOLD_NEXT_TRANSITION = 'next_transition'
PRESET_HOLD_INDEFINITE = 'indefinite'
AWAY_MODE = 'awayMode'
ECOBEE_HVAC_TO_HASS = {
'auxHeatOnly': HVAC_MODE_HEAT,
'heat': HVAC_MODE_HEAT,
'cool': HVAC_MODE_COOL,
'off': HVAC_MODE_OFF,
'auto': HVAC_MODE_AUTO,
}
PRESET_TO_ECOBEE_HOLD = {
PRESET_HOLD_NEXT_TRANSITION: 'nextTransition',
PRESET_HOLD_INDEFINITE: 'indefinite',
}
PRESET_MODES = [
PRESET_AWAY,
PRESET_TEMPERATURE,
PRESET_HOLD_NEXT_TRANSITION,
PRESET_HOLD_INDEFINITE
]
SERVICE_SET_FAN_MIN_ON_TIME = 'ecobee_set_fan_min_on_time'
SERVICE_RESUME_PROGRAM = 'ecobee_resume_program'
@ -40,11 +64,9 @@ RESUME_PROGRAM_SCHEMA = vol.Schema({
vol.Optional(ATTR_RESUME_ALL, default=DEFAULT_RESUME_ALL): cv.boolean,
})
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE |
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH |
SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_FAN_MODE)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_RANGE |
SUPPORT_FAN_MODE)
def setup_platform(hass, config, add_entities, discovery_info=None):
@ -114,9 +136,10 @@ class Thermostat(ClimateDevice):
self.hold_temp = hold_temp
self.vacation = None
self._climate_list = self.climate_list
self._operation_list = ['auto', 'auxHeatOnly', 'cool',
'heat', 'off']
self._fan_list = ['auto', 'on']
self._operation_list = [
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF
]
self._fan_modes = [FAN_AUTO, FAN_ON]
self.update_without_throttle = False
def update(self):
@ -143,6 +166,9 @@ class Thermostat(ClimateDevice):
@property
def temperature_unit(self):
"""Return the unit of measurement."""
if self.thermostat['settings']['useCelsius']:
return TEMP_CELSIUS
return TEMP_FAHRENHEIT
@property
@ -153,25 +179,25 @@ class Thermostat(ClimateDevice):
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
if self.hvac_mode == HVAC_MODE_AUTO:
return self.thermostat['runtime']['desiredHeat'] / 10.0
return None
@property
def target_temperature_high(self):
"""Return the upper bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
if self.hvac_mode == HVAC_MODE_AUTO:
return self.thermostat['runtime']['desiredCool'] / 10.0
return None
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.current_operation == STATE_AUTO:
if self.hvac_mode == HVAC_MODE_AUTO:
return None
if self.current_operation == STATE_HEAT:
if self.hvac_mode == HVAC_MODE_HEAT:
return self.thermostat['runtime']['desiredHeat'] / 10.0
if self.current_operation == STATE_COOL:
if self.hvac_mode == HVAC_MODE_COOL:
return self.thermostat['runtime']['desiredCool'] / 10.0
return None
@ -180,70 +206,63 @@ class Thermostat(ClimateDevice):
"""Return the current fan status."""
if 'fan' in self.thermostat['equipmentStatus']:
return STATE_ON
return STATE_OFF
return HVAC_MODE_OFF
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self.thermostat['runtime']['desiredFanMode']
@property
def current_hold_mode(self):
"""Return current hold mode."""
mode = self._current_hold_mode
return None if mode == AWAY_MODE else mode
@property
def fan_list(self):
def fan_modes(self):
"""Return the available fan modes."""
return self._fan_list
return self._fan_modes
@property
def _current_hold_mode(self):
def preset_mode(self):
"""Return current preset mode."""
events = self.thermostat['events']
for event in events:
if event['running']:
if event['type'] == 'hold':
if event['holdClimateRef'] == 'away':
if int(event['endDate'][0:4]) - \
int(event['startDate'][0:4]) <= 1:
# A temporary hold from away climate is a hold
return 'away'
# A permanent hold from away climate
return AWAY_MODE
if event['holdClimateRef'] != "":
# Any other hold based on climate
return event['holdClimateRef']
# Any hold not based on a climate is a temp hold
return TEMPERATURE_HOLD
if event['type'].startswith('auto'):
# All auto modes are treated as holds
return event['type'][4:].lower()
if event['type'] == 'vacation':
self.vacation = event['name']
return VACATION_HOLD
if not event['running']:
continue
if event['type'] == 'hold':
if event['holdClimateRef'] == 'away':
if int(event['endDate'][0:4]) - \
int(event['startDate'][0:4]) <= 1:
# A temporary hold from away climate is a hold
return PRESET_AWAY
# A permanent hold from away climate
return PRESET_AWAY
if event['holdClimateRef'] != "":
# Any other hold based on climate
return event['holdClimateRef']
# Any hold not based on a climate is a temp hold
return PRESET_TEMPERATURE
if event['type'].startswith('auto'):
# All auto modes are treated as holds
return event['type'][4:].lower()
if event['type'] == 'vacation':
self.vacation = event['name']
return PRESET_VACATION
if self.is_aux_heat:
return PRESET_AUX_HEAT_ONLY
return None
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation."""
if self.operation_mode == 'auxHeatOnly' or \
self.operation_mode == 'heatPump':
return STATE_HEAT
return self.operation_mode
return ECOBEE_HVAC_TO_HASS[self.thermostat['settings']['hvacMode']]
@property
def operation_list(self):
def hvac_modes(self):
"""Return the operation modes list."""
return self._operation_list
@property
def operation_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self.thermostat['settings']['hvacMode']
@property
def mode(self):
def climate_mode(self):
"""Return current mode, as the user-visible name."""
cur = self.thermostat['program']['currentClimateRef']
climates = self.thermostat['program']['climates']
@ -251,80 +270,76 @@ class Thermostat(ClimateDevice):
return current[0]['name']
@property
def fan_min_on_time(self):
"""Return current fan minimum on time."""
return self.thermostat['settings']['fanMinOnTime']
def current_humidity(self) -> Optional[int]:
"""Return the current humidity."""
return self.thermostat['runtime']['actualHumidity']
@property
def hvac_action(self):
"""Return current HVAC action."""
status = self.thermostat['equipmentStatus']
operation = None
if status == '':
operation = CURRENT_HVAC_OFF
elif 'Cool' in status:
operation = CURRENT_HVAC_COOL
elif 'auxHeat' in status or 'heatPump' in status:
operation = CURRENT_HVAC_HEAT
return operation
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
# Move these to Thermostat Device and make them global
status = self.thermostat['equipmentStatus']
operation = None
if status == '':
operation = STATE_IDLE
elif 'Cool' in status:
operation = STATE_COOL
elif 'auxHeat' in status:
operation = STATE_HEAT
elif 'heatPump' in status:
operation = STATE_HEAT
else:
operation = status
return {
"actual_humidity": self.thermostat['runtime']['actualHumidity'],
"fan": self.fan,
"climate_mode": self.mode,
"operation": operation,
"climate_mode": self.climate_mode,
"equipment_running": status,
"climate_list": self.climate_list,
"fan_min_on_time": self.fan_min_on_time
"fan_min_on_time": self.thermostat['settings']['fanMinOnTime']
}
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._current_hold_mode == AWAY_MODE
@property
def is_aux_heat_on(self):
def is_aux_heat(self):
"""Return true if aux heater."""
return 'auxHeat' in self.thermostat['equipmentStatus']
def turn_away_mode_on(self):
"""Turn away mode on by setting it on away hold indefinitely."""
if self._current_hold_mode != AWAY_MODE:
def set_preset(self, preset):
"""Activate a preset."""
if preset == self.preset_mode:
return
self.update_without_throttle = True
# If we are currently in vacation mode, cancel it.
if self.preset_mode == PRESET_VACATION:
self.data.ecobee.delete_vacation(
self.thermostat_index, self.vacation)
if preset == PRESET_AWAY:
self.data.ecobee.set_climate_hold(self.thermostat_index, 'away',
'indefinite')
self.update_without_throttle = True
def turn_away_mode_off(self):
"""Turn away off."""
if self._current_hold_mode == AWAY_MODE:
elif preset == PRESET_TEMPERATURE:
self.set_temp_hold(self.current_temperature)
elif preset in (PRESET_HOLD_NEXT_TRANSITION, PRESET_HOLD_INDEFINITE):
self.data.ecobee.set_climate_hold(
self.thermostat_index, PRESET_TO_ECOBEE_HOLD[preset],
self.hold_preference())
elif preset is None:
self.data.ecobee.resume_program(self.thermostat_index)
self.update_without_throttle = True
def set_hold_mode(self, hold_mode):
"""Set hold mode (away, home, temp, sleep, etc.)."""
hold = self.current_hold_mode
if hold == hold_mode:
# no change, so no action required
return
if hold_mode == 'None' or hold_mode is None:
if hold == VACATION_HOLD:
self.data.ecobee.delete_vacation(
self.thermostat_index, self.vacation)
else:
self.data.ecobee.resume_program(self.thermostat_index)
else:
if hold_mode == TEMPERATURE_HOLD:
self.set_temp_hold(self.current_temperature)
else:
self.data.ecobee.set_climate_hold(
self.thermostat_index, hold_mode, self.hold_preference())
self.update_without_throttle = True
_LOGGER.warning("Received invalid preset: %s", preset)
@property
def preset_modes(self):
"""Return available preset modes."""
return PRESET_MODES
def set_auto_temp_hold(self, heat_temp, cool_temp):
"""Set temperature hold in auto mode."""
@ -352,7 +367,8 @@ class Thermostat(ClimateDevice):
def set_fan_mode(self, fan_mode):
"""Set the fan mode. Valid values are "on" or "auto"."""
if (fan_mode.lower() != STATE_ON) and (fan_mode.lower() != STATE_AUTO):
if fan_mode.lower() != STATE_ON and \
fan_mode.lower() != HVAC_MODE_AUTO:
error = "Invalid fan_mode value: Valid values are 'on' or 'auto'"
_LOGGER.error(error)
return
@ -376,8 +392,8 @@ class Thermostat(ClimateDevice):
heatCoolMinDelta property.
https://www.ecobee.com/home/developer/api/examples/ex5.shtml
"""
if self.current_operation == STATE_HEAT or self.current_operation == \
STATE_COOL:
if self.hvac_mode == HVAC_MODE_HEAT or \
self.hvac_mode == HVAC_MODE_COOL:
heat_temp = temp
cool_temp = temp
else:
@ -392,7 +408,7 @@ class Thermostat(ClimateDevice):
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
temp = kwargs.get(ATTR_TEMPERATURE)
if self.current_operation == STATE_AUTO and \
if self.hvac_mode == HVAC_MODE_AUTO and \
(low_temp is not None or high_temp is not None):
self.set_auto_temp_hold(low_temp, high_temp)
elif temp is not None:
@ -405,9 +421,14 @@ class Thermostat(ClimateDevice):
"""Set the humidity level."""
self.data.ecobee.set_humidity(self.thermostat_index, humidity)
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
self.data.ecobee.set_hvac_mode(self.thermostat_index, operation_mode)
ecobee_value = next((k for k, v in ECOBEE_HVAC_TO_HASS.items()
if v == hvac_mode), None)
if ecobee_value is None:
_LOGGER.error("Invalid mode for set_hvac_mode: %s", hvac_mode)
return
self.data.ecobee.set_hvac_mode(self.thermostat_index, ecobee_value)
self.update_without_throttle = True
def set_fan_min_on_time(self, fan_min_on_time):

View File

@ -1,15 +1,20 @@
"""Support for control of Elk-M1 connected thermostats."""
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, STATE_AUTO, STATE_COOL,
STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW)
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_AUX_HEAT,
SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE_RANGE)
from homeassistant.const import PRECISION_WHOLE, STATE_ON
from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities
SUPPORT_HVAC = [HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_AUTO,
HVAC_MODE_FAN_ONLY]
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Create the Elk-M1 thermostat platform."""
@ -32,9 +37,8 @@ class ElkThermostat(ElkEntity, ClimateDevice):
@property
def supported_features(self):
"""Return the list of supported features."""
return (SUPPORT_OPERATION_MODE | SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT
| SUPPORT_TARGET_TEMPERATURE_HIGH
| SUPPORT_TARGET_TEMPERATURE_LOW)
return (SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT
| SUPPORT_TARGET_TEMPERATURE_RANGE)
@property
def temperature_unit(self):
@ -78,14 +82,14 @@ class ElkThermostat(ElkEntity, ClimateDevice):
return self._element.humidity
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self._state
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return [STATE_IDLE, STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_FAN_ONLY]
return SUPPORT_HVAC
@property
def precision(self):
@ -93,7 +97,7 @@ class ElkThermostat(ElkEntity, ClimateDevice):
return PRECISION_WHOLE
@property
def is_aux_heat_on(self):
def is_aux_heat(self):
"""Return if aux heater is on."""
from elkm1_lib.const import ThermostatMode
return self._element.mode == ThermostatMode.EMERGENCY_HEAT.value
@ -109,11 +113,11 @@ class ElkThermostat(ElkEntity, ClimateDevice):
return 99
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
from elkm1_lib.const import ThermostatFan
if self._element.fan == ThermostatFan.AUTO.value:
return STATE_AUTO
return HVAC_MODE_AUTO
if self._element.fan == ThermostatFan.ON.value:
return STATE_ON
return None
@ -125,17 +129,19 @@ class ElkThermostat(ElkEntity, ClimateDevice):
if fan is not None:
self._element.set(ThermostatSetting.FAN.value, fan)
async def async_set_operation_mode(self, operation_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set thermostat operation mode."""
from elkm1_lib.const import ThermostatFan, ThermostatMode
settings = {
STATE_IDLE: (ThermostatMode.OFF.value, ThermostatFan.AUTO.value),
STATE_HEAT: (ThermostatMode.HEAT.value, None),
STATE_COOL: (ThermostatMode.COOL.value, None),
STATE_AUTO: (ThermostatMode.AUTO.value, None),
STATE_FAN_ONLY: (ThermostatMode.OFF.value, ThermostatFan.ON.value)
HVAC_MODE_OFF:
(ThermostatMode.OFF.value, ThermostatFan.AUTO.value),
HVAC_MODE_HEAT: (ThermostatMode.HEAT.value, None),
HVAC_MODE_COOL: (ThermostatMode.COOL.value, None),
HVAC_MODE_AUTO: (ThermostatMode.AUTO.value, None),
HVAC_MODE_FAN_ONLY:
(ThermostatMode.OFF.value, ThermostatFan.ON.value)
}
self._elk_set(settings[operation_mode][0], settings[operation_mode][1])
self._elk_set(settings[hvac_mode][0], settings[hvac_mode][1])
async def async_turn_aux_heat_on(self):
"""Turn auxiliary heater on."""
@ -148,14 +154,14 @@ class ElkThermostat(ElkEntity, ClimateDevice):
self._elk_set(ThermostatMode.HEAT.value, None)
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return [STATE_AUTO, STATE_ON]
return [HVAC_MODE_AUTO, STATE_ON]
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
from elkm1_lib.const import ThermostatFan
if fan_mode == STATE_AUTO:
if fan_mode == HVAC_MODE_AUTO:
self._elk_set(None, ThermostatFan.AUTO.value)
elif fan_mode == STATE_ON:
self._elk_set(None, ThermostatFan.ON.value)
@ -175,13 +181,13 @@ class ElkThermostat(ElkEntity, ClimateDevice):
def _element_changed(self, element, changeset):
from elkm1_lib.const import ThermostatFan, ThermostatMode
mode_to_state = {
ThermostatMode.OFF.value: STATE_IDLE,
ThermostatMode.COOL.value: STATE_COOL,
ThermostatMode.HEAT.value: STATE_HEAT,
ThermostatMode.EMERGENCY_HEAT.value: STATE_HEAT,
ThermostatMode.AUTO.value: STATE_AUTO,
ThermostatMode.OFF.value: HVAC_MODE_OFF,
ThermostatMode.COOL.value: HVAC_MODE_COOL,
ThermostatMode.HEAT.value: HVAC_MODE_HEAT,
ThermostatMode.EMERGENCY_HEAT.value: HVAC_MODE_HEAT,
ThermostatMode.AUTO.value: HVAC_MODE_AUTO,
}
self._state = mode_to_state.get(self._element.mode)
if self._state == STATE_IDLE and \
if self._state == HVAC_MODE_OFF and \
self._element.fan == ThermostatFan.ON.value:
self._state = STATE_FAN_ONLY
self._state = HVAC_MODE_FAN_ONLY

View File

@ -5,10 +5,11 @@ import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate.const import (
STATE_HEAT, STATE_AUTO, SUPPORT_AUX_HEAT, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, SUPPORT_AUX_HEAT,
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, CURRENT_HVAC_HEAT,
CURRENT_HVAC_IDLE)
from homeassistant.const import (
ATTR_TEMPERATURE, TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD, STATE_OFF)
ATTR_TEMPERATURE, TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -16,7 +17,7 @@ _LOGGER = logging.getLogger(__name__)
# Return cached results if last scan was less then this time ago
SCAN_INTERVAL = timedelta(seconds=120)
OPERATION_LIST = [STATE_AUTO, STATE_HEAT, STATE_OFF]
OPERATION_LIST = [HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
@ -24,9 +25,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
EPH_TO_HA_STATE = {
'AUTO': STATE_AUTO,
'ON': STATE_HEAT,
'OFF': STATE_OFF
'AUTO': HVAC_MODE_HEAT_COOL,
'ON': HVAC_MODE_HEAT,
'OFF': HVAC_MODE_OFF
}
HA_STATE_TO_EPH = {value: key for key, value in EPH_TO_HA_STATE.items()}
@ -65,11 +66,10 @@ class EphEmberThermostat(ClimateDevice):
def supported_features(self):
"""Return the list of supported features."""
if self._hot_water:
return SUPPORT_AUX_HEAT | SUPPORT_OPERATION_MODE
return SUPPORT_AUX_HEAT
return (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_AUX_HEAT |
SUPPORT_OPERATION_MODE)
SUPPORT_AUX_HEAT)
@property
def name(self):
@ -100,43 +100,35 @@ class EphEmberThermostat(ClimateDevice):
return 1
@property
def device_state_attributes(self):
"""Show Device Attributes."""
attributes = {
'currently_active': self._zone['isCurrentlyActive']
}
return attributes
def hvac_action(self):
"""Return current HVAC action."""
if self._zone['isCurrentlyActive']:
return CURRENT_HVAC_HEAT
return CURRENT_HVAC_IDLE
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
from pyephember.pyephember import ZoneMode
mode = ZoneMode(self._zone['mode'])
return self.map_mode_eph_hass(mode)
@property
def operation_list(self):
def hvac_modes(self):
"""Return the supported operations."""
return OPERATION_LIST
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set the operation mode."""
mode = self.map_mode_hass_eph(operation_mode)
mode = self.map_mode_hass_eph(hvac_mode)
if mode is not None:
self._ember.set_mode_by_name(self._zone_name, mode)
else:
_LOGGER.error("Invalid operation mode provided %s", operation_mode)
_LOGGER.error("Invalid operation mode provided %s", hvac_mode)
@property
def is_on(self):
"""Return current state."""
if self._zone['isCurrentlyActive']:
return True
return None
@property
def is_aux_heat_on(self):
def is_aux_heat(self):
"""Return true if aux heater."""
return self._zone['isBoostActive']
@ -197,4 +189,4 @@ class EphEmberThermostat(ClimateDevice):
@staticmethod
def map_mode_eph_hass(operation_mode):
"""Map from eph mode to home assistant mode."""
return EPH_TO_HA_STATE.get(operation_mode.name, STATE_AUTO)
return EPH_TO_HA_STATE.get(operation_mode.name, HVAC_MODE_HEAT_COOL)

View File

@ -1,16 +1,15 @@
"""Support for eQ-3 Bluetooth Smart thermostats."""
import logging
import eq3bt as eq3 # pylint: disable=import-error
import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
STATE_HEAT, STATE_MANUAL, STATE_ECO,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE,
SUPPORT_ON_OFF)
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST,
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_MAC, CONF_DEVICES, STATE_ON, STATE_OFF,
TEMP_CELSIUS, PRECISION_HALVES)
ATTR_TEMPERATURE, CONF_DEVICES, CONF_MAC, PRECISION_HALVES, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -23,6 +22,32 @@ ATTR_STATE_LOCKED = 'is_locked'
ATTR_STATE_LOW_BAT = 'low_battery'
ATTR_STATE_AWAY_END = 'away_end'
EQ_TO_HA_HVAC = {
eq3.Mode.Open: HVAC_MODE_HEAT,
eq3.Mode.Closed: HVAC_MODE_OFF,
eq3.Mode.Auto: HVAC_MODE_AUTO,
eq3.Mode.Manual: HVAC_MODE_HEAT,
eq3.Mode.Boost: HVAC_MODE_AUTO,
eq3.Mode.Away: HVAC_MODE_HEAT,
}
HA_TO_EQ_HVAC = {
HVAC_MODE_HEAT: eq3.Mode.Manual,
HVAC_MODE_OFF: eq3.Mode.Closed,
HVAC_MODE_AUTO: eq3.Mode.Auto
}
EQ_TO_HA_PRESET = {
eq3.Mode.Boost: PRESET_BOOST,
eq3.Mode.Away: PRESET_AWAY,
}
HA_TO_EQ_PRESET = {
PRESET_BOOST: eq3.Mode.Boost,
PRESET_AWAY: eq3.Mode.Away,
}
DEVICE_SCHEMA = vol.Schema({
vol.Required(CONF_MAC): cv.string,
})
@ -32,8 +57,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Schema({cv.string: DEVICE_SCHEMA}),
})
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_AWAY_MODE | SUPPORT_ON_OFF)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
def setup_platform(hass, config, add_entities, discovery_info=None):
@ -42,7 +66,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
for name, device_cfg in config[CONF_DEVICES].items():
mac = device_cfg[CONF_MAC]
devices.append(EQ3BTSmartThermostat(mac, name))
devices.append(EQ3BTSmartThermostat(mac, name), True)
add_entities(devices)
@ -53,23 +77,8 @@ class EQ3BTSmartThermostat(ClimateDevice):
def __init__(self, _mac, _name):
"""Initialize the thermostat."""
# We want to avoid name clash with this module.
import eq3bt as eq3 # pylint: disable=import-error
self.modes = {
eq3.Mode.Open: STATE_ON,
eq3.Mode.Closed: STATE_OFF,
eq3.Mode.Auto: STATE_HEAT,
eq3.Mode.Manual: STATE_MANUAL,
eq3.Mode.Boost: STATE_BOOST,
eq3.Mode.Away: STATE_ECO,
}
self.reverse_modes = {v: k for k, v in self.modes.items()}
self._name = _name
self._thermostat = eq3.Thermostat(_mac)
self._target_temperature = None
self._target_mode = None
@property
def supported_features(self):
@ -79,7 +88,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
@property
def available(self) -> bool:
"""Return if thermostat is available."""
return self.current_operation is not None
return self._thermostat.mode > 0
@property
def name(self):
@ -111,46 +120,25 @@ class EQ3BTSmartThermostat(ClimateDevice):
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self._target_temperature = temperature
self._thermostat.target_temperature = temperature
@property
def current_operation(self):
def hvac_mode(self):
"""Return the current operation mode."""
if self._thermostat.mode < 0:
return None
return self.modes[self._thermostat.mode]
return HVAC_MODE_OFF
return EQ_TO_HA_HVAC[self._thermostat.mode]
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return [x for x in self.modes.values()]
return list(HA_TO_EQ_HVAC.keys())
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set operation mode."""
self._target_mode = operation_mode
self._thermostat.mode = self.reverse_modes[operation_mode]
def turn_away_mode_off(self):
"""Away mode off turns to AUTO mode."""
self.set_operation_mode(STATE_HEAT)
def turn_away_mode_on(self):
"""Set away mode on."""
self.set_operation_mode(STATE_ECO)
@property
def is_away_mode_on(self):
"""Return if we are away."""
return self.current_operation == STATE_ECO
def turn_on(self):
"""Turn device on."""
self.set_operation_mode(STATE_HEAT)
def turn_off(self):
"""Turn device off."""
self.set_operation_mode(STATE_OFF)
if self.preset_mode:
return
self._thermostat.mode = HA_TO_EQ_HVAC[hvac_mode]
@property
def min_temp(self):
@ -175,6 +163,28 @@ class EQ3BTSmartThermostat(ClimateDevice):
return dev_specific
@property
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp.
Requires SUPPORT_PRESET_MODE.
"""
return EQ_TO_HA_PRESET.get(self._thermostat.mode)
@property
def preset_modes(self):
"""Return a list of available preset modes.
Requires SUPPORT_PRESET_MODE.
"""
return list(HA_TO_EQ_PRESET.keys())
def set_preset_mode(self, preset_mode):
"""Set new preset mode."""
if not preset_mode:
self.set_hvac_mode(HVAC_MODE_HEAT)
self._thermostat.mode = HA_TO_EQ_PRESET[preset_mode]
def update(self):
"""Update the data from the thermostat."""
# pylint: disable=import-error,no-name-in-module
@ -183,15 +193,3 @@ class EQ3BTSmartThermostat(ClimateDevice):
self._thermostat.update()
except BTLEException as ex:
_LOGGER.warning("Updating the state failed: %s", ex)
if (self._target_temperature and
self._thermostat.target_temperature
!= self._target_temperature):
self.set_temperature(temperature=self._target_temperature)
else:
self._target_temperature = None
if (self._target_mode and
self.modes[self._thermostat.mode] != self._target_mode):
self.set_operation_mode(operation_mode=self._target_mode)
else:
self._target_mode = None

View File

@ -6,13 +6,14 @@ from aioesphomeapi import ClimateInfo, ClimateMode, ClimateState
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_AWAY_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE_RANGE, PRESET_AWAY,
HVAC_MODE_OFF)
from homeassistant.const import (
ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE,
STATE_OFF, TEMP_CELSIUS)
TEMP_CELSIUS)
from . import (
EsphomeEntity, esphome_map_enum, esphome_state_property,
@ -34,10 +35,10 @@ async def async_setup_entry(hass, entry, async_add_entities):
@esphome_map_enum
def _climate_modes():
return {
ClimateMode.OFF: STATE_OFF,
ClimateMode.AUTO: STATE_AUTO,
ClimateMode.COOL: STATE_COOL,
ClimateMode.HEAT: STATE_HEAT,
ClimateMode.OFF: HVAC_MODE_OFF,
ClimateMode.AUTO: HVAC_MODE_HEAT_COOL,
ClimateMode.COOL: HVAC_MODE_COOL,
ClimateMode.HEAT: HVAC_MODE_HEAT,
}
@ -68,7 +69,7 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
return TEMP_CELSIUS
@property
def operation_list(self) -> List[str]:
def hvac_modes(self) -> List[str]:
"""Return the list of available operation modes."""
return [
_climate_modes.from_esphome(mode)
@ -94,18 +95,17 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
@property
def supported_features(self) -> int:
"""Return the list of supported features."""
features = SUPPORT_OPERATION_MODE
features = 0
if self._static_info.supports_two_point_target_temperature:
features |= (SUPPORT_TARGET_TEMPERATURE_LOW |
SUPPORT_TARGET_TEMPERATURE_HIGH)
features |= (SUPPORT_TARGET_TEMPERATURE_RANGE)
else:
features |= SUPPORT_TARGET_TEMPERATURE
if self._static_info.supports_away:
features |= SUPPORT_AWAY_MODE
features |= SUPPORT_PRESET_MODE
return features
@esphome_state_property
def current_operation(self) -> Optional[str]:
def hvac_mode(self) -> Optional[str]:
"""Return current operation ie. heat, cool, idle."""
return _climate_modes.from_esphome(self._state.mode)
@ -129,17 +129,12 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
"""Return the highbound target temperature we try to reach."""
return self._state.target_temperature_high
@esphome_state_property
def is_away_mode_on(self) -> Optional[bool]:
"""Return true if away mode is on."""
return self._state.away
async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature (and operation mode if set)."""
data = {'key': self._static_info.key}
if ATTR_OPERATION_MODE in kwargs:
if ATTR_HVAC_MODE in kwargs:
data['mode'] = _climate_modes.from_hass(
kwargs[ATTR_OPERATION_MODE])
kwargs[ATTR_HVAC_MODE])
if ATTR_TEMPERATURE in kwargs:
data['target_temperature'] = kwargs[ATTR_TEMPERATURE]
if ATTR_TARGET_TEMP_LOW in kwargs:
@ -155,12 +150,24 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
mode=_climate_modes.from_hass(operation_mode),
)
async def async_turn_away_mode_on(self) -> None:
"""Turn away mode on."""
await self._client.climate_command(key=self._static_info.key,
away=True)
@property
def preset_mode(self):
"""Return current preset mode."""
if self._state and self._state.away:
return PRESET_AWAY
async def async_turn_away_mode_off(self) -> None:
"""Turn away mode off."""
return None
@property
def preset_modes(self):
"""Return preset modes."""
if self._static_info.supports_away:
return [PRESET_AWAY]
return []
async def async_set_preset_mode(self, preset_mode):
"""Set preset mode."""
away = preset_mode == PRESET_AWAY
await self._client.climate_command(key=self._static_info.key,
away=False)
away=away)

View File

@ -1,38 +1,39 @@
"""Support for (EMEA/EU-based) Honeywell evohome systems."""
# Glossary:
# TCS - temperature control system (a.k.a. Controller, Parent), which can
# have up to 13 Children:
# 0-12 Heating zones (a.k.a. Zone), and
# 0-1 DHW controller, (a.k.a. Boiler)
# The TCS & Zones are implemented as Climate devices, Boiler as a WaterHeater
"""Support for (EMEA/EU-based) Honeywell TCC climate systems.
Such systems include evohome (multi-zone), and Round Thermostat (single zone).
"""
from datetime import datetime, timedelta
import logging
from typing import Any, Dict, Tuple
from dateutil.tz import tzlocal
import requests.exceptions
import voluptuous as vol
import evohomeclient2
from homeassistant.const import (
CONF_SCAN_INTERVAL, CONF_USERNAME, CONF_PASSWORD,
EVENT_HOMEASSISTANT_START,
HTTP_SERVICE_UNAVAILABLE, HTTP_TOO_MANY_REQUESTS,
PRECISION_HALVES, TEMP_CELSIUS)
CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME,
HTTP_SERVICE_UNAVAILABLE, HTTP_TOO_MANY_REQUESTS, TEMP_CELSIUS)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import (
async_track_point_in_utc_time, async_track_time_interval)
from homeassistant.util.dt import as_utc, parse_datetime, utcnow
from .const import (
DOMAIN, DATA_EVOHOME, DISPATCHER_EVOHOME, GWS, TCS)
from .const import DOMAIN, EVO_STRFTIME, STORAGE_VERSION, STORAGE_KEY, GWS, TCS
_LOGGER = logging.getLogger(__name__)
CONF_ACCESS_TOKEN_EXPIRES = 'access_token_expires'
CONF_REFRESH_TOKEN = 'refresh_token'
CONF_LOCATION_IDX = 'location_idx'
SCAN_INTERVAL_DEFAULT = timedelta(seconds=300)
SCAN_INTERVAL_MINIMUM = timedelta(seconds=180)
SCAN_INTERVAL_MINIMUM = timedelta(seconds=60)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
@ -44,229 +45,314 @@ CONFIG_SCHEMA = vol.Schema({
}),
}, extra=vol.ALLOW_EXTRA)
CONF_SECRETS = [
CONF_USERNAME, CONF_PASSWORD,
]
# bit masks for dispatcher packets
EVO_PARENT = 0x01
EVO_CHILD = 0x02
def _local_dt_to_utc(dt_naive: datetime) -> datetime:
dt_aware = as_utc(dt_naive.replace(microsecond=0, tzinfo=tzlocal()))
return dt_aware.replace(tzinfo=None)
def setup(hass, hass_config):
"""Create a (EMEA/EU-based) Honeywell evohome system.
Currently, only the Controller and the Zones are implemented here.
"""
evo_data = hass.data[DATA_EVOHOME] = {}
evo_data['timers'] = {}
# use a copy, since scan_interval is rounded up to nearest 60s
evo_data['params'] = dict(hass_config[DOMAIN])
scan_interval = evo_data['params'][CONF_SCAN_INTERVAL]
scan_interval = timedelta(
minutes=(scan_interval.total_seconds() + 59) // 60)
def _handle_exception(err):
try:
client = evo_data['client'] = evohomeclient2.EvohomeClient(
evo_data['params'][CONF_USERNAME],
evo_data['params'][CONF_PASSWORD],
debug=False
)
raise err
except evohomeclient2.AuthenticationError as err:
except evohomeclient2.AuthenticationError:
_LOGGER.error(
"setup(): Failed to authenticate with the vendor's server. "
"Check your username and password are correct. "
"Resolve any errors and restart HA. Message is: %s",
"Failed to (re)authenticate with the vendor's server. "
"Check that your username and password are correct. "
"Message is: %s",
err
)
return False
except requests.exceptions.ConnectionError:
_LOGGER.error(
"setup(): Unable to connect with the vendor's server. "
"Check your network and the vendor's status page. "
"Resolve any errors and restart HA."
# this appears to be common with Honeywell's servers
_LOGGER.warning(
"Unable to connect with the vendor's server. "
"Check your network and the vendor's status page."
"Message is: %s",
err
)
return False
finally: # Redact any config data that's no longer needed
for parameter in CONF_SECRETS:
evo_data['params'][parameter] = 'REDACTED' \
if evo_data['params'][parameter] else None
except requests.exceptions.HTTPError:
if err.response.status_code == HTTP_SERVICE_UNAVAILABLE:
_LOGGER.warning(
"Vendor says their server is currently unavailable. "
"Check the vendor's status page."
)
return False
evo_data['status'] = {}
if err.response.status_code == HTTP_TOO_MANY_REQUESTS:
_LOGGER.warning(
"The vendor's API rate limit has been exceeded. "
"Consider increasing the %s.", CONF_SCAN_INTERVAL
)
return False
# Redact any installation data that's no longer needed
for loc in client.installation_info:
loc['locationInfo']['locationId'] = 'REDACTED'
loc['locationInfo']['locationOwner'] = 'REDACTED'
loc['locationInfo']['streetAddress'] = 'REDACTED'
loc['locationInfo']['city'] = 'REDACTED'
loc[GWS][0]['gatewayInfo'] = 'REDACTED'
raise # we don't expect/handle any other HTTPErrors
# Pull down the installation configuration
loc_idx = evo_data['params'][CONF_LOCATION_IDX]
try:
evo_data['config'] = client.installation_info[loc_idx]
except IndexError:
_LOGGER.error(
"setup(): config error, '%s' = %s, but its valid range is 0-%s. "
"Unable to continue. Fix any configuration errors and restart HA.",
CONF_LOCATION_IDX, loc_idx, len(client.installation_info) - 1
)
async def async_setup(hass, hass_config):
"""Create a (EMEA/EU-based) Honeywell evohome system."""
broker = EvoBroker(hass, hass_config[DOMAIN])
if not await broker.init_client():
return False
if _LOGGER.isEnabledFor(logging.DEBUG):
tmp_loc = dict(evo_data['config'])
tmp_loc['locationInfo']['postcode'] = 'REDACTED'
if 'dhw' in tmp_loc[GWS][0][TCS][0]: # if this location has DHW...
tmp_loc[GWS][0][TCS][0]['dhw'] = '...'
_LOGGER.debug("setup(): evo_data['config']=%s", tmp_loc)
load_platform(hass, 'climate', DOMAIN, {}, hass_config)
if broker.tcs.hotwater:
_LOGGER.warning("DHW controller detected, however this integration "
"does not currently support DHW controllers.")
if 'dhw' in evo_data['config'][GWS][0][TCS][0]:
_LOGGER.warning(
"setup(): DHW found, but this component doesn't support DHW."
)
@callback
def _first_update(event):
"""When HA has started, the hub knows to retrieve it's first update."""
pkt = {'sender': 'setup()', 'signal': 'refresh', 'to': EVO_PARENT}
async_dispatcher_send(hass, DISPATCHER_EVOHOME, pkt)
hass.bus.listen(EVENT_HOMEASSISTANT_START, _first_update)
async_track_time_interval(
hass, broker.update, hass_config[DOMAIN][CONF_SCAN_INTERVAL]
)
return True
class EvoDevice(Entity):
"""Base for any Honeywell evohome device.
class EvoBroker:
"""Container for evohome client and data."""
Such devices include the Controller, (up to 12) Heating Zones and
def __init__(self, hass, params) -> None:
"""Initialize the evohome client and data structure."""
self.hass = hass
self.params = params
self.config = self.status = self.timers = {}
self.client = self.tcs = None
self._app_storage = None
hass.data[DOMAIN] = {}
hass.data[DOMAIN]['broker'] = self
async def init_client(self) -> bool:
"""Initialse the evohome data broker.
Return True if this is successful, otherwise return False.
"""
refresh_token, access_token, access_token_expires = \
await self._load_auth_tokens()
try:
client = self.client = await self.hass.async_add_executor_job(
evohomeclient2.EvohomeClient,
self.params[CONF_USERNAME],
self.params[CONF_PASSWORD],
False,
refresh_token,
access_token,
access_token_expires
)
except (requests.exceptions.RequestException,
evohomeclient2.AuthenticationError) as err:
if not _handle_exception(err):
return False
else:
if access_token != self.client.access_token:
await self._save_auth_tokens()
finally:
self.params[CONF_PASSWORD] = 'REDACTED'
loc_idx = self.params[CONF_LOCATION_IDX]
try:
self.config = client.installation_info[loc_idx][GWS][0][TCS][0]
except IndexError:
_LOGGER.error(
"Config error: '%s' = %s, but its valid range is 0-%s. "
"Unable to continue. "
"Fix any configuration errors and restart HA.",
CONF_LOCATION_IDX, loc_idx, len(client.installation_info) - 1
)
return False
else:
self.tcs = \
client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa: E501; pylint: disable=protected-access
_LOGGER.debug("Config = %s", self.config)
return True
async def _load_auth_tokens(self) -> Tuple[str, str, datetime]:
store = self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
app_storage = self._app_storage = await store.async_load()
if app_storage.get(CONF_USERNAME) == self.params[CONF_USERNAME]:
refresh_token = app_storage.get(CONF_REFRESH_TOKEN)
access_token = app_storage.get(CONF_ACCESS_TOKEN)
at_expires_str = app_storage.get(CONF_ACCESS_TOKEN_EXPIRES)
if at_expires_str:
at_expires_dt = as_utc(parse_datetime(at_expires_str))
at_expires_dt = at_expires_dt.astimezone(tzlocal())
at_expires_dt = at_expires_dt.replace(tzinfo=None)
else:
at_expires_dt = None
return (refresh_token, access_token, at_expires_dt)
return (None, None, None) # account switched: so tokens wont be valid
async def _save_auth_tokens(self, *args) -> None:
access_token_expires_utc = _local_dt_to_utc(
self.client.access_token_expires)
self._app_storage[CONF_USERNAME] = self.params[CONF_USERNAME]
self._app_storage[CONF_REFRESH_TOKEN] = self.client.refresh_token
self._app_storage[CONF_ACCESS_TOKEN] = self.client.access_token
self._app_storage[CONF_ACCESS_TOKEN_EXPIRES] = \
access_token_expires_utc.isoformat()
store = self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
await store.async_save(self._app_storage)
async_track_point_in_utc_time(
self.hass,
self._save_auth_tokens,
access_token_expires_utc
)
def update(self, *args, **kwargs) -> None:
"""Get the latest state data of the entire evohome Location.
This includes state data for the Controller and all its child devices,
such as the operating mode of the Controller and the current temp of
its children (e.g. Zones, DHW controller).
"""
loc_idx = self.params[CONF_LOCATION_IDX]
try:
status = self.client.locations[loc_idx].status()[GWS][0][TCS][0]
except (requests.exceptions.RequestException,
evohomeclient2.AuthenticationError) as err:
_handle_exception(err)
else:
self.timers['statusUpdated'] = utcnow()
_LOGGER.debug("Status = %s", status)
# inform the evohome devices that state data has been updated
async_dispatcher_send(self.hass, DOMAIN, {'signal': 'refresh'})
class EvoDevice(Entity):
"""Base for any evohome device.
This includes the Controller, (up to 12) Heating Zones and
(optionally) a DHW controller.
"""
def __init__(self, evo_data, client, obj_ref):
def __init__(self, evo_broker, evo_device) -> None:
"""Initialize the evohome entity."""
self._client = client
self._obj = obj_ref
self._evo_device = evo_device
self._evo_tcs = evo_broker.tcs
self._name = None
self._icon = None
self._type = None
self._name = self._icon = self._precision = None
self._state_attributes = []
self._supported_features = None
self._operation_list = None
self._params = evo_data['params']
self._timers = evo_data['timers']
self._status = {}
self._available = False # should become True after first update()
self._setpoints = None
@callback
def _connect(self, packet):
if packet['to'] & self._type and packet['signal'] == 'refresh':
def _refresh(self, packet):
if packet['signal'] == 'refresh':
self.async_schedule_update_ha_state(force_refresh=True)
def _handle_exception(self, err):
try:
raise err
def get_setpoints(self) -> Dict[str, Any]:
"""Return the current/next scheduled switchpoints.
except evohomeclient2.AuthenticationError:
_LOGGER.error(
"Failed to (re)authenticate with the vendor's server. "
"This may be a temporary error. Message is: %s",
err
)
Only Zones & DHW controllers (but not the TCS) have schedules.
"""
switchpoints = {}
schedule = self._evo_device.schedule()
except requests.exceptions.ConnectionError:
# this appears to be common with Honeywell's servers
_LOGGER.warning(
"Unable to connect with the vendor's server. "
"Check your network and the vendor's status page."
)
except requests.exceptions.HTTPError:
if err.response.status_code == HTTP_SERVICE_UNAVAILABLE:
_LOGGER.warning(
"Vendor says their server is currently unavailable. "
"This may be temporary; check the vendor's status page."
)
elif err.response.status_code == HTTP_TOO_MANY_REQUESTS:
_LOGGER.warning(
"The vendor's API rate limit has been exceeded. "
"So will cease polling, and will resume after %s seconds.",
(self._params[CONF_SCAN_INTERVAL] * 3).total_seconds()
)
self._timers['statusUpdated'] = datetime.now() + \
self._params[CONF_SCAN_INTERVAL] * 3
day_time = datetime.now()
day_of_week = int(day_time.strftime('%w')) # 0 is Sunday
# Iterate today's switchpoints until past the current time of day...
day = schedule['DailySchedules'][day_of_week]
sp_idx = -1 # last switchpoint of the day before
for i, tmp in enumerate(day['Switchpoints']):
if day_time.strftime('%H:%M:%S') > tmp['TimeOfDay']:
sp_idx = i # current setpoint
else:
raise # we don't expect/handle any other HTTPErrors
break
# These properties, methods are from the Entity class
async def async_added_to_hass(self):
"""Run when entity about to be added."""
async_dispatcher_connect(self.hass, DISPATCHER_EVOHOME, self._connect)
# Did the current SP start yesterday? Does the next start SP tomorrow?
current_sp_day = -1 if sp_idx == -1 else 0
next_sp_day = 1 if sp_idx + 1 == len(day['Switchpoints']) else 0
for key, offset, idx in [
('current', current_sp_day, sp_idx),
('next', next_sp_day, (sp_idx + 1) * (1 - next_sp_day))]:
spt = switchpoints[key] = {}
sp_date = (day_time + timedelta(days=offset)).strftime('%Y-%m-%d')
day = schedule['DailySchedules'][(day_of_week + offset) % 7]
switchpoint = day['Switchpoints'][idx]
dt_naive = datetime.strptime(
'{}T{}'.format(sp_date, switchpoint['TimeOfDay']),
'%Y-%m-%dT%H:%M:%S')
spt['target_temp'] = switchpoint['heatSetpoint']
spt['from_datetime'] = \
_local_dt_to_utc(dt_naive).strftime(EVO_STRFTIME)
return switchpoints
@property
def should_poll(self) -> bool:
"""Most evohome devices push their state to HA.
Only the Controller should be polled.
"""
"""Evohome entities should not be polled."""
return False
@property
def name(self) -> str:
"""Return the name to use in the frontend UI."""
"""Return the name of the Evohome entity."""
return self._name
@property
def device_state_attributes(self):
"""Return the device state attributes of the evohome device.
def device_state_attributes(self) -> Dict[str, Any]:
"""Return the Evohome-specific state attributes."""
status = {}
for attr in self._state_attributes:
if attr != 'setpoints':
status[attr] = getattr(self._evo_device, attr)
This is state data that is not available otherwise, due to the
restrictions placed upon ClimateDevice properties, etc. by HA.
"""
return {'status': self._status}
if 'setpoints' in self._state_attributes:
status['setpoints'] = self._setpoints
return {'status': status}
@property
def icon(self):
def icon(self) -> str:
"""Return the icon to use in the frontend UI."""
return self._icon
@property
def available(self) -> bool:
"""Return True if the device is currently available."""
return self._available
@property
def supported_features(self):
"""Get the list of supported features of the device."""
def supported_features(self) -> int:
"""Get the flag of supported features of the device."""
return self._supported_features
# These properties are common to ClimateDevice, WaterHeaterDevice classes
@property
def precision(self):
"""Return the temperature precision to use in the frontend UI."""
return PRECISION_HALVES
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""
async_dispatcher_connect(self.hass, DOMAIN, self._refresh)
@property
def temperature_unit(self):
def precision(self) -> float:
"""Return the temperature precision to use in the frontend UI."""
return self._precision
@property
def temperature_unit(self) -> str:
"""Return the temperature unit to use in the frontend UI."""
return TEMP_CELSIUS
@property
def operation_list(self):
"""Return the list of available operations."""
return self._operation_list
def update(self) -> None:
"""Get the latest state data."""
self._setpoints = self.get_setpoints()

View File

@ -1,457 +1,331 @@
"""Support for Climate devices of (EMEA/EU-based) Honeywell evohome systems."""
from datetime import datetime, timedelta
"""Support for Climate devices of (EMEA/EU-based) Honeywell TCC systems."""
from datetime import datetime
import logging
from typing import Optional, List
import requests.exceptions
import evohomeclient2
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_ECO, STATE_MANUAL, SUPPORT_AWAY_MODE, SUPPORT_ON_OFF,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
CONF_SCAN_INTERVAL, STATE_OFF,)
from homeassistant.helpers.dispatcher import dispatcher_send
HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF,
PRESET_AWAY, PRESET_ECO, PRESET_HOME,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE)
from . import (
EvoDevice,
CONF_LOCATION_IDX, EVO_CHILD, EVO_PARENT)
from . import CONF_LOCATION_IDX, _handle_exception, EvoDevice
from .const import (
DATA_EVOHOME, DISPATCHER_EVOHOME, GWS, TCS)
DOMAIN, EVO_STRFTIME,
EVO_RESET, EVO_AUTO, EVO_AUTOECO, EVO_AWAY, EVO_DAYOFF, EVO_CUSTOM,
EVO_HEATOFF, EVO_FOLLOW, EVO_TEMPOVER, EVO_PERMOVER)
_LOGGER = logging.getLogger(__name__)
# The Controller's opmode/state and the zone's (inherited) state
EVO_RESET = 'AutoWithReset'
EVO_AUTO = 'Auto'
EVO_AUTOECO = 'AutoWithEco'
EVO_AWAY = 'Away'
EVO_DAYOFF = 'DayOff'
EVO_CUSTOM = 'Custom'
EVO_HEATOFF = 'HeatingOff'
PRESET_RESET = 'Reset' # reset all child zones to EVO_FOLLOW
PRESET_CUSTOM = 'Custom'
# These are for Zones' opmode, and state
EVO_FOLLOW = 'FollowSchedule'
EVO_TEMPOVER = 'TemporaryOverride'
EVO_PERMOVER = 'PermanentOverride'
HA_HVAC_TO_TCS = {
HVAC_MODE_OFF: EVO_HEATOFF,
HVAC_MODE_HEAT: EVO_AUTO,
}
HA_PRESET_TO_TCS = {
PRESET_AWAY: EVO_AWAY,
PRESET_CUSTOM: EVO_CUSTOM,
PRESET_ECO: EVO_AUTOECO,
PRESET_HOME: EVO_DAYOFF,
PRESET_RESET: EVO_RESET,
}
TCS_PRESET_TO_HA = {v: k for k, v in HA_PRESET_TO_TCS.items()}
# For the Controller. NB: evohome treats Away mode as a mode in/of itself,
# where HA considers it to 'override' the exising operating mode
TCS_STATE_TO_HA = {
EVO_RESET: STATE_AUTO,
EVO_AUTO: STATE_AUTO,
EVO_AUTOECO: STATE_ECO,
EVO_AWAY: STATE_AUTO,
EVO_DAYOFF: STATE_AUTO,
EVO_CUSTOM: STATE_AUTO,
EVO_HEATOFF: STATE_OFF
HA_PRESET_TO_EVO = {
'temporary': EVO_TEMPOVER,
'permanent': EVO_PERMOVER,
}
HA_STATE_TO_TCS = {
STATE_AUTO: EVO_AUTO,
STATE_ECO: EVO_AUTOECO,
STATE_OFF: EVO_HEATOFF
}
TCS_OP_LIST = list(HA_STATE_TO_TCS)
# the Zones' opmode; their state is usually 'inherited' from the TCS
EVO_FOLLOW = 'FollowSchedule'
EVO_TEMPOVER = 'TemporaryOverride'
EVO_PERMOVER = 'PermanentOverride'
# for the Zones...
ZONE_STATE_TO_HA = {
EVO_FOLLOW: STATE_AUTO,
EVO_TEMPOVER: STATE_MANUAL,
EVO_PERMOVER: STATE_MANUAL
}
HA_STATE_TO_ZONE = {
STATE_AUTO: EVO_FOLLOW,
STATE_MANUAL: EVO_PERMOVER
}
ZONE_OP_LIST = list(HA_STATE_TO_ZONE)
EVO_PRESET_TO_HA = {v: k for k, v in HA_PRESET_TO_EVO.items()}
async def async_setup_platform(hass, hass_config, async_add_entities,
discovery_info=None):
discovery_info=None) -> None:
"""Create the evohome Controller, and its Zones, if any."""
evo_data = hass.data[DATA_EVOHOME]
client = evo_data['client']
loc_idx = evo_data['params'][CONF_LOCATION_IDX]
# evohomeclient has exposed no means of accessing non-default location
# (i.e. loc_idx > 0) other than using a protected member, such as below
tcs_obj_ref = client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa: E501; pylint: disable=protected-access
broker = hass.data[DOMAIN]['broker']
loc_idx = broker.params[CONF_LOCATION_IDX]
_LOGGER.debug(
"Found Controller, id=%s [%s], name=%s (location_idx=%s)",
tcs_obj_ref.systemId, tcs_obj_ref.modelType, tcs_obj_ref.location.name,
broker.tcs.systemId, broker.tcs.modelType, broker.tcs.location.name,
loc_idx)
controller = EvoController(evo_data, client, tcs_obj_ref)
zones = []
controller = EvoController(broker, broker.tcs)
for zone_idx in tcs_obj_ref.zones:
zone_obj_ref = tcs_obj_ref.zones[zone_idx]
zones = []
for zone_idx in broker.tcs.zones:
evo_zone = broker.tcs.zones[zone_idx]
_LOGGER.debug(
"Found Zone, id=%s [%s], name=%s",
zone_obj_ref.zoneId, zone_obj_ref.zone_type, zone_obj_ref.name)
zones.append(EvoZone(evo_data, client, zone_obj_ref))
evo_zone.zoneId, evo_zone.zone_type, evo_zone.name)
zones.append(EvoZone(broker, evo_zone))
entities = [controller] + zones
async_add_entities(entities, update_before_add=False)
async_add_entities(entities, update_before_add=True)
class EvoZone(EvoDevice, ClimateDevice):
"""Base for a Honeywell evohome Zone device."""
class EvoClimateDevice(EvoDevice, ClimateDevice):
"""Base for a Honeywell evohome Climate device."""
def __init__(self, evo_data, client, obj_ref):
def __init__(self, evo_broker, evo_device) -> None:
"""Initialize the evohome Climate device."""
super().__init__(evo_broker, evo_device)
self._hvac_modes = self._preset_modes = None
@property
def hvac_modes(self) -> List[str]:
"""Return the list of available hvac operation modes."""
return self._hvac_modes
@property
def preset_modes(self) -> Optional[List[str]]:
"""Return a list of available preset modes."""
return self._preset_modes
class EvoZone(EvoClimateDevice):
"""Base for a Honeywell evohome Zone."""
def __init__(self, evo_broker, evo_device) -> None:
"""Initialize the evohome Zone."""
super().__init__(evo_data, client, obj_ref)
super().__init__(evo_broker, evo_device)
self._id = obj_ref.zoneId
self._name = obj_ref.name
self._icon = "mdi:radiator"
self._type = EVO_CHILD
self._id = evo_device.zoneId
self._name = evo_device.name
self._icon = 'mdi:radiator'
for _zone in evo_data['config'][GWS][0][TCS][0]['zones']:
self._precision = \
self._evo_device.setpointCapabilities['valueResolution']
self._state_attributes = [
'activeFaults', 'setpointStatus', 'temperatureStatus', 'setpoints']
self._supported_features = SUPPORT_PRESET_MODE | \
SUPPORT_TARGET_TEMPERATURE
self._hvac_modes = [HVAC_MODE_OFF, HVAC_MODE_HEAT]
self._preset_modes = list(HA_PRESET_TO_EVO)
for _zone in evo_broker.config['zones']:
if _zone['zoneId'] == self._id:
self._config = _zone
break
self._status = {}
self._operation_list = ZONE_OP_LIST
self._supported_features = \
SUPPORT_OPERATION_MODE | \
SUPPORT_TARGET_TEMPERATURE | \
SUPPORT_ON_OFF
@property
def current_operation(self):
def hvac_mode(self) -> str:
"""Return the current operating mode of the evohome Zone.
The evohome Zones that are in 'FollowSchedule' mode inherit their
actual operating mode from the Controller.
"""
evo_data = self.hass.data[DATA_EVOHOME]
NB: evohome Zones 'inherit' their operating mode from the controller.
system_mode = evo_data['status']['systemModeStatus']['mode']
setpoint_mode = self._status['setpointStatus']['setpointMode']
if setpoint_mode == EVO_FOLLOW:
# then inherit state from the controller
if system_mode == EVO_RESET:
current_operation = TCS_STATE_TO_HA.get(EVO_AUTO)
else:
current_operation = TCS_STATE_TO_HA.get(system_mode)
else:
current_operation = ZONE_STATE_TO_HA.get(setpoint_mode)
return current_operation
@property
def current_temperature(self):
"""Return the current temperature of the evohome Zone."""
return (self._status['temperatureStatus']['temperature']
if self._status['temperatureStatus']['isAvailable'] else None)
@property
def target_temperature(self):
"""Return the target temperature of the evohome Zone."""
return self._status['setpointStatus']['targetHeatTemperature']
@property
def is_on(self) -> bool:
"""Return True if the evohome Zone is off.
A Zone is considered off if its target temp is set to its minimum, and
it is not following its schedule (i.e. not in 'FollowSchedule' mode).
"""
is_off = \
self.target_temperature == self.min_temp and \
self._status['setpointStatus']['setpointMode'] == EVO_PERMOVER
return not is_off
@property
def min_temp(self):
"""Return the minimum target temperature of a evohome Zone.
The default is 5 (in Celsius), but it is configurable within 5-35.
"""
return self._config['setpointCapabilities']['minHeatSetpoint']
@property
def max_temp(self):
"""Return the maximum target temperature of a evohome Zone.
The default is 35 (in Celsius), but it is configurable within 5-35.
"""
return self._config['setpointCapabilities']['maxHeatSetpoint']
def _set_temperature(self, temperature, until=None):
"""Set the new target temperature of a Zone.
temperature is required, until can be:
- strftime('%Y-%m-%dT%H:%M:%SZ') for TemporaryOverride, or
- None for PermanentOverride (i.e. indefinitely)
"""
try:
self._obj.set_temperature(temperature, until)
except (requests.exceptions.RequestException,
evohomeclient2.AuthenticationError) as err:
self._handle_exception(err)
def set_temperature(self, **kwargs):
"""Set new target temperature, indefinitely."""
self._set_temperature(kwargs['temperature'], until=None)
def turn_on(self):
"""Turn the evohome Zone on.
This is achieved by setting the Zone to its 'FollowSchedule' mode.
"""
self._set_operation_mode(EVO_FOLLOW)
def turn_off(self):
"""Turn the evohome Zone off.
This is achieved by setting the Zone to its minimum temperature,
indefinitely (i.e. 'PermanentOverride' mode).
"""
self._set_temperature(self.min_temp, until=None)
def _set_operation_mode(self, operation_mode):
if operation_mode == EVO_FOLLOW:
try:
self._obj.cancel_temp_override()
except (requests.exceptions.RequestException,
evohomeclient2.AuthenticationError) as err:
self._handle_exception(err)
elif operation_mode == EVO_TEMPOVER:
_LOGGER.error(
"_set_operation_mode(op_mode=%s): mode not yet implemented",
operation_mode
)
elif operation_mode == EVO_PERMOVER:
self._set_temperature(self.target_temperature, until=None)
else:
_LOGGER.error(
"_set_operation_mode(op_mode=%s): mode not valid",
operation_mode
)
def set_operation_mode(self, operation_mode):
"""Set an operating mode for a Zone.
Currently limited to 'Auto' & 'Manual'. If 'Off' is needed, it can be
enabled via turn_off method.
NB: evohome Zones do not have an operating mode as understood by HA.
Instead they usually 'inherit' an operating mode from their controller.
More correctly, these Zones are in a follow mode, 'FollowSchedule',
where their setpoint temperatures are a function of their schedule, and
the Controller's operating_mode, e.g. Economy mode is their scheduled
setpoint less (usually) 3C.
Thus, you cannot set a Zone to Away mode, but the location (i.e. the
Controller) is set to Away and each Zones's setpoints are adjusted
accordingly to some lower temperature.
Usually, Zones are in 'FollowSchedule' mode, where their setpoints are
a function of their schedule, and the Controller's operating_mode, e.g.
Economy mode is their scheduled setpoint less (usually) 3C.
However, Zones can override these setpoints, either for a specified
period of time, 'TemporaryOverride', after which they will revert back
to 'FollowSchedule' mode, or indefinitely, 'PermanentOverride'.
"""
self._set_operation_mode(HA_STATE_TO_ZONE.get(operation_mode))
if self._evo_tcs.systemModeStatus['mode'] in [EVO_AWAY, EVO_HEATOFF]:
return HVAC_MODE_AUTO
is_off = self.target_temperature <= self.min_temp
return HVAC_MODE_OFF if is_off else HVAC_MODE_HEAT
def update(self):
"""Process the evohome Zone's state data."""
evo_data = self.hass.data[DATA_EVOHOME]
@property
def current_temperature(self) -> Optional[float]:
"""Return the current temperature of the evohome Zone."""
return (self._evo_device.temperatureStatus['temperature']
if self._evo_device.temperatureStatus['isAvailable'] else None)
for _zone in evo_data['status']['zones']:
if _zone['zoneId'] == self._id:
self._status = _zone
break
@property
def target_temperature(self) -> Optional[float]:
"""Return the target temperature of the evohome Zone."""
if self._evo_tcs.systemModeStatus['mode'] == EVO_HEATOFF:
return self._evo_device.setpointCapabilities['minHeatSetpoint']
return self._evo_device.setpointStatus['targetHeatTemperature']
self._available = True
@property
def preset_mode(self) -> Optional[str]:
"""Return the current preset mode, e.g., home, away, temp."""
if self._evo_tcs.systemModeStatus['mode'] in [EVO_AWAY, EVO_HEATOFF]:
return None
return EVO_PRESET_TO_HA.get(
self._evo_device.setpointStatus['setpointMode'], 'follow')
@property
def min_temp(self) -> float:
"""Return the minimum target temperature of a evohome Zone.
The default is 5, but is user-configurable within 5-35 (in Celsius).
"""
return self._evo_device.setpointCapabilities['minHeatSetpoint']
@property
def max_temp(self) -> float:
"""Return the maximum target temperature of a evohome Zone.
The default is 35, but is user-configurable within 5-35 (in Celsius).
"""
return self._evo_device.setpointCapabilities['maxHeatSetpoint']
def _set_temperature(self, temperature: float,
until: Optional[datetime] = None):
"""Set a new target temperature for the Zone.
until == None means indefinitely (i.e. PermanentOverride)
"""
try:
self._evo_device.set_temperature(temperature, until)
except (requests.exceptions.RequestException,
evohomeclient2.AuthenticationError) as err:
_handle_exception(err)
def set_temperature(self, **kwargs) -> None:
"""Set a new target temperature for an hour."""
until = kwargs.get('until')
if until:
until = datetime.strptime(until, EVO_STRFTIME)
self._set_temperature(kwargs['temperature'], until)
def _set_operation_mode(self, op_mode) -> None:
"""Set the Zone to one of its native EVO_* operating modes."""
if op_mode == EVO_FOLLOW:
try:
self._evo_device.cancel_temp_override()
except (requests.exceptions.RequestException,
evohomeclient2.AuthenticationError) as err:
_handle_exception(err)
return
self._setpoints = self.get_setpoints()
temperature = self._evo_device.setpointStatus['targetHeatTemperature']
if op_mode == EVO_TEMPOVER:
until = self._setpoints['next']['from_datetime']
until = datetime.strptime(until, EVO_STRFTIME)
else: # EVO_PERMOVER:
until = None
self._set_temperature(temperature, until=until)
def set_hvac_mode(self, hvac_mode: str) -> None:
"""Set an operating mode for the Zone."""
if hvac_mode == HVAC_MODE_OFF:
self._set_temperature(self.min_temp, until=None)
else: # HVAC_MODE_HEAT
self._set_operation_mode(EVO_FOLLOW)
def set_preset_mode(self, preset_mode: str) -> None:
"""Set a new preset mode.
If preset_mode is None, then revert to following the schedule.
"""
self._set_operation_mode(HA_PRESET_TO_EVO.get(preset_mode, EVO_FOLLOW))
class EvoController(EvoDevice, ClimateDevice):
"""Base for a Honeywell evohome hub/Controller device.
class EvoController(EvoClimateDevice):
"""Base for a Honeywell evohome Controller (hub).
The Controller (aka TCS, temperature control system) is the parent of all
the child (CH/DHW) devices. It is also a Climate device.
"""
def __init__(self, evo_data, client, obj_ref):
def __init__(self, evo_broker, evo_device) -> None:
"""Initialize the evohome Controller (hub)."""
super().__init__(evo_data, client, obj_ref)
super().__init__(evo_broker, evo_device)
self._id = obj_ref.systemId
self._name = '_{}'.format(obj_ref.location.name)
self._icon = "mdi:thermostat"
self._type = EVO_PARENT
self._id = evo_device.systemId
self._name = evo_device.location.name
self._icon = 'mdi:thermostat'
self._config = evo_data['config'][GWS][0][TCS][0]
self._status = evo_data['status']
self._timers['statusUpdated'] = datetime.min
self._precision = None
self._state_attributes = [
'activeFaults', 'systemModeStatus']
self._operation_list = TCS_OP_LIST
self._supported_features = \
SUPPORT_OPERATION_MODE | \
SUPPORT_AWAY_MODE
self._supported_features = SUPPORT_PRESET_MODE
self._hvac_modes = list(HA_HVAC_TO_TCS)
self._preset_modes = list(HA_PRESET_TO_TCS)
self._config = dict(evo_broker.config)
self._config['zones'] = '...'
if 'dhw' in self._config:
self._config['dhw'] = '...'
@property
def device_state_attributes(self):
"""Return the device state attributes of the evohome Controller.
This is state data that is not available otherwise, due to the
restrictions placed upon ClimateDevice properties, etc. by HA.
"""
status = dict(self._status)
if 'zones' in status:
del status['zones']
if 'dhw' in status:
del status['dhw']
return {'status': status}
@property
def current_operation(self):
def hvac_mode(self) -> str:
"""Return the current operating mode of the evohome Controller."""
return TCS_STATE_TO_HA.get(self._status['systemModeStatus']['mode'])
tcs_mode = self._evo_device.systemModeStatus['mode']
return HVAC_MODE_OFF if tcs_mode == EVO_HEATOFF else HVAC_MODE_HEAT
@property
def current_temperature(self):
"""Return the average current temperature of the Heating/DHW zones.
def current_temperature(self) -> Optional[float]:
"""Return the average current temperature of the heating Zones.
Although evohome Controllers do not have a target temp, one is
expected by the HA schema.
Controllers do not have a current temp, but one is expected by HA.
"""
tmp_list = [x for x in self._status['zones']
if x['temperatureStatus']['isAvailable']]
temps = [zone['temperatureStatus']['temperature'] for zone in tmp_list]
avg_temp = round(sum(temps) / len(temps), 1) if temps else None
return avg_temp
temps = [z.temperatureStatus['temperature'] for z in
self._evo_device._zones if z.temperatureStatus['isAvailable']] # noqa: E501; pylint: disable=protected-access
return round(sum(temps) / len(temps), 1) if temps else None
@property
def target_temperature(self):
"""Return the average target temperature of the Heating/DHW zones.
def target_temperature(self) -> Optional[float]:
"""Return the average target temperature of the heating Zones.
Although evohome Controllers do not have a target temp, one is
expected by the HA schema.
Controllers do not have a target temp, but one is expected by HA.
"""
temps = [zone['setpointStatus']['targetHeatTemperature']
for zone in self._status['zones']]
avg_temp = round(sum(temps) / len(temps), 1) if temps else None
return avg_temp
temps = [z.setpointStatus['targetHeatTemperature']
for z in self._evo_device._zones] # noqa: E501; pylint: disable=protected-access
return round(sum(temps) / len(temps), 1) if temps else None
@property
def is_away_mode_on(self) -> bool:
"""Return True if away mode is on."""
return self._status['systemModeStatus']['mode'] == EVO_AWAY
def preset_mode(self) -> Optional[str]:
"""Return the current preset mode, e.g., home, away, temp."""
return TCS_PRESET_TO_HA.get(self._evo_device.systemModeStatus['mode'])
@property
def is_on(self) -> bool:
"""Return True as evohome Controllers are always on.
def min_temp(self) -> float:
"""Return the minimum target temperature of the heating Zones.
For example, evohome Controllers have a 'HeatingOff' mode, but even
then the DHW would remain on.
Controllers do not have a min target temp, but one is required by HA.
"""
return True
temps = [z.setpointCapabilities['minHeatSetpoint']
for z in self._evo_device._zones] # noqa: E501; pylint: disable=protected-access
return min(temps) if temps else 5
@property
def min_temp(self):
"""Return the minimum target temperature of a evohome Controller.
def max_temp(self) -> float:
"""Return the maximum target temperature of the heating Zones.
Although evohome Controllers do not have a minimum target temp, one is
expected by the HA schema; the default for an evohome HR92 is used.
Controllers do not have a max target temp, but one is required by HA.
"""
return 5
temps = [z.setpointCapabilities['maxHeatSetpoint']
for z in self._evo_device._zones] # noqa: E501; pylint: disable=protected-access
return max(temps) if temps else 35
@property
def max_temp(self):
"""Return the maximum target temperature of a evohome Controller.
Although evohome Controllers do not have a maximum target temp, one is
expected by the HA schema; the default for an evohome HR92 is used.
"""
return 35
@property
def should_poll(self) -> bool:
"""Return True as the evohome Controller should always be polled."""
return True
def _set_operation_mode(self, operation_mode):
def _set_operation_mode(self, op_mode) -> None:
"""Set the Controller to any of its native EVO_* operating modes."""
try:
self._obj._set_status(operation_mode) # noqa: E501; pylint: disable=protected-access
self._evo_device._set_status(op_mode) # noqa: E501; pylint: disable=protected-access
except (requests.exceptions.RequestException,
evohomeclient2.AuthenticationError) as err:
self._handle_exception(err)
_handle_exception(err)
def set_operation_mode(self, operation_mode):
"""Set new target operation mode for the TCS.
def set_hvac_mode(self, hvac_mode: str) -> None:
"""Set an operating mode for the Controller."""
self._set_operation_mode(HA_HVAC_TO_TCS.get(hvac_mode))
Currently limited to 'Auto', 'AutoWithEco' & 'HeatingOff'. If 'Away'
mode is needed, it can be enabled via turn_away_mode_on method.
def set_preset_mode(self, preset_mode: str) -> None:
"""Set a new preset mode.
If preset_mode is None, then revert to 'Auto' mode.
"""
self._set_operation_mode(HA_STATE_TO_TCS.get(operation_mode))
self._set_operation_mode(HA_PRESET_TO_TCS.get(preset_mode, EVO_AUTO))
def turn_away_mode_on(self):
"""Turn away mode on.
The evohome Controller will not remember is previous operating mode.
"""
self._set_operation_mode(EVO_AWAY)
def turn_away_mode_off(self):
"""Turn away mode off.
The evohome Controller can not recall its previous operating mode (as
intimated by the HA schema), so this method is achieved by setting the
Controller's mode back to Auto.
"""
self._set_operation_mode(EVO_AUTO)
def update(self):
"""Get the latest state data of the entire evohome Location.
This includes state data for the Controller and all its child devices,
such as the operating mode of the Controller and the current temp of
its children (e.g. Zones, DHW controller).
"""
# should the latest evohome state data be retreived this cycle?
timeout = datetime.now() + timedelta(seconds=55)
expired = timeout > self._timers['statusUpdated'] + \
self._params[CONF_SCAN_INTERVAL]
if not expired:
return
# Retrieve the latest state data via the client API
loc_idx = self._params[CONF_LOCATION_IDX]
try:
self._status.update(
self._client.locations[loc_idx].status()[GWS][0][TCS][0])
except (requests.exceptions.RequestException,
evohomeclient2.AuthenticationError) as err:
self._handle_exception(err)
else:
self._timers['statusUpdated'] = datetime.now()
self._available = True
_LOGGER.debug("Status = %s", self._status)
# inform the child devices that state data has been updated
pkt = {'sender': 'controller', 'signal': 'refresh', 'to': EVO_CHILD}
dispatcher_send(self.hass, DISPATCHER_EVOHOME, pkt)
def update(self) -> None:
"""Get the latest state data."""
pass

View File

@ -1,9 +1,25 @@
"""Provides the constants needed for evohome."""
"""Support for (EMEA/EU-based) Honeywell TCC climate systems."""
DOMAIN = 'evohome'
DATA_EVOHOME = 'data_' + DOMAIN
DISPATCHER_EVOHOME = 'dispatcher_' + DOMAIN
# These are used only to help prevent E501 (line too long) violations.
STORAGE_VERSION = 1
STORAGE_KEY = DOMAIN
# The Parent's (i.e. TCS, Controller's) operating mode is one of:
EVO_RESET = 'AutoWithReset'
EVO_AUTO = 'Auto'
EVO_AUTOECO = 'AutoWithEco'
EVO_AWAY = 'Away'
EVO_DAYOFF = 'DayOff'
EVO_CUSTOM = 'Custom'
EVO_HEATOFF = 'HeatingOff'
# The Childs' operating mode is one of:
EVO_FOLLOW = 'FollowSchedule' # the operating mode is 'inherited' from the TCS
EVO_TEMPOVER = 'TemporaryOverride'
EVO_PERMOVER = 'PermanentOverride'
# These are used only to help prevent E501 (line too long) violations
GWS = 'gateways'
TCS = 'temperatureControlSystems'
EVO_STRFTIME = '%Y-%m-%dT%H:%M:%SZ'

View File

@ -3,7 +3,7 @@
"name": "Evohome",
"documentation": "https://www.home-assistant.io/components/evohome",
"requirements": [
"evohomeclient==0.3.2"
"evohomeclient==0.3.3"
],
"dependencies": [],
"codeowners": ["@zxdavb"]

View File

@ -1,90 +1,87 @@
"""Support for Fibaro thermostats."""
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_COOL, STATE_DRY,
STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
STATE_MANUAL, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE)
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, SUPPORT_FAN_MODE,
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.components.climate import (
ClimateDevice)
from . import FIBARO_DEVICES, FibaroDevice
from homeassistant.const import (
ATTR_TEMPERATURE,
STATE_OFF,
TEMP_CELSIUS,
TEMP_FAHRENHEIT)
from . import (
FIBARO_DEVICES, FibaroDevice)
SPEED_LOW = 'low'
SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high'
# State definitions missing from HA, but defined by Z-Wave standard.
# We map them to states known supported by HA here:
STATE_AUXILIARY = STATE_HEAT
STATE_RESUME = STATE_HEAT
STATE_MOIST = STATE_DRY
STATE_AUTO_CHANGEOVER = STATE_AUTO
STATE_ENERGY_HEAT = STATE_ECO
STATE_ENERGY_COOL = STATE_COOL
STATE_FULL_POWER = STATE_AUTO
STATE_FORCE_OPEN = STATE_MANUAL
STATE_AWAY = STATE_AUTO
STATE_FURNACE = STATE_HEAT
FAN_AUTO_HIGH = 'auto_high'
FAN_AUTO_MEDIUM = 'auto_medium'
FAN_CIRCULATION = 'circulation'
FAN_HUMIDITY_CIRCULATION = 'humidity_circulation'
FAN_LEFT_RIGHT = 'left_right'
FAN_UP_DOWN = 'up_down'
FAN_QUIET = 'quiet'
PRESET_RESUME = 'resume'
PRESET_MOIST = 'moist'
PRESET_FURNACE = 'furnace'
PRESET_CHANGEOVER = 'changeover'
PRESET_ECO_HEAT = 'eco_heat'
PRESET_ECO_COOL = 'eco_cool'
PRESET_FORCE_OPEN = 'force_open'
_LOGGER = logging.getLogger(__name__)
# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
# Table 128, Thermostat Fan Mode Set version 4::Fan Mode encoding
FANMODES = {
0: STATE_OFF,
1: SPEED_LOW,
2: FAN_AUTO_HIGH,
3: SPEED_HIGH,
4: FAN_AUTO_MEDIUM,
5: SPEED_MEDIUM,
6: FAN_CIRCULATION,
7: FAN_HUMIDITY_CIRCULATION,
8: FAN_LEFT_RIGHT,
9: FAN_UP_DOWN,
10: FAN_QUIET,
128: STATE_AUTO
0: 'off',
1: 'low',
2: 'auto_high',
3: 'medium',
4: 'auto_medium',
5: 'high',
6: 'circulation',
7: 'humidity_circulation',
8: 'left_right',
9: 'up_down',
10: 'quiet',
128: 'auto'
}
HA_FANMODES = {v: k for k, v in FANMODES.items()}
# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
# Table 130, Thermostat Mode Set version 3::Mode encoding.
OPMODES = {
0: STATE_OFF,
1: STATE_HEAT,
2: STATE_COOL,
3: STATE_AUTO,
4: STATE_AUXILIARY,
5: STATE_RESUME,
6: STATE_FAN_ONLY,
7: STATE_FURNACE,
8: STATE_DRY,
9: STATE_MOIST,
10: STATE_AUTO_CHANGEOVER,
11: STATE_ENERGY_HEAT,
12: STATE_ENERGY_COOL,
13: STATE_AWAY,
15: STATE_FULL_POWER,
31: STATE_FORCE_OPEN
# 4 AUXILARY
OPMODES_PRESET = {
5: PRESET_RESUME,
7: PRESET_FURNACE,
9: PRESET_MOIST,
10: PRESET_CHANGEOVER,
11: PRESET_ECO_HEAT,
12: PRESET_ECO_COOL,
13: PRESET_AWAY,
15: PRESET_BOOST,
31: PRESET_FORCE_OPEN,
}
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
HA_OPMODES_PRESET = {v: k for k, v in OPMODES_PRESET.items()}
OPMODES_HVAC = {
0: HVAC_MODE_OFF,
1: HVAC_MODE_HEAT,
2: HVAC_MODE_COOL,
3: HVAC_MODE_AUTO,
4: HVAC_MODE_HEAT,
5: HVAC_MODE_AUTO,
6: HVAC_MODE_FAN_ONLY,
7: HVAC_MODE_HEAT,
8: HVAC_MODE_DRY,
9: HVAC_MODE_DRY,
10: HVAC_MODE_AUTO,
11: HVAC_MODE_HEAT,
12: HVAC_MODE_COOL,
13: HVAC_MODE_AUTO,
15: HVAC_MODE_AUTO,
31: HVAC_MODE_HEAT,
}
HA_OPMODES_HVAC = {
HVAC_MODE_OFF: 0,
HVAC_MODE_HEAT: 1,
HVAC_MODE_COOL: 2,
HVAC_MODE_AUTO: 3,
HVAC_MODE_FAN_ONLY: 6,
}
def setup_platform(hass, config, add_entities, discovery_info=None):
@ -109,10 +106,9 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
self._fan_mode_device = None
self._support_flags = 0
self.entity_id = 'climate.{}'.format(self.ha_id)
self._fan_mode_to_state = {}
self._fan_state_to_mode = {}
self._op_mode_to_state = {}
self._op_state_to_mode = {}
self._hvac_support = []
self._preset_support = []
self._fan_support = []
siblings = fibaro_device.fibaro_controller.get_siblings(
fibaro_device.id)
@ -129,7 +125,7 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
if 'setMode' in device.actions or \
'setOperatingMode' in device.actions:
self._op_mode_device = FibaroDevice(device)
self._support_flags |= SUPPORT_OPERATION_MODE
self._support_flags |= SUPPORT_PRESET_MODE
if 'setFanMode' in device.actions:
self._fan_mode_device = FibaroDevice(device)
self._support_flags |= SUPPORT_FAN_MODE
@ -143,11 +139,11 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
fan_modes = self._fan_mode_device.fibaro_device.\
properties.supportedModes.split(",")
for mode in fan_modes:
try:
self._fan_mode_to_state[int(mode)] = FANMODES[int(mode)]
self._fan_state_to_mode[FANMODES[int(mode)]] = int(mode)
except KeyError:
self._fan_mode_to_state[int(mode)] = 'unknown'
mode = int(mode)
if mode not in FANMODES:
_LOGGER.warning("%d unknown fan mode", mode)
continue
self._fan_support.append(FANMODES[int(mode)])
if self._op_mode_device:
prop = self._op_mode_device.fibaro_device.properties
@ -156,11 +152,13 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
elif "supportedModes" in prop:
op_modes = prop.supportedModes.split(",")
for mode in op_modes:
try:
self._op_mode_to_state[int(mode)] = OPMODES[int(mode)]
self._op_state_to_mode[OPMODES[int(mode)]] = int(mode)
except KeyError:
self._op_mode_to_state[int(mode)] = 'unknown'
mode = int(mode)
if mode in OPMODES_HVAC:
mode_ha = OPMODES_HVAC[mode]
if mode_ha not in self._hvac_support:
self._hvac_support.append(mode_ha)
if mode in OPMODES_PRESET:
self._preset_support.append(OPMODES_PRESET[mode])
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
@ -194,32 +192,70 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
return self._support_flags
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
if self._fan_mode_device is None:
if not self._fan_mode_device:
return None
return list(self._fan_state_to_mode)
return self._fan_support
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
if self._fan_mode_device is None:
if not self._fan_mode_device:
return None
mode = int(self._fan_mode_device.fibaro_device.properties.mode)
return self._fan_mode_to_state[mode]
return FANMODES[mode]
def set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
if self._fan_mode_device is None:
if not self._fan_mode_device:
return
self._fan_mode_device.action(
"setFanMode", self._fan_state_to_mode[fan_mode])
self._fan_mode_device.action("setFanMode", HA_FANMODES[fan_mode])
@property
def current_operation(self):
def fibaro_op_mode(self):
"""Return the operating mode of the device."""
if not self._op_mode_device:
return 6 # Fan only
if "operatingMode" in self._op_mode_device.fibaro_device.properties:
return int(self._op_mode_device.fibaro_device.
properties.operatingMode)
return int(self._op_mode_device.fibaro_device.properties.mode)
@property
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
if self._op_mode_device is None:
return OPMODES_HVAC[self.fibaro_op_mode]
@property
def hvac_modes(self):
"""Return the list of available operation modes."""
if not self._op_mode_device:
return [HVAC_MODE_FAN_ONLY]
return self._hvac_support
def set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
if not self._op_mode_device:
return
if self.preset_mode:
return
if "setOperatingMode" in self._op_mode_device.fibaro_device.actions:
self._op_mode_device.action(
"setOperatingMode", HA_OPMODES_HVAC[hvac_mode])
elif "setMode" in self._op_mode_device.fibaro_device.actions:
self._op_mode_device.action("setMode", HA_OPMODES_HVAC[hvac_mode])
@property
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp.
Requires SUPPORT_PRESET_MODE.
"""
if not self._op_mode_device:
return None
if "operatingMode" in self._op_mode_device.fibaro_device.properties:
@ -227,25 +263,31 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
properties.operatingMode)
else:
mode = int(self._op_mode_device.fibaro_device.properties.mode)
return self._op_mode_to_state.get(mode)
if mode not in OPMODES_PRESET:
return None
return OPMODES_PRESET[mode]
@property
def operation_list(self):
"""Return the list of available operation modes."""
if self._op_mode_device is None:
return None
return list(self._op_state_to_mode)
def preset_modes(self):
"""Return a list of available preset modes.
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
Requires SUPPORT_PRESET_MODE.
"""
if not self._op_mode_device:
return None
return self._preset_support
def set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
if self._op_mode_device is None:
return
if "setOperatingMode" in self._op_mode_device.fibaro_device.actions:
self._op_mode_device.action(
"setOperatingMode", self._op_state_to_mode[operation_mode])
"setOperatingMode", HA_OPMODES_PRESET[preset_mode])
elif "setMode" in self._op_mode_device.fibaro_device.actions:
self._op_mode_device.action(
"setMode", self._op_state_to_mode[operation_mode])
"setMode", HA_OPMODES_PRESET[preset_mode])
@property
def temperature_unit(self):
@ -275,15 +317,6 @@ class FibaroThermostat(FibaroDevice, ClimateDevice):
if temperature is not None:
if "setThermostatSetpoint" in target.fibaro_device.actions:
target.action("setThermostatSetpoint",
self._op_state_to_mode[self.current_operation],
temperature)
self.fibaro_op_mode, temperature)
else:
target.action("setTargetLevel",
temperature)
@property
def is_on(self):
"""Return true if on."""
if self.current_operation == STATE_OFF:
return False
return True
target.action("setTargetLevel", temperature)

View File

@ -12,6 +12,7 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/climate.flexit/
"""
import logging
from typing import List
import voluptuous as vol
from homeassistant.const import (
@ -20,7 +21,7 @@ from homeassistant.const import (
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE,
SUPPORT_FAN_MODE)
SUPPORT_FAN_MODE, HVAC_MODE_COOL)
from homeassistant.components.modbus import (
CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN)
import homeassistant.helpers.config_validation as cv
@ -57,7 +58,7 @@ class Flexit(ClimateDevice):
self._current_temperature = None
self._current_fan_mode = None
self._current_operation = None
self._fan_list = ['Off', 'Low', 'Medium', 'High']
self._fan_modes = ['Off', 'Low', 'Medium', 'High']
self._current_operation = None
self._filter_hours = None
self._filter_alarm = None
@ -81,7 +82,7 @@ class Flexit(ClimateDevice):
self._target_temperature = self.unit.get_target_temp
self._current_temperature = self.unit.get_temp
self._current_fan_mode =\
self._fan_list[self.unit.get_fan_speed]
self._fan_modes[self.unit.get_fan_speed]
self._filter_hours = self.unit.get_filter_hours
# Mechanical heat recovery, 0-100%
self._heat_recovery = self.unit.get_heat_recovery
@ -134,19 +135,27 @@ class Flexit(ClimateDevice):
return self._target_temperature
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
@property
def current_fan_mode(self):
def hvac_modes(self) -> List[str]:
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return [HVAC_MODE_COOL]
@property
def fan_mode(self):
"""Return the fan setting."""
return self._current_fan_mode
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return self._fan_list
return self._fan_modes
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@ -156,4 +165,4 @@ class Flexit(ClimateDevice):
def set_fan_mode(self, fan_mode):
"""Set new fan mode."""
self.unit.set_fan_speed(self._fan_list.index(fan_mode))
self.unit.set_fan_speed(self._fan_modes.index(fan_mode))

View File

@ -5,11 +5,11 @@ import requests
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
ATTR_OPERATION_MODE, STATE_ECO, STATE_HEAT, STATE_MANUAL,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
ATTR_HVAC_MODE, HVAC_MODE_HEAT, PRESET_ECO, PRESET_COMFORT,
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, SUPPORT_PRESET_MODE)
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES, STATE_OFF,
STATE_ON, TEMP_CELSIUS)
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES,
TEMP_CELSIUS)
from . import (
ATTR_STATE_BATTERY_LOW, ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_HOLIDAY_MODE,
@ -18,13 +18,15 @@ from . import (
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
OPERATION_LIST = [STATE_HEAT, STATE_ECO, STATE_OFF, STATE_ON]
OPERATION_LIST = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
MIN_TEMPERATURE = 8
MAX_TEMPERATURE = 28
PRESET_MANUAL = 'manual'
# special temperatures for on/off in Fritz!Box API (modified by pyfritzhome)
ON_API_TEMPERATURE = 127.0
OFF_API_TEMPERATURE = 126.5
@ -98,41 +100,51 @@ class FritzboxThermostat(ClimateDevice):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
if ATTR_OPERATION_MODE in kwargs:
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
self.set_operation_mode(operation_mode)
if ATTR_HVAC_MODE in kwargs:
hvac_mode = kwargs.get(ATTR_HVAC_MODE)
self.set_hvac_mode(hvac_mode)
elif ATTR_TEMPERATURE in kwargs:
temperature = kwargs.get(ATTR_TEMPERATURE)
self._device.set_target_temperature(temperature)
@property
def current_operation(self):
def hvac_mode(self):
"""Return the current operation mode."""
if self._target_temperature == ON_API_TEMPERATURE:
return STATE_ON
if self._target_temperature == OFF_API_TEMPERATURE:
return STATE_OFF
if self._target_temperature == self._comfort_temperature:
return STATE_HEAT
if self._target_temperature == self._eco_temperature:
return STATE_ECO
return STATE_MANUAL
if self._target_temperature == OFF_REPORT_SET_TEMPERATURE:
return HVAC_MODE_OFF
return HVAC_MODE_HEAT
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return OPERATION_LIST
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set new operation mode."""
if operation_mode == STATE_HEAT:
self.set_temperature(temperature=self._comfort_temperature)
elif operation_mode == STATE_ECO:
self.set_temperature(temperature=self._eco_temperature)
elif operation_mode == STATE_OFF:
if hvac_mode == HVAC_MODE_OFF:
self.set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE)
elif operation_mode == STATE_ON:
self.set_temperature(temperature=ON_REPORT_SET_TEMPERATURE)
else:
self.set_temperature(temperature=self._comfort_temperature)
@property
def preset_mode(self):
"""Return current preset mode."""
if self._target_temperature == self._comfort_temperature:
return PRESET_COMFORT
if self._target_temperature == self._eco_temperature:
return PRESET_ECO
def preset_modes(self):
"""Return supported preset modes."""
return [PRESET_ECO, PRESET_COMFORT]
def set_preset_mode(self, preset_mode):
"""Set preset mode."""
if preset_mode == PRESET_COMFORT:
self.set_temperature(temperature=self._comfort_temperature)
elif preset_mode == PRESET_ECO:
self.set_temperature(temperature=self._eco_temperature)
@property
def min_temp(self):

View File

@ -3,7 +3,7 @@
"name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/components/frontend",
"requirements": [
"home-assistant-frontend==20190702.0"
"home-assistant-frontend==20190705.0"
],
"dependencies": [
"api",

View File

@ -4,10 +4,15 @@ import logging
import voluptuous as vol
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
ATTR_PRESET_MODE, CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE,
CURRENT_HVAC_OFF, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF,
PRESET_AWAY, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_NAME, PRECISION_HALVES,
PRECISION_TENTHS, PRECISION_WHOLE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
STATE_OFF, STATE_ON, STATE_UNKNOWN)
ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_NAME, EVENT_HOMEASSISTANT_START,
PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE, SERVICE_TURN_OFF,
SERVICE_TURN_ON, STATE_ON, STATE_UNKNOWN)
from homeassistant.core import DOMAIN as HA_DOMAIN, callback
from homeassistant.helpers import condition
import homeassistant.helpers.config_validation as cv
@ -15,12 +20,6 @@ from homeassistant.helpers.event import (
async_track_state_change, async_track_time_interval)
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
ATTR_AWAY_MODE, ATTR_OPERATION_MODE, STATE_AUTO, STATE_COOL, STATE_HEAT,
STATE_IDLE, SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE)
_LOGGER = logging.getLogger(__name__)
DEFAULT_TOLERANCE = 0.3
@ -36,11 +35,10 @@ CONF_MIN_DUR = 'min_cycle_duration'
CONF_COLD_TOLERANCE = 'cold_tolerance'
CONF_HOT_TOLERANCE = 'hot_tolerance'
CONF_KEEP_ALIVE = 'keep_alive'
CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode'
CONF_INITIAL_HVAC_MODE = 'initial_hvac_mode'
CONF_AWAY_TEMP = 'away_temp'
CONF_PRECISION = 'precision'
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_OPERATION_MODE)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HEATER): cv.entity_id,
@ -57,8 +55,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
vol.Optional(CONF_KEEP_ALIVE): vol.All(
cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_INITIAL_OPERATION_MODE):
vol.In([STATE_AUTO, STATE_OFF]),
vol.Optional(CONF_INITIAL_HVAC_MODE):
vol.In([HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF]),
vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float),
vol.Optional(CONF_PRECISION): vol.In(
[PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]),
@ -79,77 +77,78 @@ async def async_setup_platform(hass, config, async_add_entities,
cold_tolerance = config.get(CONF_COLD_TOLERANCE)
hot_tolerance = config.get(CONF_HOT_TOLERANCE)
keep_alive = config.get(CONF_KEEP_ALIVE)
initial_operation_mode = config.get(CONF_INITIAL_OPERATION_MODE)
initial_hvac_mode = config.get(CONF_INITIAL_HVAC_MODE)
away_temp = config.get(CONF_AWAY_TEMP)
precision = config.get(CONF_PRECISION)
unit = hass.config.units.temperature_unit
async_add_entities([GenericThermostat(
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
target_temp, ac_mode, min_cycle_duration, cold_tolerance,
hot_tolerance, keep_alive, initial_operation_mode, away_temp,
precision)])
hot_tolerance, keep_alive, initial_hvac_mode, away_temp,
precision, unit)])
class GenericThermostat(ClimateDevice, RestoreEntity):
"""Representation of a Generic Thermostat device."""
def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
def __init__(self, name, heater_entity_id, sensor_entity_id,
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
cold_tolerance, hot_tolerance, keep_alive,
initial_operation_mode, away_temp, precision):
initial_hvac_mode, away_temp, precision, unit):
"""Initialize the thermostat."""
self.hass = hass
self._name = name
self.heater_entity_id = heater_entity_id
self.sensor_entity_id = sensor_entity_id
self.ac_mode = ac_mode
self.min_cycle_duration = min_cycle_duration
self._cold_tolerance = cold_tolerance
self._hot_tolerance = hot_tolerance
self._keep_alive = keep_alive
self._initial_operation_mode = initial_operation_mode
self._saved_target_temp = target_temp if target_temp is not None \
else away_temp
self._hvac_mode = initial_hvac_mode
self._saved_target_temp = target_temp or away_temp
self._temp_precision = precision
if self.ac_mode:
self._current_operation = STATE_COOL
self._operation_list = [STATE_COOL, STATE_OFF]
self._hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
else:
self._current_operation = STATE_HEAT
self._operation_list = [STATE_HEAT, STATE_OFF]
if initial_operation_mode == STATE_OFF:
self._enabled = False
self._current_operation = STATE_OFF
else:
self._enabled = True
self._hvac_list = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
self._active = False
self._cur_temp = None
self._temp_lock = asyncio.Lock()
self._min_temp = min_temp
self._max_temp = max_temp
self._target_temp = target_temp
self._unit = hass.config.units.temperature_unit
self._unit = unit
self._support_flags = SUPPORT_FLAGS
if away_temp is not None:
self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE
if away_temp:
self._support_flags = SUPPORT_FLAGS | SUPPORT_PRESET_MODE
self._away_temp = away_temp
self._is_away = False
async_track_state_change(
hass, sensor_entity_id, self._async_sensor_changed)
async_track_state_change(
hass, heater_entity_id, self._async_switch_changed)
if self._keep_alive:
async_track_time_interval(
hass, self._async_control_heating, self._keep_alive)
sensor_state = hass.states.get(sensor_entity_id)
if sensor_state and sensor_state.state != STATE_UNKNOWN:
self._async_update_temp(sensor_state)
async def async_added_to_hass(self):
"""Run when entity about to be added."""
await super().async_added_to_hass()
# Add listener
async_track_state_change(
self.hass, self.sensor_entity_id, self._async_sensor_changed)
async_track_state_change(
self.hass, self.heater_entity_id, self._async_switch_changed)
if self._keep_alive:
async_track_time_interval(
self.hass, self._async_control_heating, self._keep_alive)
@callback
def _async_startup(event):
"""Init on startup."""
sensor_state = self.hass.states.get(self.sensor_entity_id)
if sensor_state and sensor_state.state != STATE_UNKNOWN:
self._async_update_temp(sensor_state)
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, _async_startup)
# Check If we have an old state
old_state = await self.async_get_last_state()
if old_state is not None:
@ -166,14 +165,10 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
else:
self._target_temp = float(
old_state.attributes[ATTR_TEMPERATURE])
if old_state.attributes.get(ATTR_AWAY_MODE) is not None:
self._is_away = str(
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON
if (self._initial_operation_mode is None and
old_state.attributes[ATTR_OPERATION_MODE] is not None):
self._current_operation = \
old_state.attributes[ATTR_OPERATION_MODE]
self._enabled = self._current_operation != STATE_OFF
if old_state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY:
self._is_away = True
if not self._hvac_mode and old_state.state:
self._hvac_mode = old_state.state
else:
# No previous state, try and restore defaults
@ -185,14 +180,9 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
_LOGGER.warning("No previously saved temperature, setting to %s",
self._target_temp)
@property
def state(self):
"""Return the current state."""
if self._is_device_active:
return self.current_operation
if self._enabled:
return STATE_IDLE
return STATE_OFF
# Set default state to off
if not self._hvac_mode:
self._hvac_mode = HVAC_MODE_OFF
@property
def should_poll(self):
@ -222,9 +212,23 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
return self._cur_temp
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation."""
return self._current_operation
return self._hvac_mode
@property
def hvac_action(self):
"""Return the current running hvac operation if supported.
Need to be one of CURRENT_HVAC_*.
"""
if self._hvac_mode == HVAC_MODE_OFF:
return CURRENT_HVAC_OFF
if not self._is_device_active:
return CURRENT_HVAC_IDLE
if self.ac_mode:
return CURRENT_HVAC_COOL
return CURRENT_HVAC_HEAT
@property
def target_temperature(self):
@ -232,39 +236,42 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
return self._target_temp
@property
def operation_list(self):
def hvac_modes(self):
"""List of available operation modes."""
return self._operation_list
return self._hvac_list
async def async_set_operation_mode(self, operation_mode):
"""Set operation mode."""
if operation_mode == STATE_HEAT:
self._current_operation = STATE_HEAT
self._enabled = True
@property
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp."""
if self._is_away:
return PRESET_AWAY
return None
@property
def preset_modes(self):
"""Return a list of available preset modes."""
if self._away_temp:
return [PRESET_AWAY]
return None
async def async_set_hvac_mode(self, hvac_mode):
"""Set hvac mode."""
if hvac_mode == HVAC_MODE_HEAT:
self._hvac_mode = HVAC_MODE_HEAT
await self._async_control_heating(force=True)
elif operation_mode == STATE_COOL:
self._current_operation = STATE_COOL
self._enabled = True
elif hvac_mode == HVAC_MODE_COOL:
self._hvac_mode = HVAC_MODE_COOL
await self._async_control_heating(force=True)
elif operation_mode == STATE_OFF:
self._current_operation = STATE_OFF
self._enabled = False
elif hvac_mode == HVAC_MODE_OFF:
self._hvac_mode = HVAC_MODE_OFF
if self._is_device_active:
await self._async_heater_turn_off()
else:
_LOGGER.error("Unrecognized operation mode: %s", operation_mode)
_LOGGER.error("Unrecognized hvac mode: %s", hvac_mode)
return
# Ensure we update the current operation after changing the mode
self.schedule_update_ha_state()
async def async_turn_on(self):
"""Turn thermostat on."""
await self.async_set_operation_mode(self.operation_list[0])
async def async_turn_off(self):
"""Turn thermostat off."""
await self.async_set_operation_mode(STATE_OFF)
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
@ -326,7 +333,7 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
"Generic thermostat active. %s, %s",
self._cur_temp, self._target_temp)
if not self._active or not self._enabled:
if not self._active or self._hvac_mode == HVAC_MODE_OFF:
return
if not force and time is None:
@ -338,7 +345,7 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
if self._is_device_active:
current_state = STATE_ON
else:
current_state = STATE_OFF
current_state = HVAC_MODE_OFF
long_enough = condition.state(
self.hass, self.heater_entity_id, current_state,
self.min_cycle_duration)
@ -387,26 +394,19 @@ class GenericThermostat(ClimateDevice, RestoreEntity):
data = {ATTR_ENTITY_ID: self.heater_entity_id}
await self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_OFF, data)
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._is_away
async def async_set_preset_mode(self, preset_mode: str):
"""Set new preset mode.
async def async_turn_away_mode_on(self):
"""Turn away mode on by setting it on away hold indefinitely."""
if self._is_away:
return
self._is_away = True
self._saved_target_temp = self._target_temp
self._target_temp = self._away_temp
await self._async_control_heating(force=True)
await self.async_update_ha_state()
This method must be run in the event loop and returns a coroutine.
"""
if preset_mode == PRESET_AWAY and not self._is_away:
self._is_away = True
self._saved_target_temp = self._target_temp
self._target_temp = self._away_temp
await self._async_control_heating(force=True)
elif not preset_mode and self._is_away:
self._is_away = False
self._target_temp = self._saved_target_temp
await self._async_control_heating(force=True)
async def async_turn_away_mode_off(self):
"""Turn away off."""
if not self._is_away:
return
self._is_away = False
self._target_temp = self._saved_target_temp
await self._async_control_heating(force=True)
await self.async_update_ha_state()

View File

@ -1,12 +1,12 @@
"""Support for Genius Hub climate devices."""
import logging
from typing import Any, Awaitable, Dict, Optional, List
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_ECO, STATE_HEAT, STATE_MANUAL,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_ON_OFF)
from homeassistant.const import (
ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS)
HVAC_MODE_OFF, HVAC_MODE_HEAT, PRESET_BOOST, PRESET_ACTIVITY,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -14,36 +14,25 @@ from . import DOMAIN
_LOGGER = logging.getLogger(__name__)
ATTR_DURATION = 'duration'
GH_ZONES = ['radiator']
GH_SUPPORT_FLAGS = \
SUPPORT_TARGET_TEMPERATURE | \
SUPPORT_ON_OFF | \
SUPPORT_OPERATION_MODE
GH_MAX_TEMP = 28.0
GH_MIN_TEMP = 4.0
# Genius Hub Zones support only Off, Override/Boost, Footprint & Timer modes
HA_OPMODE_TO_GH = {
STATE_OFF: 'off',
STATE_AUTO: 'timer',
STATE_ECO: 'footprint',
STATE_MANUAL: 'override',
}
GH_STATE_TO_HA = {
'off': STATE_OFF,
'timer': STATE_AUTO,
'footprint': STATE_ECO,
'away': None,
'override': STATE_MANUAL,
'early': STATE_HEAT,
'test': None,
'linked': None,
'other': None,
}
# temperature is repeated here, as it gives access to high-precision temps
GH_STATE_ATTRS = ['temperature', 'type', 'occupied', 'override']
GH_STATE_ATTRS = ['mode', 'temperature', 'type', 'occupied', 'override']
# GeniusHub Zones support: Off, Timer, Override/Boost, Footprint & Linked modes
HA_HVAC_TO_GH = {
HVAC_MODE_OFF: 'off',
HVAC_MODE_HEAT: 'timer'
}
GH_HVAC_TO_HA = {v: k for k, v in HA_HVAC_TO_GH.items()}
HA_PRESET_TO_GH = {
PRESET_ACTIVITY: 'footprint',
PRESET_BOOST: 'override'
}
GH_PRESET_TO_HA = {v: k for k, v in HA_PRESET_TO_GH.items()}
async def async_setup_platform(hass, hass_config, async_add_entities,
@ -63,28 +52,26 @@ class GeniusClimateZone(ClimateDevice):
self._client = client
self._zone = zone
# Only some zones have movement detectors, which allows footprint mode
op_list = list(HA_OPMODE_TO_GH)
if not hasattr(self._zone, 'occupied'):
op_list.remove(STATE_ECO)
self._operation_list = op_list
self._supported_features = GH_SUPPORT_FLAGS
if hasattr(self._zone, 'occupied'): # has a movement sensor
self._preset_modes = list(HA_PRESET_TO_GH)
else:
self._preset_modes = [PRESET_BOOST]
async def async_added_to_hass(self):
async def async_added_to_hass(self) -> Awaitable[None]:
"""Run when entity about to be added."""
async_dispatcher_connect(self.hass, DOMAIN, self._refresh)
@callback
def _refresh(self):
def _refresh(self) -> None:
self.async_schedule_update_ha_state(force_refresh=True)
@property
def name(self):
def name(self) -> str:
"""Return the name of the climate device."""
return self._zone.name
@property
def device_state_attributes(self):
def device_state_attributes(self) -> Dict[str, Any]:
"""Return the device state attributes."""
tmp = self._zone.__dict__.items()
return {'status': {k: v for k, v in tmp if k in GH_STATE_ATTRS}}
@ -95,72 +82,69 @@ class GeniusClimateZone(ClimateDevice):
return False
@property
def icon(self):
def icon(self) -> str:
"""Return the icon to use in the frontend UI."""
return "mdi:radiator"
@property
def current_temperature(self):
def current_temperature(self) -> Optional[float]:
"""Return the current temperature."""
return self._zone.temperature
@property
def target_temperature(self):
def target_temperature(self) -> Optional[float]:
"""Return the temperature we try to reach."""
return self._zone.setpoint
@property
def min_temp(self):
def min_temp(self) -> float:
"""Return max valid temperature that can be set."""
return GH_MIN_TEMP
return 4.0
@property
def max_temp(self):
def max_temp(self) -> float:
"""Return max valid temperature that can be set."""
return GH_MAX_TEMP
return 28.0
@property
def temperature_unit(self):
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def supported_features(self):
def supported_features(self) -> int:
"""Return the list of supported features."""
return self._supported_features
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
@property
def operation_list(self):
"""Return the list of available operation modes."""
return self._operation_list
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode."""
return GH_HVAC_TO_HA.get(self._zone.mode, HVAC_MODE_HEAT)
@property
def current_operation(self):
"""Return the current operation mode."""
return GH_STATE_TO_HA[self._zone.mode]
def hvac_modes(self) -> List[str]:
"""Return the list of available hvac operation modes."""
return list(HA_HVAC_TO_GH)
@property
def is_on(self):
"""Return True if the device is on."""
return self._zone.mode != HA_OPMODE_TO_GH[STATE_OFF]
def preset_mode(self) -> Optional[str]:
"""Return the current preset mode, e.g., home, away, temp."""
return GH_PRESET_TO_HA.get(self._zone.mode)
async def async_set_operation_mode(self, operation_mode):
"""Set a new operation mode for this zone."""
await self._zone.set_mode(HA_OPMODE_TO_GH[operation_mode])
@property
def preset_modes(self) -> Optional[List[str]]:
"""Return a list of available preset modes."""
return self._preset_modes
async def async_set_temperature(self, **kwargs):
async def async_set_temperature(self, **kwargs) -> Awaitable[None]:
"""Set a new target temperature for this zone."""
await self._zone.set_override(kwargs.get(ATTR_TEMPERATURE), 3600)
await self._zone.set_override(kwargs[ATTR_TEMPERATURE],
kwargs.get(ATTR_DURATION, 3600))
async def async_turn_on(self):
"""Turn on this heating zone.
async def async_set_hvac_mode(self, hvac_mode: str) -> Awaitable[None]:
"""Set a new hvac mode."""
await self._zone.set_mode(HA_HVAC_TO_GH.get(hvac_mode))
Set a Zone to Footprint mode if they have a Room sensor, and to Timer
mode otherwise.
"""
mode = STATE_ECO if hasattr(self._zone, 'occupied') else STATE_AUTO
await self._zone.set_mode(HA_OPMODE_TO_GH[mode])
async def async_turn_off(self):
"""Turn off this heating zone (i.e. to frost protect)."""
await self._zone.set_mode(HA_OPMODE_TO_GH[STATE_OFF])
async def async_set_preset_mode(self, preset_mode: str) -> Awaitable[None]:
"""Set a new preset mode."""
await self._zone.set_mode(HA_PRESET_TO_GH.get(preset_mode, 'timer'))

View File

@ -537,26 +537,59 @@ class TemperatureSettingTrait(_Trait):
]
# We do not support "on" as we are unable to know how to restore
# the last mode.
hass_to_google = {
climate.STATE_HEAT: 'heat',
climate.STATE_COOL: 'cool',
STATE_OFF: 'off',
climate.STATE_AUTO: 'heatcool',
climate.STATE_FAN_ONLY: 'fan-only',
climate.STATE_DRY: 'dry',
climate.STATE_ECO: 'eco'
hvac_to_google = {
climate.HVAC_MODE_HEAT: 'heat',
climate.HVAC_MODE_COOL: 'cool',
climate.HVAC_MODE_OFF: 'off',
climate.HVAC_MODE_AUTO: 'auto',
climate.HVAC_MODE_HEAT_COOL: 'heatcool',
climate.HVAC_MODE_FAN_ONLY: 'fan-only',
climate.HVAC_MODE_DRY: 'dry',
}
google_to_hass = {value: key for key, value in hass_to_google.items()}
google_to_hvac = {value: key for key, value in hvac_to_google.items()}
preset_to_google = {
climate.PRESET_ECO: 'eco'
}
google_to_preset = {value: key for key, value in preset_to_google.items()}
@staticmethod
def supported(domain, features, device_class):
"""Test if state is supported."""
if domain == climate.DOMAIN:
return features & climate.SUPPORT_OPERATION_MODE
return True
return (domain == sensor.DOMAIN
and device_class == sensor.DEVICE_CLASS_TEMPERATURE)
@property
def climate_google_modes(self):
"""Return supported Google modes."""
modes = []
attrs = self.state.attributes
for mode in attrs.get(climate.ATTR_HVAC_MODES, []):
google_mode = self.hvac_to_google.get(mode)
if google_mode and google_mode not in modes:
modes.append(google_mode)
for preset in attrs.get(climate.ATTR_PRESET_MODES, []):
google_mode = self.preset_to_google.get(preset)
if google_mode and google_mode not in modes:
modes.append(google_mode)
return modes
@property
def climate_on_mode(self):
"""Return the mode that should be considered on."""
modes = [m for m in self.climate_google_modes if m != 'off']
if len(modes) == 1:
return modes[0]
return None
def sync_attributes(self):
"""Return temperature point and modes attributes for a sync request."""
response = {}
@ -571,18 +604,10 @@ class TemperatureSettingTrait(_Trait):
response["queryOnlyTemperatureSetting"] = True
elif domain == climate.DOMAIN:
modes = []
supported = attrs.get(ATTR_SUPPORTED_FEATURES)
if supported & climate.SUPPORT_ON_OFF != 0:
modes.append(STATE_OFF)
modes.append(STATE_ON)
if supported & climate.SUPPORT_OPERATION_MODE != 0:
for mode in attrs.get(climate.ATTR_OPERATION_LIST, []):
google_mode = self.hass_to_google.get(mode)
if google_mode and google_mode not in modes:
modes.append(google_mode)
modes = self.climate_google_modes
on_mode = self.climate_on_mode
if on_mode is not None:
modes.append('on')
response['availableThermostatModes'] = ','.join(modes)
return response
@ -606,17 +631,14 @@ class TemperatureSettingTrait(_Trait):
), 1)
elif domain == climate.DOMAIN:
operation = attrs.get(climate.ATTR_OPERATION_MODE)
supported = attrs.get(ATTR_SUPPORTED_FEATURES)
operation = self.state.state
preset = attrs.get(climate.ATTR_PRESET_MODE)
supported = attrs.get(ATTR_SUPPORTED_FEATURES, 0)
if (supported & climate.SUPPORT_ON_OFF
and self.state.state == STATE_OFF):
response['thermostatMode'] = 'off'
elif (supported & climate.SUPPORT_OPERATION_MODE
and operation in self.hass_to_google):
response['thermostatMode'] = self.hass_to_google[operation]
elif supported & climate.SUPPORT_ON_OFF:
response['thermostatMode'] = 'on'
if preset in self.preset_to_google:
response['thermostatMode'] = self.preset_to_google[preset]
else:
response['thermostatMode'] = self.hvac_to_google.get(operation)
current_temp = attrs.get(climate.ATTR_CURRENT_TEMPERATURE)
if current_temp is not None:
@ -631,9 +653,9 @@ class TemperatureSettingTrait(_Trait):
if current_humidity is not None:
response['thermostatHumidityAmbient'] = current_humidity
if operation == climate.STATE_AUTO:
if (supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH and
supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW):
if operation in (climate.HVAC_MODE_AUTO,
climate.HVAC_MODE_HEAT_COOL):
if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE:
response['thermostatTemperatureSetpointHigh'] = \
round(temp_util.convert(
attrs[climate.ATTR_TARGET_TEMP_HIGH],
@ -725,8 +747,7 @@ class TemperatureSettingTrait(_Trait):
ATTR_ENTITY_ID: self.state.entity_id,
}
if(supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH
and supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW):
if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE:
svc_data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high
svc_data[climate.ATTR_TARGET_TEMP_LOW] = temp_low
else:
@ -740,22 +761,40 @@ class TemperatureSettingTrait(_Trait):
target_mode = params['thermostatMode']
supported = self.state.attributes.get(ATTR_SUPPORTED_FEATURES)
if (target_mode in [STATE_ON, STATE_OFF] and
supported & climate.SUPPORT_ON_OFF):
if target_mode in self.google_to_preset:
await self.hass.services.async_call(
climate.DOMAIN,
(SERVICE_TURN_ON
if target_mode == STATE_ON
else SERVICE_TURN_OFF),
{ATTR_ENTITY_ID: self.state.entity_id},
blocking=True, context=data.context)
elif supported & climate.SUPPORT_OPERATION_MODE:
await self.hass.services.async_call(
climate.DOMAIN, climate.SERVICE_SET_OPERATION_MODE, {
ATTR_ENTITY_ID: self.state.entity_id,
climate.ATTR_OPERATION_MODE:
self.google_to_hass[target_mode],
}, blocking=True, context=data.context)
climate.DOMAIN, climate.SERVICE_SET_PRESET_MODE,
{
climate.ATTR_PRESET_MODE:
self.google_to_preset[target_mode],
ATTR_ENTITY_ID: self.state.entity_id
},
blocking=True, context=data.context
)
return
if target_mode == 'on':
# When targetting 'on', we're going to try best effort.
modes = [m for m in self.climate_google_modes
if m != climate.HVAC_MODE_OFF]
if len(modes) == 1:
target_mode = modes[0]
elif 'auto' in modes:
target_mode = 'auto'
elif 'heatcool' in modes:
target_mode = 'heatcool'
else:
raise SmartHomeError(
ERR_FUNCTION_NOT_SUPPORTED,
"Unable to translate 'on' to a HVAC mode.")
await self.hass.services.async_call(
climate.DOMAIN, climate.SERVICE_SET_HVAC_MODE, {
ATTR_ENTITY_ID: self.state.entity_id,
climate.ATTR_HVAC_MODE:
self.google_to_hvac[target_mode],
}, blocking=True, context=data.context)
@register_trait

View File

@ -39,11 +39,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
serport = connection.connection(ipaddress, port)
serport.open()
for tstat in tstats.values():
add_entities([
HeatmiserV3Thermostat(
heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport)
])
add_entities([
HeatmiserV3Thermostat(
heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport)
for tstat in tstats.values()], True)
class HeatmiserV3Thermostat(ClimateDevice):
@ -54,11 +53,10 @@ class HeatmiserV3Thermostat(ClimateDevice):
self.heatmiser = heatmiser
self.serport = serport
self._current_temperature = None
self._target_temperature = None
self._name = name
self._id = device
self.dcb = None
self.update()
self._target_temperature = int(self.dcb.get('roomset'))
@property
def supported_features(self):
@ -78,13 +76,6 @@ class HeatmiserV3Thermostat(ClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
if self.dcb is not None:
low = self.dcb.get('floortemplow ')
high = self.dcb.get('floortemphigh')
temp = (high * 256 + low) / 10.0
self._current_temperature = temp
else:
self._current_temperature = None
return self._current_temperature
@property
@ -95,16 +86,17 @@ class HeatmiserV3Thermostat(ClimateDevice):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self.heatmiser.hmSendAddress(
self._id,
18,
temperature,
1,
self.serport)
self._target_temperature = temperature
def update(self):
"""Get the latest data."""
self.dcb = self.heatmiser.hmReadAddress(self._id, 'prt', self.serport)
low = self.dcb.get('floortemplow ')
high = self.dcb.get('floortemphigh')
self._current_temperature = (high * 256 + low) / 10.0
self._target_temperature = int(self.dcb.get('roomset'))

View File

@ -1,6 +1,7 @@
"""Support for the Hive devices."""
import logging
from pyhiveapi import Pyhiveapi
import voluptuous as vol
from homeassistant.const import (
@ -45,8 +46,6 @@ class HiveSession:
def setup(hass, config):
"""Set up the Hive Component."""
from pyhiveapi import Pyhiveapi
session = HiveSession()
session.core = Pyhiveapi()

View File

@ -1,39 +1,41 @@
"""Support for the Hive climate devices."""
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_HEAT, SUPPORT_AUX_HEAT, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS)
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_BOOST,
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import DATA_HIVE, DOMAIN
HIVE_TO_HASS_STATE = {
'SCHEDULE': STATE_AUTO,
'MANUAL': STATE_HEAT,
'ON': STATE_ON,
'OFF': STATE_OFF,
'SCHEDULE': HVAC_MODE_AUTO,
'MANUAL': HVAC_MODE_HEAT,
'OFF': HVAC_MODE_OFF,
}
HASS_TO_HIVE_STATE = {
STATE_AUTO: 'SCHEDULE',
STATE_HEAT: 'MANUAL',
STATE_ON: 'ON',
STATE_OFF: 'OFF',
HVAC_MODE_AUTO: 'SCHEDULE',
HVAC_MODE_HEAT: 'MANUAL',
HVAC_MODE_OFF: 'OFF',
}
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_OPERATION_MODE |
SUPPORT_AUX_HEAT)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
SUPPORT_HVAC = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF]
SUPPORT_PRESET = [PRESET_BOOST]
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Hive climate devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
if discovery_info["HA_DeviceType"] != "Heating":
return
add_entities([HiveClimateEntity(session, discovery_info)])
session = hass.data.get(DATA_HIVE)
climate = HiveClimateEntity(session, discovery_info)
add_entities([climate])
session.entities.append(climate)
class HiveClimateEntity(ClimateDevice):
@ -43,21 +45,11 @@ class HiveClimateEntity(ClimateDevice):
"""Initialize the Climate device."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
if self.device_type == "Heating":
self.thermostat_node_id = hivedevice["Thermostat_NodeID"]
self.thermostat_node_id = hivedevice["Thermostat_NodeID"]
self.session = hivesession
self.attributes = {}
self.data_updatesource = '{}.{}'.format(
self.device_type, self.node_id)
self._unique_id = '{}-{}'.format(self.node_id, self.device_type)
if self.device_type == "Heating":
self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF]
elif self.device_type == "HotWater":
self.modes = [STATE_AUTO, STATE_ON, STATE_OFF]
self.session.entities.append(self)
self.data_updatesource = 'Heating.{}'.format(self.node_id)
self._unique_id = '{}-Heating'.format(self.node_id)
@property
def unique_id(self):
@ -81,19 +73,15 @@ class HiveClimateEntity(ClimateDevice):
def handle_update(self, updatesource):
"""Handle the new update request."""
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
if 'Heating.{}'.format(self.node_id) not in updatesource:
self.schedule_update_ha_state()
@property
def name(self):
"""Return the name of the Climate device."""
friendly_name = "Climate Device"
if self.device_type == "Heating":
friendly_name = "Heating"
if self.node_name is not None:
friendly_name = '{} {}'.format(self.node_name, friendly_name)
elif self.device_type == "HotWater":
friendly_name = "Hot Water"
friendly_name = "Heating"
if self.node_name is not None:
friendly_name = '{} {}'.format(self.node_name, friendly_name)
return friendly_name
@property
@ -101,6 +89,22 @@ class HiveClimateEntity(ClimateDevice):
"""Show Device Attributes."""
return self.attributes
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return SUPPORT_HVAC
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
return HIVE_TO_HASS_STATE[self.session.heating.get_mode(self.node_id)]
@property
def temperature_unit(self):
"""Return the unit of measurement."""
@ -109,48 +113,39 @@ class HiveClimateEntity(ClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
if self.device_type == "Heating":
return self.session.heating.current_temperature(self.node_id)
return self.session.heating.current_temperature(self.node_id)
@property
def target_temperature(self):
"""Return the target temperature."""
if self.device_type == "Heating":
return self.session.heating.get_target_temperature(self.node_id)
return self.session.heating.get_target_temperature(self.node_id)
@property
def min_temp(self):
"""Return minimum temperature."""
if self.device_type == "Heating":
return self.session.heating.min_temperature(self.node_id)
return self.session.heating.min_temperature(self.node_id)
@property
def max_temp(self):
"""Return the maximum temperature."""
if self.device_type == "Heating":
return self.session.heating.max_temperature(self.node_id)
return self.session.heating.max_temperature(self.node_id)
@property
def operation_list(self):
"""List of the operation modes."""
return self.modes
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp."""
if self.session.heating.get_boost(self.node_id) == "ON":
return PRESET_BOOST
return None
@property
def current_operation(self):
"""Return current mode."""
if self.device_type == "Heating":
currentmode = self.session.heating.get_mode(self.node_id)
elif self.device_type == "HotWater":
currentmode = self.session.hotwater.get_mode(self.node_id)
return HIVE_TO_HASS_STATE.get(currentmode)
def preset_modes(self):
"""Return a list of available preset modes."""
return SUPPORT_PRESET
def set_operation_mode(self, operation_mode):
"""Set new Heating mode."""
new_mode = HASS_TO_HIVE_STATE.get(operation_mode)
if self.device_type == "Heating":
self.session.heating.set_mode(self.node_id, new_mode)
elif self.device_type == "HotWater":
self.session.hotwater.set_mode(self.node_id, new_mode)
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
new_mode = HASS_TO_HIVE_STATE[hvac_mode]
self.session.heating.set_mode(self.node_id, new_mode)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
@ -159,55 +154,29 @@ class HiveClimateEntity(ClimateDevice):
"""Set new target temperature."""
new_temperature = kwargs.get(ATTR_TEMPERATURE)
if new_temperature is not None:
if self.device_type == "Heating":
self.session.heating.set_target_temperature(self.node_id,
new_temperature)
self.session.heating.set_target_temperature(
self.node_id, new_temperature)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
@property
def is_aux_heat_on(self):
"""Return true if auxiliary heater is on."""
boost_status = None
if self.device_type == "Heating":
boost_status = self.session.heating.get_boost(self.node_id)
elif self.device_type == "HotWater":
boost_status = self.session.hotwater.get_boost(self.node_id)
return boost_status == "ON"
def set_preset_mode(self, preset_mode) -> None:
"""Set new preset mode."""
if preset_mode is None and self.preset_mode == PRESET_BOOST:
self.session.heating.turn_boost_off(self.node_id)
def turn_aux_heat_on(self):
"""Turn auxiliary heater on."""
target_boost_time = 30
if self.device_type == "Heating":
elif preset_mode == PRESET_BOOST:
curtemp = self.session.heating.current_temperature(self.node_id)
curtemp = round(curtemp * 2) / 2
target_boost_temperature = curtemp + 0.5
self.session.heating.turn_boost_on(self.node_id,
target_boost_time,
target_boost_temperature)
elif self.device_type == "HotWater":
self.session.hotwater.turn_boost_on(self.node_id,
target_boost_time)
temperature = curtemp + 0.5
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def turn_aux_heat_off(self):
"""Turn auxiliary heater off."""
if self.device_type == "Heating":
self.session.heating.turn_boost_off(self.node_id)
elif self.device_type == "HotWater":
self.session.hotwater.turn_boost_off(self.node_id)
self.session.heating.turn_boost_on(self.node_id, 30, temperature)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def update(self):
"""Update all Node data from Hive."""
node = self.node_id
if self.device_type == "Heating":
node = self.thermostat_node_id
self.session.core.update_data(self.node_id)
self.attributes = self.session.attributes.state_attributes(node)
self.attributes = self.session.attributes.state_attributes(
self.thermostat_node_id)

View File

@ -4,21 +4,20 @@ import logging
from pyhap.const import CATEGORY_THERMOSTAT
from homeassistant.components.climate.const import (
ATTR_CURRENT_TEMPERATURE, ATTR_MAX_TEMP, ATTR_MIN_TEMP,
ATTR_OPERATION_LIST, ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_STEP,
DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP,
DOMAIN as DOMAIN_CLIMATE,
SERVICE_SET_OPERATION_MODE as SERVICE_SET_OPERATION_MODE_THERMOSTAT,
SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT, STATE_AUTO,
STATE_COOL, STATE_HEAT, SUPPORT_ON_OFF, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW)
ATTR_CURRENT_TEMPERATURE, ATTR_HVAC_ACTIONS, ATTR_HVAC_MODE, ATTR_MAX_TEMP,
ATTR_MIN_TEMP, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
ATTR_TARGET_TEMP_STEP, CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT,
CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP,
DOMAIN as DOMAIN_CLIMATE, HVAC_MODE_COOL, HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF,
SERVICE_SET_HVAC_MODE as SERVICE_SET_HVAC_MODE_THERMOSTAT,
SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT,
SUPPORT_TARGET_TEMPERATURE_RANGE)
from homeassistant.components.water_heater import (
DOMAIN as DOMAIN_WATER_HEATER,
SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_WATER_HEATER)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE,
SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, TEMP_CELSIUS,
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, TEMP_CELSIUS,
TEMP_FAHRENHEIT)
from . import TYPES
@ -36,12 +35,16 @@ _LOGGER = logging.getLogger(__name__)
UNIT_HASS_TO_HOMEKIT = {TEMP_CELSIUS: 0, TEMP_FAHRENHEIT: 1}
UNIT_HOMEKIT_TO_HASS = {c: s for s, c in UNIT_HASS_TO_HOMEKIT.items()}
HC_HASS_TO_HOMEKIT = {STATE_OFF: 0, STATE_HEAT: 1,
STATE_COOL: 2, STATE_AUTO: 3}
HC_HASS_TO_HOMEKIT = {HVAC_MODE_OFF: 0, HVAC_MODE_HEAT: 1,
HVAC_MODE_COOL: 2, HVAC_MODE_HEAT_COOL: 3}
HC_HOMEKIT_TO_HASS = {c: s for s, c in HC_HASS_TO_HOMEKIT.items()}
SUPPORT_TEMP_RANGE = SUPPORT_TARGET_TEMPERATURE_LOW | \
SUPPORT_TARGET_TEMPERATURE_HIGH
HC_HASS_TO_HOMEKIT_ACTION = {
CURRENT_HVAC_OFF: 0,
CURRENT_HVAC_IDLE: 0,
CURRENT_HVAC_HEAT: 1,
CURRENT_HVAC_COOL: 2,
}
@TYPES.register('Thermostat')
@ -56,7 +59,6 @@ class Thermostat(HomeAccessory):
self._flag_temperature = False
self._flag_coolingthresh = False
self._flag_heatingthresh = False
self.support_power_state = False
min_temp, max_temp = self.get_temperature_range()
temp_step = self.hass.states.get(self.entity_id) \
.attributes.get(ATTR_TARGET_TEMP_STEP, 0.5)
@ -65,9 +67,7 @@ class Thermostat(HomeAccessory):
self.chars = []
features = self.hass.states.get(self.entity_id) \
.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if features & SUPPORT_ON_OFF:
self.support_power_state = True
if features & SUPPORT_TEMP_RANGE:
if features & SUPPORT_TARGET_TEMPERATURE_RANGE:
self.chars.extend((CHAR_COOLING_THRESHOLD_TEMPERATURE,
CHAR_HEATING_THRESHOLD_TEMPERATURE))
@ -133,17 +133,13 @@ class Thermostat(HomeAccessory):
_LOGGER.debug('%s: Set heat-cool to %d', self.entity_id, value)
self._flag_heat_cool = True
hass_value = HC_HOMEKIT_TO_HASS[value]
if self.support_power_state is True:
params = {ATTR_ENTITY_ID: self.entity_id}
if hass_value == STATE_OFF:
self.call_service(DOMAIN_CLIMATE, SERVICE_TURN_OFF, params)
return
self.call_service(DOMAIN_CLIMATE, SERVICE_TURN_ON, params)
params = {ATTR_ENTITY_ID: self.entity_id,
ATTR_OPERATION_MODE: hass_value}
params = {
ATTR_ENTITY_ID: self.entity_id,
ATTR_HVAC_MODE: hass_value
}
self.call_service(
DOMAIN_CLIMATE, SERVICE_SET_OPERATION_MODE_THERMOSTAT,
params, hass_value)
DOMAIN_CLIMATE, SERVICE_SET_HVAC_MODE_THERMOSTAT, params,
hass_value)
@debounce
def set_cooling_threshold(self, value):
@ -232,56 +228,18 @@ class Thermostat(HomeAccessory):
self.char_display_units.set_value(UNIT_HASS_TO_HOMEKIT[self._unit])
# Update target operation mode
operation_mode = new_state.attributes.get(ATTR_OPERATION_MODE)
if self.support_power_state is True and new_state.state == STATE_OFF:
self.char_target_heat_cool.set_value(0) # Off
elif operation_mode and operation_mode in HC_HASS_TO_HOMEKIT:
hvac_mode = new_state.state
if hvac_mode and hvac_mode in HC_HASS_TO_HOMEKIT:
if not self._flag_heat_cool:
self.char_target_heat_cool.set_value(
HC_HASS_TO_HOMEKIT[operation_mode])
HC_HASS_TO_HOMEKIT[hvac_mode])
self._flag_heat_cool = False
# Set current operation mode based on temperatures and target mode
if self.support_power_state is True and new_state.state == STATE_OFF:
current_operation_mode = STATE_OFF
elif operation_mode == STATE_HEAT:
if isinstance(target_temp, float) and current_temp < target_temp:
current_operation_mode = STATE_HEAT
else:
current_operation_mode = STATE_OFF
elif operation_mode == STATE_COOL:
if isinstance(target_temp, float) and current_temp > target_temp:
current_operation_mode = STATE_COOL
else:
current_operation_mode = STATE_OFF
elif operation_mode == STATE_AUTO:
# Check if auto is supported
if self.char_cooling_thresh_temp:
lower_temp = self.char_heating_thresh_temp.value
upper_temp = self.char_cooling_thresh_temp.value
if current_temp < lower_temp:
current_operation_mode = STATE_HEAT
elif current_temp > upper_temp:
current_operation_mode = STATE_COOL
else:
current_operation_mode = STATE_OFF
else:
# Check if heating or cooling are supported
heat = STATE_HEAT in new_state.attributes[ATTR_OPERATION_LIST]
cool = STATE_COOL in new_state.attributes[ATTR_OPERATION_LIST]
if isinstance(target_temp, float) and \
current_temp < target_temp and heat:
current_operation_mode = STATE_HEAT
elif isinstance(target_temp, float) and \
current_temp > target_temp and cool:
current_operation_mode = STATE_COOL
else:
current_operation_mode = STATE_OFF
else:
current_operation_mode = STATE_OFF
self.char_current_heat_cool.set_value(
HC_HASS_TO_HOMEKIT[current_operation_mode])
# Set current operation mode for supported thermostats
hvac_action = new_state.attributes.get(ATTR_HVAC_ACTIONS)
if hvac_action:
self.char_current_heat_cool.set_value(
HC_HASS_TO_HOMEKIT_ACTION[hvac_action])
@TYPES.register('WaterHeater')
@ -337,7 +295,7 @@ class WaterHeater(HomeAccessory):
_LOGGER.debug('%s: Set heat-cool to %d', self.entity_id, value)
self._flag_heat_cool = True
hass_value = HC_HOMEKIT_TO_HASS[value]
if hass_value != STATE_HEAT:
if hass_value != HVAC_MODE_HEAT:
self.char_target_heat_cool.set_value(1) # Heat
@debounce
@ -370,7 +328,7 @@ class WaterHeater(HomeAccessory):
self.char_display_units.set_value(UNIT_HASS_TO_HOMEKIT[self._unit])
# Update target operation mode
operation_mode = new_state.attributes.get(ATTR_OPERATION_MODE)
operation_mode = new_state.state
if operation_mode and not self._flag_heat_cool:
self.char_target_heat_cool.set_value(1) # Heat
self._flag_heat_cool = False

View File

@ -1,12 +1,14 @@
"""Support for Homekit climate devices."""
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate import (
ClimateDevice, DEFAULT_MIN_HUMIDITY, DEFAULT_MAX_HUMIDITY,
)
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW)
from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS
HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF,
CURRENT_HVAC_OFF, CURRENT_HVAC_HEAT, CURRENT_HVAC_COOL,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import KNOWN_DEVICES, HomeKitEntity
@ -14,10 +16,10 @@ _LOGGER = logging.getLogger(__name__)
# Map of Homekit operation modes to hass modes
MODE_HOMEKIT_TO_HASS = {
0: STATE_OFF,
1: STATE_HEAT,
2: STATE_COOL,
3: STATE_AUTO,
0: HVAC_MODE_OFF,
1: HVAC_MODE_HEAT,
2: HVAC_MODE_COOL,
3: HVAC_MODE_HEAT_COOL,
}
# Map of hass operation modes to homekit modes
@ -25,6 +27,12 @@ MODE_HASS_TO_HOMEKIT = {v: k for k, v in MODE_HOMEKIT_TO_HASS.items()}
DEFAULT_VALID_MODES = list(MODE_HOMEKIT_TO_HASS)
CURRENT_MODE_HOMEKIT_TO_HASS = {
0: CURRENT_HVAC_OFF,
1: CURRENT_HVAC_HEAT,
2: CURRENT_HVAC_COOL,
}
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
@ -53,6 +61,7 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
def __init__(self, *args):
"""Initialise the device."""
self._state = None
self._target_mode = None
self._current_mode = None
self._valid_modes = []
self._current_temp = None
@ -61,8 +70,8 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
self._target_humidity = None
self._min_target_temp = None
self._max_target_temp = None
self._min_target_humidity = None
self._max_target_humidity = None
self._min_target_humidity = DEFAULT_MIN_HUMIDITY
self._max_target_humidity = DEFAULT_MAX_HUMIDITY
super().__init__(*args)
def get_characteristic_types(self):
@ -79,8 +88,6 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
]
def _setup_heating_cooling_target(self, characteristic):
self._features |= SUPPORT_OPERATION_MODE
if 'valid-values' in characteristic:
valid_values = [
val for val in DEFAULT_VALID_MODES
@ -117,17 +124,22 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
if 'minValue' in characteristic:
self._min_target_humidity = characteristic['minValue']
self._features |= SUPPORT_TARGET_HUMIDITY_LOW
if 'maxValue' in characteristic:
self._max_target_humidity = characteristic['maxValue']
self._features |= SUPPORT_TARGET_HUMIDITY_HIGH
def _update_heating_cooling_current(self, value):
self._state = MODE_HOMEKIT_TO_HASS.get(value)
# This characteristic describes the current mode of a device,
# e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit.
# Can be 0 - 2 (Off, Heat, Cool)
self._current_mode = CURRENT_MODE_HOMEKIT_TO_HASS.get(value)
def _update_heating_cooling_target(self, value):
self._current_mode = MODE_HOMEKIT_TO_HASS.get(value)
# This characteristic describes the target mode
# E.g. should the device start heating a room if the temperature
# falls below the target temperature.
# Can be 0 - 3 (Off, Heat, Cool, Auto)
self._target_mode = MODE_HOMEKIT_TO_HASS.get(value)
def _update_temperature_current(self, value):
self._current_temp = value
@ -157,25 +169,13 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
'value': humidity}]
await self._accessory.put_characteristics(characteristics)
async def async_set_operation_mode(self, operation_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
characteristics = [{'aid': self._aid,
'iid': self._chars['heating-cooling.target'],
'value': MODE_HASS_TO_HOMEKIT[operation_mode]}]
'value': MODE_HASS_TO_HOMEKIT[hvac_mode]}]
await self._accessory.put_characteristics(characteristics)
@property
def state(self):
"""Return the current state."""
# If the device reports its operating mode as off, it sometimes doesn't
# report a new state.
if self._current_mode == STATE_OFF:
return STATE_OFF
if self._state == STATE_OFF and self._current_mode != STATE_OFF:
return STATE_IDLE
return self._state
@property
def current_temperature(self):
"""Return the current temperature."""
@ -221,13 +221,18 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
return self._max_target_humidity
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
def hvac_action(self):
"""Return the current running hvac operation."""
return self._current_mode
@property
def operation_list(self):
"""Return the list of available operation modes."""
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode."""
return self._target_mode
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes."""
return self._valid_modes
@property

View File

@ -3,26 +3,14 @@ import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_MANUAL, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE)
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_AUTO,
HVAC_MODE_HEAT, PRESET_BOOST, PRESET_COMFORT, PRESET_ECO)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import ATTR_DISCOVER_DEVICES, HM_ATTRIBUTE_SUPPORT, HMDevice
_LOGGER = logging.getLogger(__name__)
STATE_BOOST = 'boost'
STATE_COMFORT = 'comfort'
STATE_LOWERING = 'lowering'
HM_STATE_MAP = {
'AUTO_MODE': STATE_AUTO,
'MANU_MODE': STATE_MANUAL,
'BOOST_MODE': STATE_BOOST,
'COMFORT_MODE': STATE_COMFORT,
'LOWERING_MODE': STATE_LOWERING
}
HM_TEMP_MAP = [
'ACTUAL_TEMPERATURE',
'TEMPERATURE',
@ -33,10 +21,16 @@ HM_HUMI_MAP = [
'HUMIDITY',
]
HM_PRESET_MAP = {
"BOOST_MODE": PRESET_BOOST,
"COMFORT_MODE": PRESET_COMFORT,
"LOWERING_MODE": PRESET_ECO,
}
HM_CONTROL_MODE = 'CONTROL_MODE'
HMIP_CONTROL_MODE = 'SET_POINT_MODE'
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
def setup_platform(hass, config, add_entities, discovery_info=None):
@ -66,40 +60,54 @@ class HMThermostat(HMDevice, ClimateDevice):
return TEMP_CELSIUS
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if HM_CONTROL_MODE not in self._data:
return None
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
# boost mode is active
if self._data.get('BOOST_MODE', False):
return STATE_BOOST
Need to be one of HVAC_MODE_*.
"""
if "MANU_MODE" in self._hmdevice.ACTIONNODE:
if self._hm_controll_mode == self._hmdevice.MANU_MODE:
return HVAC_MODE_HEAT
return HVAC_MODE_AUTO
# HmIP uses the set_point_mode to say if its
# auto or manual
if HMIP_CONTROL_MODE in self._data:
code = self._data[HMIP_CONTROL_MODE]
# Other devices use the control_mode
else:
code = self._data['CONTROL_MODE']
# get the name of the mode
name = HM_ATTRIBUTE_SUPPORT[HM_CONTROL_MODE][1][code]
return name.lower()
# Simple devices
if self._data.get("BOOST_MODE"):
return HVAC_MODE_AUTO
return HVAC_MODE_HEAT
@property
def operation_list(self):
"""Return the list of available operation modes."""
# HMIP use set_point_mode for operation
if HMIP_CONTROL_MODE in self._data:
return [STATE_MANUAL, STATE_AUTO, STATE_BOOST]
def hvac_modes(self):
"""Return the list of available hvac operation modes.
# HM
op_list = []
Need to be a subset of HVAC_MODES.
"""
if "AUTO_MODE" in self._hmdevice.ACTIONNODE:
return [HVAC_MODE_AUTO, HVAC_MODE_HEAT]
return [HVAC_MODE_HEAT]
@property
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp."""
if self._data.get('BOOST_MODE', False):
return 'boost'
# Get the name of the mode
mode = HM_ATTRIBUTE_SUPPORT[HM_CONTROL_MODE][1][self._hm_controll_mode]
mode = mode.lower()
# Filter HVAC states
if mode not in (HVAC_MODE_AUTO, HVAC_MODE_HEAT):
return None
return mode
@property
def preset_modes(self):
"""Return a list of available preset modes."""
preset_modes = []
for mode in self._hmdevice.ACTIONNODE:
if mode in HM_STATE_MAP:
op_list.append(HM_STATE_MAP.get(mode))
return op_list
if mode in HM_PRESET_MAP:
preset_modes.append(HM_PRESET_MAP[mode])
return preset_modes
@property
def current_humidity(self):
@ -128,13 +136,21 @@ class HMThermostat(HMDevice, ClimateDevice):
self._hmdevice.writeNodeData(self._state, float(temperature))
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
for mode, state in HM_STATE_MAP.items():
if state == operation_mode:
code = getattr(self._hmdevice, mode, 0)
self._hmdevice.MODE = code
return
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if hvac_mode == HVAC_MODE_AUTO:
self._hmdevice.MODE = self._hmdevice.AUTO_MODE
else:
self._hmdevice.MODE = self._hmdevice.MANU_MODE
def set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
if preset_mode == PRESET_BOOST:
self._hmdevice.MODE = self._hmdevice.BOOST_MODE
elif preset_mode == PRESET_COMFORT:
self._hmdevice.MODE = self._hmdevice.COMFORT_MODE
elif preset_mode == PRESET_ECO:
self._hmdevice.MODE = self._hmdevice.LOWERING_MODE
@property
def min_temp(self):
@ -146,6 +162,19 @@ class HMThermostat(HMDevice, ClimateDevice):
"""Return the maximum temperature - 30.5 means on."""
return 30.5
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return 0.5
@property
def _hm_controll_mode(self):
"""Return Control mode."""
if HMIP_CONTROL_MODE in self._data:
return self._data[HMIP_CONTROL_MODE]
# Homematic
return self._data['CONTROL_MODE']
def _init_data_struct(self):
"""Generate a data dict (self._data) from the Homematic metadata."""
self._state = next(iter(self._hmdevice.WRITENODE.keys()))

View File

@ -1,5 +1,6 @@
"""Support for HomematicIP Cloud climate devices."""
import logging
from typing import Awaitable
from homematicip.aio.device import (
AsyncHeatingThermostat, AsyncHeatingThermostatCompact)
@ -8,7 +9,8 @@ from homematicip.aio.home import AsyncHome
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_MANUAL, SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_AUTO, HVAC_MODE_HEAT, PRESET_BOOST, PRESET_COMFORT, PRESET_ECO,
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.core import HomeAssistant
@ -17,12 +19,9 @@ from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice
_LOGGER = logging.getLogger(__name__)
HA_STATE_TO_HMIP = {
STATE_AUTO: 'AUTOMATIC',
STATE_MANUAL: 'MANUAL',
}
HMIP_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_HMIP.items()}
HMIP_AUTOMATIC_CM = 'AUTOMATIC'
HMIP_MANUAL_CM = 'MANUAL'
HMIP_ECO_CM = 'ECO'
async def async_setup_platform(
@ -63,7 +62,7 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE
return SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE
@property
def target_temperature(self) -> float:
@ -83,9 +82,48 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
return self._device.humidity
@property
def current_operation(self) -> str:
"""Return current operation ie. automatic or manual."""
return HMIP_STATE_TO_HA.get(self._device.controlMode)
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if self._device.boostMode:
return HVAC_MODE_AUTO
if self._device.controlMode == HMIP_MANUAL_CM:
return HVAC_MODE_HEAT
return HVAC_MODE_AUTO
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return [HVAC_MODE_AUTO, HVAC_MODE_HEAT]
@property
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp.
Requires SUPPORT_PRESET_MODE.
"""
if self._device.boostMode:
return PRESET_BOOST
if self._device.controlMode == HMIP_AUTOMATIC_CM:
return PRESET_COMFORT
if self._device.controlMode == HMIP_ECO_CM:
return PRESET_ECO
return None
@property
def preset_modes(self):
"""Return a list of available preset modes.
Requires SUPPORT_PRESET_MODE.
"""
return [PRESET_BOOST, PRESET_COMFORT]
@property
def min_temp(self) -> float:
@ -104,6 +142,22 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
return
await self._device.set_point_temperature(temperature)
async def async_set_hvac_mode(self, hvac_mode: str) -> Awaitable[None]:
"""Set new target hvac mode."""
if hvac_mode == HVAC_MODE_AUTO:
await self._device.set_control_mode(HMIP_AUTOMATIC_CM)
else:
await self._device.set_control_mode(HMIP_MANUAL_CM)
async def async_set_preset_mode(self, preset_mode: str) -> Awaitable[None]:
"""Set new preset mode."""
if self._device.boostMode and preset_mode != PRESET_BOOST:
await self._device.set_boost(False)
if preset_mode == PRESET_BOOST:
await self._device.set_boost()
elif preset_mode == PRESET_COMFORT:
await self._device.set_control_mode(HMIP_AUTOMATIC_CM)
def _get_first_heating_thermostat(heating_group: AsyncHeatingGroup):
"""Return the first HeatingThermostat from a HeatingGroup."""

View File

@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/homematicip_cloud",
"requirements": [
"homematicip==0.10.7"
"homematicip==0.10.9"
],
"dependencies": [],
"codeowners": []

View File

@ -1 +1 @@
"""Support for Honeywell Total Connect Comfort climate systems."""
"""Support for Honeywell (US) Total Connect Comfort climate systems."""

View File

@ -1,31 +1,35 @@
"""Support for Honeywell Total Connect Comfort climate systems."""
import logging
"""Support for Honeywell (US) Total Connect Comfort climate systems."""
import datetime
import logging
from typing import Any, Dict, Optional, List
import requests
import voluptuous as vol
import somecomfort
import homeassistant.helpers.config_validation as cv
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate.const import (
ATTR_FAN_MODE, ATTR_FAN_LIST,
ATTR_OPERATION_MODE, ATTR_OPERATION_LIST, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE)
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
FAN_AUTO, FAN_DIFFUSE, FAN_ON,
SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE,
SUPPORT_PRESET_MODE, SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE,
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF,
HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT_COOL,
PRESET_AWAY,
)
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE, CONF_REGION)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
ATTR_FAN = 'fan'
ATTR_SYSTEM_MODE = 'system_mode'
ATTR_CURRENT_OPERATION = 'equipment_output_status'
ATTR_FAN_ACTION = 'fan_action'
CONF_AWAY_TEMPERATURE = 'away_temperature'
CONF_COOL_AWAY_TEMPERATURE = 'away_cool_temperature'
CONF_HEAT_AWAY_TEMPERATURE = 'away_heat_temperature'
DEFAULT_AWAY_TEMPERATURE = 16 # in C, for eu regions, the others are F/us
DEFAULT_COOL_AWAY_TEMPERATURE = 88
DEFAULT_HEAT_AWAY_TEMPERATURE = 61
DEFAULT_REGION = 'eu'
@ -34,8 +38,6 @@ REGIONS = ['eu', 'us']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_AWAY_TEMPERATURE,
default=DEFAULT_AWAY_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_COOL_AWAY_TEMPERATURE,
default=DEFAULT_COOL_AWAY_TEMPERATURE): vol.Coerce(int),
vol.Optional(CONF_HEAT_AWAY_TEMPERATURE,
@ -43,191 +45,71 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(REGIONS),
})
HVAC_MODE_TO_HW_MODE = {
'SwitchOffAllowed': {HVAC_MODE_OFF: 'off'},
'SwitchAutoAllowed': {HVAC_MODE_HEAT_COOL: 'auto'},
'SwitchCoolAllowed': {HVAC_MODE_COOL: 'cool'},
'SwitchHeatAllowed': {HVAC_MODE_HEAT: 'heat'},
}
HW_MODE_TO_HVAC_MODE = {
'off': HVAC_MODE_OFF,
'emheat': HVAC_MODE_HEAT,
'heat': HVAC_MODE_HEAT,
'cool': HVAC_MODE_COOL,
'auto': HVAC_MODE_HEAT_COOL,
}
HW_MODE_TO_HA_HVAC_ACTION = {
'off': CURRENT_HVAC_OFF,
'fan': CURRENT_HVAC_IDLE,
'heat': CURRENT_HVAC_HEAT,
'cool': CURRENT_HVAC_COOL,
}
FAN_MODE_TO_HW = {
'fanModeOnAllowed': {FAN_ON: 'on'},
'fanModeAutoAllowed': {FAN_AUTO: 'auto'},
'fanModeCirculateAllowed': {FAN_DIFFUSE: 'circulate'},
}
HW_FAN_MODE_TO_HA = {
'on': FAN_ON,
'auto': FAN_AUTO,
'circulate': FAN_DIFFUSE,
'follow schedule': FAN_AUTO,
}
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Honeywell thermostat."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
region = config.get(CONF_REGION)
if region == 'us':
return _setup_us(username, password, config, add_entities)
if config.get(CONF_REGION) == 'us':
try:
client = somecomfort.SomeComfort(username, password)
except somecomfort.AuthError:
_LOGGER.error("Failed to login to honeywell account %s", username)
return
except somecomfort.SomeComfortError as ex:
_LOGGER.error("Failed to initialize honeywell client: %s", str(ex))
return
dev_id = config.get('thermostat')
loc_id = config.get('location')
cool_away_temp = config.get(CONF_COOL_AWAY_TEMPERATURE)
heat_away_temp = config.get(CONF_HEAT_AWAY_TEMPERATURE)
add_entities([HoneywellUSThermostat(client, device, cool_away_temp,
heat_away_temp, username, password)
for location in client.locations_by_id.values()
for device in location.devices_by_id.values()
if ((not loc_id or location.locationid == loc_id) and
(not dev_id or device.deviceid == dev_id))])
return
_LOGGER.warning(
"The honeywell component is deprecated for EU (i.e. non-US) systems, "
"this functionality will be removed in version 0.96. "
"Please switch to the evohome component, "
"The honeywell component has been deprecated for EU (i.e. non-US) "
"systems. For EU-based systems, use the evohome component, "
"see: https://home-assistant.io/components/evohome")
return _setup_round(username, password, config, add_entities)
def _setup_round(username, password, config, add_entities):
"""Set up the rounding function."""
from evohomeclient import EvohomeClient
away_temp = config.get(CONF_AWAY_TEMPERATURE)
evo_api = EvohomeClient(username, password)
try:
zones = evo_api.temperatures(force_refresh=True)
for i, zone in enumerate(zones):
add_entities(
[RoundThermostat(evo_api, zone['id'], i == 0, away_temp)],
True
)
except requests.exceptions.RequestException as err:
_LOGGER.error(
"Connection error logging into the honeywell evohome web service, "
"hint: %s", err)
return False
return True
# config will be used later
def _setup_us(username, password, config, add_entities):
"""Set up the user."""
import somecomfort
try:
client = somecomfort.SomeComfort(username, password)
except somecomfort.AuthError:
_LOGGER.error("Failed to login to honeywell account %s", username)
return False
except somecomfort.SomeComfortError as ex:
_LOGGER.error("Failed to initialize honeywell client: %s", str(ex))
return False
dev_id = config.get('thermostat')
loc_id = config.get('location')
cool_away_temp = config.get(CONF_COOL_AWAY_TEMPERATURE)
heat_away_temp = config.get(CONF_HEAT_AWAY_TEMPERATURE)
add_entities([HoneywellUSThermostat(client, device, cool_away_temp,
heat_away_temp, username, password)
for location in client.locations_by_id.values()
for device in location.devices_by_id.values()
if ((not loc_id or location.locationid == loc_id) and
(not dev_id or device.deviceid == dev_id))])
return True
class RoundThermostat(ClimateDevice):
"""Representation of a Honeywell Round Connected thermostat."""
def __init__(self, client, zone_id, master, away_temp):
"""Initialize the thermostat."""
self.client = client
self._current_temperature = None
self._target_temperature = None
self._name = 'round connected'
self._id = zone_id
self._master = master
self._is_dhw = False
self._away_temp = away_temp
self._away = False
@property
def supported_features(self):
"""Return the list of supported features."""
supported = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE)
if hasattr(self.client, ATTR_SYSTEM_MODE):
supported |= SUPPORT_OPERATION_MODE
return supported
@property
def name(self):
"""Return the name of the honeywell, if any."""
return self._name
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._is_dhw:
return None
return self._target_temperature
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self.client.set_temperature(self._name, temperature)
@property
def current_operation(self) -> str:
"""Get the current operation of the system."""
return getattr(self.client, ATTR_SYSTEM_MODE, None)
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
def set_operation_mode(self, operation_mode: str) -> None:
"""Set the HVAC mode for the thermostat."""
if hasattr(self.client, ATTR_SYSTEM_MODE):
self.client.system_mode = operation_mode
def turn_away_mode_on(self):
"""Turn away on.
Honeywell does have a proprietary away mode, but it doesn't really work
the way it should. For example: If you set a temperature manually
it doesn't get overwritten when away mode is switched on.
"""
self._away = True
self.client.set_temperature(self._name, self._away_temp)
def turn_away_mode_off(self):
"""Turn away off."""
self._away = False
self.client.cancel_temp_override(self._name)
def update(self):
"""Get the latest date."""
try:
# Only refresh if this is the "master" device,
# others will pick up the cache
for val in self.client.temperatures(force_refresh=self._master):
if val['id'] == self._id:
data = val
except KeyError:
_LOGGER.error("Update failed from Honeywell server")
self.client.user_data = None
return
except StopIteration:
_LOGGER.error("Did not receive any temperature data from the "
"evohomeclient API")
return
self._current_temperature = data['temp']
self._target_temperature = data['setpoint']
if data['thermostat'] == 'DOMESTIC_HOT_WATER':
self._name = 'Hot Water'
self._is_dhw = True
else:
self._name = data['name']
self._is_dhw = False
# The underlying library doesn't expose the thermostat's mode
# but we can pull it out of the big dictionary of information.
device = self.client.devices[self._id]
self.client.system_mode = device[
'thermostat']['changeableValues']['mode']
class HoneywellUSThermostat(ClimateDevice):
"""Representation of a Honeywell US Thermostat."""
@ -243,61 +125,132 @@ class HoneywellUSThermostat(ClimateDevice):
self._username = username
self._password = password
@property
def supported_features(self):
"""Return the list of supported features."""
supported = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE)
if hasattr(self._device, ATTR_SYSTEM_MODE):
supported |= SUPPORT_OPERATION_MODE
return supported
self._supported_features = (SUPPORT_PRESET_MODE |
SUPPORT_TARGET_TEMPERATURE |
SUPPORT_TARGET_TEMPERATURE_RANGE)
# pylint: disable=protected-access
_LOGGER.debug("uiData = %s ", device._data['uiData'])
# not all honeywell HVACs upport all modes
mappings = [v for k, v in HVAC_MODE_TO_HW_MODE.items()
if k in device._data['uiData']]
self._hvac_mode_map = {k: v for d in mappings for k, v in d.items()}
if device._data['canControlHumidification']:
self._supported_features |= SUPPORT_TARGET_HUMIDITY
if device._data['uiData']['SwitchEmergencyHeatAllowed']:
self._supported_features |= SUPPORT_AUX_HEAT
if not device._data['hasFan']:
return
self._supported_features |= SUPPORT_FAN_MODE
# not all honeywell fans support all modes
mappings = [v for k, v in FAN_MODE_TO_HW.items()
if k in device._data['fanData']]
self._fan_mode_map = {k: v for d in mappings for k, v in d.items()}
@property
def is_fan_on(self):
"""Return true if fan is on."""
return self._device.fan_running
@property
def name(self):
def name(self) -> Optional[str]:
"""Return the name of the honeywell, if any."""
return self._device.name
@property
def temperature_unit(self):
def device_state_attributes(self) -> Dict[str, Any]:
"""Return the device specific state attributes."""
# pylint: disable=protected-access
data = {}
if self._device._data['hasFan']:
data[ATTR_FAN_ACTION] = \
'running' if self._device.fan_running else 'idle'
return data
@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return self._supported_features
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
return (TEMP_CELSIUS if self._device.temperature_unit == 'C'
else TEMP_FAHRENHEIT)
@property
def current_temperature(self):
"""Return the current temperature."""
return self._device.current_temperature
@property
def current_humidity(self):
def current_humidity(self) -> Optional[int]:
"""Return the current humidity."""
return self._device.current_humidity
@property
def target_temperature(self):
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode."""
return HW_MODE_TO_HVAC_MODE[self._device.system_mode]
@property
def hvac_modes(self) -> List[str]:
"""Return the list of available hvac operation modes."""
return list(self._hvac_mode_map)
@property
def hvac_action(self) -> Optional[str]:
"""Return the current running hvac operation if supported."""
return HW_MODE_TO_HA_HVAC_ACTION[self._device.equipment_output_status]
@property
def current_temperature(self) -> Optional[float]:
"""Return the current temperature."""
return self._device.current_temperature
@property
def target_temperature(self) -> Optional[float]:
"""Return the temperature we try to reach."""
if self._device.system_mode == 'cool':
if self.hvac_mode == HVAC_MODE_COOL:
return self._device.setpoint_cool
if self.hvac_mode != HVAC_MODE_HEAT:
return self._device.setpoint_heat
return None
@property
def target_temperature_high(self) -> Optional[float]:
"""Return the highbound target temperature we try to reach."""
return self._device.setpoint_cool
@property
def target_temperature_low(self) -> Optional[float]:
"""Return the lowbound target temperature we try to reach."""
return self._device.setpoint_heat
@property
def current_operation(self) -> str:
"""Return current operation ie. heat, cool, idle."""
oper = getattr(self._device, ATTR_CURRENT_OPERATION, None)
if oper == "off":
oper = "idle"
return oper
def preset_mode(self) -> Optional[str]:
"""Return the current preset mode, e.g., home, away, temp."""
return PRESET_AWAY if self._away else None
def set_temperature(self, **kwargs):
"""Set target temperature."""
@property
def preset_modes(self) -> Optional[List[str]]:
"""Return a list of available preset modes."""
return [PRESET_AWAY]
@property
def is_aux_heat(self) -> Optional[str]:
"""Return true if aux heater."""
return self._device.system_mode == 'emheat'
@property
def fan_mode(self) -> Optional[str]:
"""Return the fan setting."""
return HW_FAN_MODE_TO_HA[self._device.fan_mode]
@property
def fan_modes(self) -> Optional[List[str]]:
"""Return the list of available fan modes."""
return list(self._fan_mode_map)
def _set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
import somecomfort
try:
# Get current mode
mode = self._device.system_mode
@ -320,25 +273,31 @@ class HoneywellUSThermostat(ClimateDevice):
except somecomfort.SomeComfortError:
_LOGGER.error("Temperature %.1f out of range", temperature)
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
import somecomfort
data = {
ATTR_FAN: (self.is_fan_on and 'running' or 'idle'),
ATTR_FAN_MODE: self._device.fan_mode,
ATTR_OPERATION_MODE: self._device.system_mode,
}
data[ATTR_FAN_LIST] = somecomfort.FAN_MODES
data[ATTR_OPERATION_LIST] = somecomfort.SYSTEM_MODES
return data
def set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
if {HVAC_MODE_COOL, HVAC_MODE_HEAT} & set(self._hvac_mode_map):
self._set_temperature(**kwargs)
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
try:
if HVAC_MODE_HEAT_COOL in self._hvac_mode_map:
temperature = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if temperature:
self._device.setpoint_cool = temperature
temperature = kwargs.get(ATTR_TARGET_TEMP_LOW)
if temperature:
self._device.setpoint_heat = temperature
except somecomfort.SomeComfortError as err:
_LOGGER.error("Invalid temperature %s: %s", temperature, err)
def turn_away_mode_on(self):
def set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
self._device.fan_mode = self._fan_mode_map[fan_mode]
def set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
self._device.system_mode = self._hvac_mode_map[hvac_mode]
def _turn_away_mode_on(self) -> None:
"""Turn away on.
Somecomfort does have a proprietary away mode, but it doesn't really
@ -346,7 +305,6 @@ class HoneywellUSThermostat(ClimateDevice):
it doesn't get overwritten when away mode is switched on.
"""
self._away = True
import somecomfort
try:
# Get current mode
mode = self._device.system_mode
@ -367,10 +325,9 @@ class HoneywellUSThermostat(ClimateDevice):
_LOGGER.error('Temperature %.1f out of range',
getattr(self, "_{}_away_temp".format(mode)))
def turn_away_mode_off(self):
def _turn_away_mode_off(self) -> None:
"""Turn away off."""
self._away = False
import somecomfort
try:
# Disabling all hold modes
self._device.hold_cool = False
@ -378,36 +335,27 @@ class HoneywellUSThermostat(ClimateDevice):
except somecomfort.SomeComfortError:
_LOGGER.error('Can not stop hold mode')
def set_operation_mode(self, operation_mode: str) -> None:
"""Set the system mode (Cool, Heat, etc)."""
if hasattr(self._device, ATTR_SYSTEM_MODE):
self._device.system_mode = operation_mode
def set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
if preset_mode == PRESET_AWAY:
self._turn_away_mode_on()
else:
self._turn_away_mode_off()
def update(self):
"""Update the state."""
import somecomfort
retries = 3
while retries > 0:
try:
self._device.refresh()
break
except (somecomfort.client.APIRateLimited, OSError,
requests.exceptions.ReadTimeout) as exp:
retries -= 1
if retries == 0:
raise exp
if not self._retry():
raise exp
_LOGGER.error(
"SomeComfort update failed, Retrying - Error: %s", exp)
def turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on."""
self._device.system_mode = 'emheat'
def _retry(self):
def turn_aux_heat_off(self) -> None:
"""Turn auxiliary heater off."""
self._device.system_mode = 'auto'
def _retry(self) -> bool:
"""Recreate a new somecomfort client.
When we got an error, the best way to be sure that the next query
will succeed, is to recreate a new somecomfort client.
"""
import somecomfort
try:
self._client = somecomfort.SomeComfort(
self._username, self._password)
@ -431,3 +379,20 @@ class HoneywellUSThermostat(ClimateDevice):
self._device = devices[0]
return True
def update(self) -> None:
"""Update the state."""
retries = 3
while retries > 0:
try:
self._device.refresh()
break
except (somecomfort.client.APIRateLimited, OSError,
requests.exceptions.ReadTimeout) as exp:
retries -= 1
if retries == 0:
raise exp
if not self._retry():
raise exp
_LOGGER.error(
"SomeComfort update failed, Retrying - Error: %s", exp)

View File

@ -3,7 +3,6 @@
"name": "Honeywell",
"documentation": "https://www.home-assistant.io/components/honeywell",
"requirements": [
"evohomeclient==0.3.2",
"somecomfort==0.5.2"
],
"dependencies": [],

View File

@ -1,17 +1,15 @@
"""Support for an Intergas boiler via an InComfort/InTouch Lan2RF gateway."""
from typing import Any, Dict, Optional, List
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE
from homeassistant.const import (ATTR_TEMPERATURE, TEMP_CELSIUS)
from homeassistant.components.climate.const import (
HVAC_MODE_HEAT, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import DOMAIN
INTOUCH_SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
INTOUCH_MAX_TEMP = 30.0
INTOUCH_MIN_TEMP = 5.0
async def async_setup_platform(hass, hass_config, async_add_entities,
discovery_info=None):
@ -31,7 +29,7 @@ class InComfortClimate(ClimateDevice):
self._room = room
self._name = 'Room {}'.format(room.room_no)
async def async_added_to_hass(self):
async def async_added_to_hass(self) -> None:
"""Set up a listener when this entity is added to HA."""
async_dispatcher_connect(self.hass, DOMAIN, self._refresh)
@ -40,51 +38,65 @@ class InComfortClimate(ClimateDevice):
self.async_schedule_update_ha_state(force_refresh=True)
@property
def name(self):
def should_poll(self) -> bool:
"""Return False as this device should never be polled."""
return False
@property
def name(self) -> str:
"""Return the name of the climate device."""
return self._name
@property
def device_state_attributes(self):
def device_state_attributes(self) -> Dict[str, Any]:
"""Return the device state attributes."""
return {'status': self._room.status}
@property
def current_temperature(self):
"""Return the current temperature."""
return self._room.room_temp
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._room.override
@property
def min_temp(self):
"""Return max valid temperature that can be set."""
return INTOUCH_MIN_TEMP
@property
def max_temp(self):
"""Return max valid temperature that can be set."""
return INTOUCH_MAX_TEMP
@property
def temperature_unit(self):
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def supported_features(self):
"""Return the list of supported features."""
return INTOUCH_SUPPORT_FLAGS
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode."""
return HVAC_MODE_HEAT
async def async_set_temperature(self, **kwargs):
@property
def hvac_modes(self) -> List[str]:
"""Return the list of available hvac operation modes."""
return [HVAC_MODE_HEAT]
@property
def current_temperature(self) -> Optional[float]:
"""Return the current temperature."""
return self._room.room_temp
@property
def target_temperature(self) -> Optional[float]:
"""Return the temperature we try to reach."""
return self._room.override
@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE
@property
def min_temp(self) -> float:
"""Return max valid temperature that can be set."""
return 5.0
@property
def max_temp(self) -> float:
"""Return max valid temperature that can be set."""
return 30.0
async def async_set_temperature(self, **kwargs) -> None:
"""Set a new target temperature for this zone."""
temperature = kwargs.get(ATTR_TEMPERATURE)
await self._room.set_override(temperature)
@property
def should_poll(self) -> bool:
"""Return False as this device should never be polled."""
return False
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
pass

View File

@ -1,10 +1,13 @@
"""Support for KNX/IP climate devices."""
from typing import Optional, List
import voluptuous as vol
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
STATE_DRY, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, STATE_MANUAL,
SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF,
PRESET_ECO, PRESET_SLEEP, PRESET_AWAY, PRESET_COMFORT,
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
@ -41,19 +44,25 @@ DEFAULT_SETPOINT_SHIFT_MAX = 6
DEFAULT_SETPOINT_SHIFT_MIN = -6
# Map KNX operation modes to HA modes. This list might not be full.
OPERATION_MODES = {
# Map DPT 201.100 HVAC operating modes
"Frost Protection": STATE_MANUAL,
"Night": STATE_IDLE,
"Standby": STATE_ECO,
"Comfort": STATE_HEAT,
# Map DPT 201.104 HVAC control modes
"Fan only": STATE_FAN_ONLY,
"Dehumidification": STATE_DRY
"Fan only": HVAC_MODE_FAN_ONLY,
"Dehumidification": HVAC_MODE_DRY
}
OPERATION_MODES_INV = dict((
reversed(item) for item in OPERATION_MODES.items()))
PRESET_MODES = {
# Map DPT 201.100 HVAC operating modes to HA presets
"Frost Protection": PRESET_ECO,
"Night": PRESET_SLEEP,
"Standby": PRESET_AWAY,
"Comfort": PRESET_COMFORT,
}
PRESET_MODES_INV = dict((
reversed(item) for item in PRESET_MODES.items()))
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
@ -167,16 +176,11 @@ class KNXClimate(ClimateDevice):
self._unit_of_measurement = TEMP_CELSIUS
@property
def supported_features(self):
def supported_features(self) -> int:
"""Return the list of supported features."""
support = SUPPORT_TARGET_TEMPERATURE
if self.device.mode.supports_operation_mode:
support |= SUPPORT_OPERATION_MODE
if self.device.supports_on_off:
support |= SUPPORT_ON_OFF
return support
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
async def async_added_to_hass(self):
async def async_added_to_hass(self) -> None:
"""Register callbacks to update hass after device was changed."""
async def after_update_callback(device):
"""Call after device was updated."""
@ -184,17 +188,17 @@ class KNXClimate(ClimateDevice):
self.device.register_device_updated_cb(after_update_callback)
@property
def name(self):
def name(self) -> str:
"""Return the name of the KNX device."""
return self.device.name
@property
def available(self):
def available(self) -> bool:
"""Return True if entity is available."""
return self.hass.data[DATA_KNX].connected
@property
def should_poll(self):
def should_poll(self) -> bool:
"""No polling needed within KNX."""
return False
@ -211,7 +215,7 @@ class KNXClimate(ClimateDevice):
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return self.device.setpoint_shift_step
return self.device.temperature_step
@property
def target_temperature(self):
@ -228,7 +232,7 @@ class KNXClimate(ClimateDevice):
"""Return the maximum temperature."""
return self.device.target_temperature_max
async def async_set_temperature(self, **kwargs):
async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
@ -237,39 +241,74 @@ class KNXClimate(ClimateDevice):
await self.async_update_ha_state()
@property
def current_operation(self):
def hvac_mode(self) -> Optional[str]:
"""Return current operation ie. heat, cool, idle."""
if self.device.supports_on_off and not self.device.is_on:
return HVAC_MODE_OFF
if self.device.supports_on_off and self.device.is_on:
return HVAC_MODE_HEAT
if self.device.mode.supports_operation_mode:
return OPERATION_MODES.get(self.device.mode.operation_mode.value)
return OPERATION_MODES.get(
self.device.mode.operation_mode.value, HVAC_MODE_HEAT)
return None
@property
def operation_list(self):
def hvac_modes(self) -> Optional[List[str]]:
"""Return the list of available operation modes."""
return [OPERATION_MODES.get(operation_mode.value) for
operation_mode in
self.device.mode.operation_modes]
_operations = [OPERATION_MODES.get(operation_mode.value) for
operation_mode in
self.device.mode.operation_modes]
async def async_set_operation_mode(self, operation_mode):
if self.device.supports_on_off:
_operations.append(HVAC_MODE_HEAT)
_operations.append(HVAC_MODE_OFF)
return [op for op in _operations if op is not None]
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set operation mode."""
if self.device.mode.supports_operation_mode:
if self.device.supports_on_off and hvac_mode == HVAC_MODE_OFF:
await self.device.turn_off()
elif self.device.supports_on_off and hvac_mode == HVAC_MODE_HEAT:
await self.device.turn_on()
elif self.device.mode.supports_operation_mode:
from xknx.knx import HVACOperationMode
knx_operation_mode = HVACOperationMode(
OPERATION_MODES_INV.get(operation_mode))
OPERATION_MODES_INV.get(hvac_mode))
await self.device.mode.set_operation_mode(knx_operation_mode)
await self.async_update_ha_state()
@property
def is_on(self):
"""Return true if the device is on."""
if self.device.supports_on_off:
return self.device.is_on
def preset_mode(self) -> Optional[str]:
"""Return the current preset mode, e.g., home, away, temp.
Requires SUPPORT_PRESET_MODE.
"""
if self.device.mode.supports_operation_mode:
return PRESET_MODES.get(
self.device.mode.operation_mode.value, PRESET_AWAY)
return None
async def async_turn_on(self):
"""Turn on."""
await self.device.turn_on()
@property
def preset_modes(self) -> Optional[List[str]]:
"""Return a list of available preset modes.
async def async_turn_off(self):
"""Turn off."""
await self.device.turn_off()
Requires SUPPORT_PRESET_MODE.
"""
_presets = [PRESET_MODES.get(operation_mode.value) for
operation_mode in
self.device.mode.operation_modes]
return list(filter(None, _presets))
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode.
This method must be run in the event loop and returns a coroutine.
"""
if self.device.mode.supports_operation_mode:
from xknx.knx import HVACOperationMode
knx_operation_mode = HVACOperationMode(
PRESET_MODES_INV.get(preset_mode))
await self.device.mode.set_operation_mode(knx_operation_mode)
await self.async_update_ha_state()

View File

@ -1,4 +1,5 @@
"""Support for LCN climate control."""
import pypck
from homeassistant.components.climate import ClimateDevice, const
@ -53,10 +54,6 @@ class LcnClimate(LcnDevice, ClimateDevice):
self._target_temperature = None
self._is_on = None
self.support = const.SUPPORT_TARGET_TEMPERATURE
if self.is_lockable:
self.support |= const.SUPPORT_ON_OFF
async def async_added_to_hass(self):
"""Run when entity about to be added to hass."""
await super().async_added_to_hass()
@ -68,7 +65,7 @@ class LcnClimate(LcnDevice, ClimateDevice):
@property
def supported_features(self):
"""Return the list of supported features."""
return self.support
return const.SUPPORT_TARGET_TEMPERATURE
@property
def temperature_unit(self):
@ -86,9 +83,25 @@ class LcnClimate(LcnDevice, ClimateDevice):
return self._target_temperature
@property
def is_on(self):
"""Return true if the device is on."""
return self._is_on
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if self._is_on:
return const.HVAC_MODE_HEAT
return const.HVAC_MODE_OFF
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
modes = [const.HVAC_MODE_HEAT]
if self.is_lockable:
modes.append(const.HVAC_MODE_OFF)
return modes
@property
def max_temp(self):
@ -100,18 +113,17 @@ class LcnClimate(LcnDevice, ClimateDevice):
"""Return the minimum temperature."""
return self._min_temp
async def async_turn_on(self):
"""Turn on."""
self._is_on = True
self.address_connection.lock_regulator(self.regulator_id, False)
await self.async_update_ha_state()
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if hvac_mode == const.HVAC_MODE_HEAT:
self._is_on = True
self.address_connection.lock_regulator(self.regulator_id, False)
elif hvac_mode == const.HVAC_MODE_OFF:
self._is_on = False
self.address_connection.lock_regulator(self.regulator_id, True)
self._target_temperature = None
async def async_turn_off(self):
"""Turn off."""
self._is_on = False
self.address_connection.lock_regulator(self.regulator_id, True)
self._target_temperature = None
await self.async_update_ha_state()
self.async_schedule_update_ha_state()
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
@ -122,7 +134,7 @@ class LcnClimate(LcnDevice, ClimateDevice):
self._target_temperature = temperature
self.address_connection.var_abs(
self.setpoint, self._target_temperature, self.unit)
await self.async_update_ha_state()
self.async_schedule_update_ha_state()
def input_received(self, input_obj):
"""Set temperature value when LCN input object is received."""
@ -134,7 +146,7 @@ class LcnClimate(LcnDevice, ClimateDevice):
input_obj.get_value().to_var_unit(self.unit)
elif input_obj.get_var() == self.setpoint:
self._is_on = not input_obj.get_value().is_locked_regulator()
if self.is_on:
if self._is_on:
self._target_temperature = \
input_obj.get_value().to_var_unit(self.unit)

View File

@ -2,20 +2,26 @@
import logging
import socket
from maxcube.device import \
MAX_DEVICE_MODE_AUTOMATIC, \
MAX_DEVICE_MODE_MANUAL, \
MAX_DEVICE_MODE_VACATION, \
MAX_DEVICE_MODE_BOOST
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_AUTO, SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import DATA_KEY
_LOGGER = logging.getLogger(__name__)
STATE_MANUAL = 'manual'
STATE_BOOST = 'boost'
STATE_VACATION = 'vacation'
PRESET_MANUAL = 'manual'
PRESET_BOOST = 'boost'
PRESET_VACATION = 'vacation'
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
def setup_platform(hass, config, add_entities, discovery_info=None):
@ -41,8 +47,7 @@ class MaxCubeClimate(ClimateDevice):
def __init__(self, handler, name, rf_address):
"""Initialize MAX! Cube ClimateDevice."""
self._name = name
self._operation_list = [STATE_AUTO, STATE_MANUAL, STATE_BOOST,
STATE_VACATION]
self._operation_list = [HVAC_MODE_AUTO]
self._rf_address = rf_address
self._cubehandle = handler
@ -87,13 +92,12 @@ class MaxCubeClimate(ClimateDevice):
return self.map_temperature_max_hass(device.actual_temperature)
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation (auto, manual, boost, vacation)."""
device = self._cubehandle.cube.device_by_rf(self._rf_address)
return self.map_mode_max_hass(device.mode)
return HVAC_MODE_AUTO
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return self._operation_list
@ -120,13 +124,25 @@ class MaxCubeClimate(ClimateDevice):
_LOGGER.error("Setting target temperature failed")
return False
def set_operation_mode(self, operation_mode):
@property
def preset_mode(self):
"""Return the current preset mode."""
device = self._cubehandle.cube.device_by_rf(self._rf_address)
return self.map_mode_max_hass(device.mode)
@property
def preset_modes(self):
"""Return available preset modes."""
return [
PRESET_BOOST,
PRESET_MANUAL,
PRESET_VACATION,
]
def set_preset_mode(self, preset_mode):
"""Set new operation mode."""
device = self._cubehandle.cube.device_by_rf(self._rf_address)
mode = self.map_mode_hass_max(operation_mode)
if mode is None:
return False
mode = self.map_mode_hass_max(preset_mode) or MAX_DEVICE_MODE_AUTOMATIC
with self._cubehandle.mutex:
try:
@ -148,21 +164,13 @@ class MaxCubeClimate(ClimateDevice):
return temperature
@staticmethod
def map_mode_hass_max(operation_mode):
def map_mode_hass_max(mode):
"""Map Home Assistant Operation Modes to MAX! Operation Modes."""
from maxcube.device import \
MAX_DEVICE_MODE_AUTOMATIC, \
MAX_DEVICE_MODE_MANUAL, \
MAX_DEVICE_MODE_VACATION, \
MAX_DEVICE_MODE_BOOST
if operation_mode == STATE_AUTO:
mode = MAX_DEVICE_MODE_AUTOMATIC
elif operation_mode == STATE_MANUAL:
if mode == PRESET_MANUAL:
mode = MAX_DEVICE_MODE_MANUAL
elif operation_mode == STATE_VACATION:
elif mode == PRESET_VACATION:
mode = MAX_DEVICE_MODE_VACATION
elif operation_mode == STATE_BOOST:
elif mode == PRESET_BOOST:
mode = MAX_DEVICE_MODE_BOOST
else:
mode = None
@ -172,20 +180,12 @@ class MaxCubeClimate(ClimateDevice):
@staticmethod
def map_mode_max_hass(mode):
"""Map MAX! Operation Modes to Home Assistant Operation Modes."""
from maxcube.device import \
MAX_DEVICE_MODE_AUTOMATIC, \
MAX_DEVICE_MODE_MANUAL, \
MAX_DEVICE_MODE_VACATION, \
MAX_DEVICE_MODE_BOOST
if mode == MAX_DEVICE_MODE_AUTOMATIC:
operation_mode = STATE_AUTO
elif mode == MAX_DEVICE_MODE_MANUAL:
operation_mode = STATE_MANUAL
if mode == MAX_DEVICE_MODE_MANUAL:
operation_mode = PRESET_MANUAL
elif mode == MAX_DEVICE_MODE_VACATION:
operation_mode = STATE_VACATION
operation_mode = PRESET_VACATION
elif mode == MAX_DEVICE_MODE_BOOST:
operation_mode = STATE_BOOST
operation_mode = PRESET_BOOST
else:
operation_mode = None

View File

@ -3,27 +3,26 @@ import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT,
SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
from homeassistant.const import (
ATTR_TEMPERATURE, PRECISION_WHOLE, STATE_IDLE, STATE_OFF, STATE_ON,
TEMP_CELSIUS)
ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS)
from . import DATA_MELISSA
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_OPERATION_MODE |
SUPPORT_ON_OFF | SUPPORT_TARGET_TEMPERATURE)
SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_TARGET_TEMPERATURE)
OP_MODES = [
STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT
HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
HVAC_MODE_OFF
]
FAN_MODES = [
STATE_AUTO, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
HVAC_MODE_AUTO, SPEED_HIGH, SPEED_MEDIUM, SPEED_LOW
]
@ -61,15 +60,7 @@ class MelissaClimate(ClimateDevice):
return self._name
@property
def is_on(self):
"""Return current state."""
if self._cur_settings is not None:
return self._cur_settings[self._api.STATE] in (
self._api.STATE_ON, self._api.STATE_IDLE)
return None
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the current fan mode."""
if self._cur_settings is not None:
return self.melissa_fan_to_hass(
@ -93,19 +84,26 @@ class MelissaClimate(ClimateDevice):
return PRECISION_WHOLE
@property
def current_operation(self):
def hvac_mode(self):
"""Return the current operation mode."""
if self._cur_settings is not None:
return self.melissa_op_to_hass(
self._cur_settings[self._api.MODE])
if self._cur_settings is None:
return None
is_on = self._cur_settings[self._api.STATE] in (
self._api.STATE_ON, self._api.STATE_IDLE)
if not is_on:
return HVAC_MODE_OFF
return self.melissa_op_to_hass(self._cur_settings[self._api.MODE])
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return OP_MODES
@property
def fan_list(self):
def fan_modes(self):
"""List of available fan modes."""
return FAN_MODES
@ -116,13 +114,6 @@ class MelissaClimate(ClimateDevice):
return None
return self._cur_settings[self._api.TEMP]
@property
def state(self):
"""Return current state."""
if self._cur_settings is not None:
return self.melissa_state_to_hass(
self._cur_settings[self._api.STATE])
@property
def temperature_unit(self):
"""Return the unit of measurement which this thermostat uses."""
@ -153,19 +144,15 @@ class MelissaClimate(ClimateDevice):
melissa_fan_mode = self.hass_fan_to_melissa(fan_mode)
await self.async_send({self._api.FAN: melissa_fan_mode})
async def async_set_operation_mode(self, operation_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set operation mode."""
mode = self.hass_mode_to_melissa(operation_mode)
if hvac_mode == HVAC_MODE_OFF:
await self.async_send({self._api.STATE: self._api.STATE_OFF})
return
mode = self.hass_mode_to_melissa(hvac_mode)
await self.async_send({self._api.MODE: mode})
async def async_turn_on(self):
"""Turn on device."""
await self.async_send({self._api.STATE: self._api.STATE_ON})
async def async_turn_off(self):
"""Turn off device."""
await self.async_send({self._api.STATE: self._api.STATE_OFF})
async def async_send(self, value):
"""Send action to service."""
try:
@ -189,26 +176,16 @@ class MelissaClimate(ClimateDevice):
_LOGGER.warning(
'Unable to update entity %s', self.entity_id)
def melissa_state_to_hass(self, state):
"""Translate Melissa states to hass states."""
if state == self._api.STATE_ON:
return STATE_ON
if state == self._api.STATE_OFF:
return STATE_OFF
if state == self._api.STATE_IDLE:
return STATE_IDLE
return None
def melissa_op_to_hass(self, mode):
"""Translate Melissa modes to hass states."""
if mode == self._api.MODE_HEAT:
return STATE_HEAT
return HVAC_MODE_HEAT
if mode == self._api.MODE_COOL:
return STATE_COOL
return HVAC_MODE_COOL
if mode == self._api.MODE_DRY:
return STATE_DRY
return HVAC_MODE_DRY
if mode == self._api.MODE_FAN:
return STATE_FAN_ONLY
return HVAC_MODE_FAN_ONLY
_LOGGER.warning(
"Operation mode %s could not be mapped to hass", mode)
return None
@ -216,7 +193,7 @@ class MelissaClimate(ClimateDevice):
def melissa_fan_to_hass(self, fan):
"""Translate Melissa fan modes to hass modes."""
if fan == self._api.FAN_AUTO:
return STATE_AUTO
return HVAC_MODE_AUTO
if fan == self._api.FAN_LOW:
return SPEED_LOW
if fan == self._api.FAN_MEDIUM:
@ -228,19 +205,19 @@ class MelissaClimate(ClimateDevice):
def hass_mode_to_melissa(self, mode):
"""Translate hass states to melissa modes."""
if mode == STATE_HEAT:
if mode == HVAC_MODE_HEAT:
return self._api.MODE_HEAT
if mode == STATE_COOL:
if mode == HVAC_MODE_COOL:
return self._api.MODE_COOL
if mode == STATE_DRY:
if mode == HVAC_MODE_DRY:
return self._api.MODE_DRY
if mode == STATE_FAN_ONLY:
if mode == HVAC_MODE_FAN_ONLY:
return self._api.MODE_FAN
_LOGGER.warning("Melissa have no setting for %s mode", mode)
def hass_fan_to_melissa(self, fan):
"""Translate hass fan modes to melissa modes."""
if fan == STATE_AUTO:
if fan == HVAC_MODE_AUTO:
return self._api.FAN_AUTO
if fan == SPEED_LOW:
return self._api.FAN_LOW

View File

@ -1,17 +1,15 @@
"""Support for mill wifi-enabled home heaters."""
import logging
from mill import Mill
import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
DOMAIN, STATE_HEAT,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE)
DOMAIN, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE, FAN_ON)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME,
STATE_ON, STATE_OFF, TEMP_CELSIUS)
ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -25,8 +23,7 @@ MAX_TEMP = 35
MIN_TEMP = 5
SERVICE_SET_ROOM_TEMP = 'mill_set_room_temperature'
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_FAN_MODE)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
@ -44,7 +41,6 @@ SET_ROOM_TEMP_SCHEMA = vol.Schema({
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Mill heater."""
from mill import Mill
mill_data_connection = Mill(config[CONF_USERNAME],
config[CONF_PASSWORD],
websession=async_get_clientsession(hass))
@ -85,9 +81,7 @@ class MillHeater(ClimateDevice):
@property
def supported_features(self):
"""Return the list of supported features."""
if self._heater.is_gen1:
return SUPPORT_FLAGS
return SUPPORT_FLAGS | SUPPORT_ON_OFF | SUPPORT_OPERATION_MODE
return SUPPORT_FLAGS
@property
def available(self):
@ -141,21 +135,14 @@ class MillHeater(ClimateDevice):
return self._heater.current_temp
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return STATE_ON if self._heater.fan_status == 1 else STATE_OFF
return FAN_ON if self._heater.fan_status == 1 else HVAC_MODE_OFF
@property
def fan_list(self):
def fan_modes(self):
"""List of available fan modes."""
return [STATE_ON, STATE_OFF]
@property
def is_on(self):
"""Return true if heater is on."""
if self._heater.is_gen1:
return True
return self._heater.power_status == 1
return [FAN_ON, HVAC_MODE_OFF]
@property
def min_temp(self):
@ -168,50 +155,48 @@ class MillHeater(ClimateDevice):
return MAX_TEMP
@property
def current_operation(self):
"""Return current operation."""
return STATE_HEAT if self.is_on else STATE_OFF
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if self._heater.is_gen1 or self._heater.power_status == 1:
return HVAC_MODE_HEAT
return HVAC_MODE_OFF
@property
def operation_list(self):
"""List of available operation modes."""
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
if self._heater.is_gen1:
return None
return [STATE_HEAT, STATE_OFF]
return [HVAC_MODE_HEAT]
return [HVAC_MODE_HEAT, HVAC_MODE_OFF]
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
await self._conn.set_heater_temp(self._heater.device_id,
int(temperature))
await self._conn.set_heater_temp(
self._heater.device_id, int(temperature))
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
fan_status = 1 if fan_mode == STATE_ON else 0
await self._conn.heater_control(self._heater.device_id,
fan_status=fan_status)
fan_status = 1 if fan_mode == FAN_ON else 0
await self._conn.heater_control(
self._heater.device_id, fan_status=fan_status)
async def async_turn_on(self):
"""Turn Mill unit on."""
await self._conn.heater_control(self._heater.device_id,
power_status=1)
async def async_turn_off(self):
"""Turn Mill unit off."""
await self._conn.heater_control(self._heater.device_id,
power_status=0)
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if hvac_mode == HVAC_MODE_HEAT:
await self._conn.heater_control(
self._heater.device_id, power_status=1)
elif hvac_mode == HVAC_MODE_OFF and not self._heater.is_gen1:
await self._conn.heater_control(
self._heater.device_id, power_status=0)
async def async_update(self):
"""Retrieve latest state."""
self._heater = await self._conn.update_device(self._heater.device_id)
async def async_set_operation_mode(self, operation_mode):
"""Set operation mode."""
if operation_mode == STATE_HEAT:
await self.async_turn_on()
elif operation_mode == STATE_OFF and not self._heater.is_gen1:
await self.async_turn_off()
else:
_LOGGER.error("Unrecognized operation mode: %s", operation_mode)

View File

@ -5,7 +5,8 @@ import struct
import voluptuous as vol
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE
from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_HEAT)
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, CONF_SLAVE
import homeassistant.helpers.config_validation as cv
@ -23,6 +24,7 @@ DATA_TYPE_INT = 'int'
DATA_TYPE_UINT = 'uint'
DATA_TYPE_FLOAT = 'float'
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
HVAC_MODES = [HVAC_MODE_HEAT]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_CURRENT_TEMP): cv.positive_int,
@ -93,6 +95,16 @@ class ModbusThermostat(ClimateDevice):
self._current_temperature = self.read_register(
self._current_temperature_register)
@property
def hvac_mode(self):
"""Return the current HVAC mode."""
return HVAC_MODE_HEAT
@property
def hvac_modes(self):
"""Return the possible HVAC modes."""
return HVAC_MODES
@property
def name(self):
"""Return the name of the climate device."""

View File

@ -7,17 +7,15 @@ from homeassistant.components import climate, mqtt
from homeassistant.components.climate import (
PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, ClimateDevice)
from homeassistant.components.climate.const import (
ATTR_OPERATION_MODE, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, STATE_AUTO,
STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT, SUPPORT_AUX_HEAT,
SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, SUPPORT_HOLD_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE,
ATTR_TARGET_TEMP_LOW,
ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
SUPPORT_TARGET_TEMPERATURE_HIGH)
ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, HVAC_MODE_AUTO, HVAC_MODE_COOL,
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF,
SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE,
SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, PRESET_AWAY,
SUPPORT_TARGET_TEMPERATURE_RANGE)
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_OFF,
STATE_ON)
ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_ON)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -48,6 +46,7 @@ CONF_FAN_MODE_STATE_TOPIC = 'fan_mode_state_topic'
CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic'
CONF_HOLD_STATE_TEMPLATE = 'hold_state_template'
CONF_HOLD_STATE_TOPIC = 'hold_state_topic'
CONF_HOLD_LIST = 'hold_modes'
CONF_MODE_COMMAND_TOPIC = 'mode_command_topic'
CONF_MODE_LIST = 'modes'
CONF_MODE_STATE_TEMPLATE = 'mode_state_template'
@ -127,17 +126,19 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_FAN_MODE_LIST,
default=[STATE_AUTO, SPEED_LOW,
default=[HVAC_MODE_AUTO, SPEED_LOW,
SPEED_MEDIUM, SPEED_HIGH]): cv.ensure_list,
vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_HOLD_LIST, default=list): cv.ensure_list,
vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_MODE_LIST,
default=[STATE_AUTO, STATE_OFF, STATE_COOL, STATE_HEAT,
STATE_DRY, STATE_FAN_ONLY]): cv.ensure_list,
default=[HVAC_MODE_AUTO, HVAC_MODE_OFF, HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY]): cv.ensure_list,
vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@ -150,7 +151,7 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_SWING_MODE_LIST,
default=[STATE_ON, STATE_OFF]): cv.ensure_list,
default=[STATE_ON, HVAC_MODE_OFF]): cv.ensure_list,
vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_TEMP_INITIAL, default=21): cv.positive_int,
@ -275,9 +276,9 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None:
self._current_fan_mode = SPEED_LOW
if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None:
self._current_swing_mode = STATE_OFF
self._current_swing_mode = HVAC_MODE_OFF
if self._topic[CONF_MODE_STATE_TOPIC] is None:
self._current_operation = STATE_OFF
self._current_operation = HVAC_MODE_OFF
self._away = False
self._hold = None
self._aux = False
@ -442,6 +443,9 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""Handle receiving hold mode via MQTT."""
payload = render_template(msg, CONF_HOLD_STATE_TEMPLATE)
if payload == 'off':
payload = None
self._hold = payload
self.async_write_ha_state()
@ -500,12 +504,12 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
return self._target_temp_high
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return self._config[CONF_MODE_LIST]
@ -515,27 +519,39 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
return self._config[CONF_TEMP_STEP]
@property
def is_away_mode_on(self):
"""Return if away mode is on."""
return self._away
def preset_mode(self):
"""Return preset mode."""
if self._hold:
return self._hold
if self._away:
return PRESET_AWAY
return None
@property
def current_hold_mode(self):
"""Return hold mode setting."""
return self._hold
def preset_modes(self):
"""Return preset modes."""
presets = []
if (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) or \
(self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None):
presets.append(PRESET_AWAY)
presets.extend(self._config[CONF_HOLD_LIST])
return presets
@property
def is_aux_heat_on(self):
def is_aux_heat(self):
"""Return true if away mode is on."""
return self._aux
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self._current_fan_mode
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return self._config[CONF_FAN_MODE_LIST]
@ -552,14 +568,14 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
setattr(self, attr, temp)
if (self._config[CONF_SEND_IF_OFF] or
self._current_operation != STATE_OFF):
self._current_operation != HVAC_MODE_OFF):
self._publish(cmnd_topic, temp)
async def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
if kwargs.get(ATTR_OPERATION_MODE) is not None:
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
await self.async_set_operation_mode(operation_mode)
if kwargs.get(ATTR_HVAC_MODE) is not None:
operation_mode = kwargs.get(ATTR_HVAC_MODE)
await self.async_set_hvac_mode(operation_mode)
self._set_temperature(
kwargs.get(ATTR_TEMPERATURE), CONF_TEMP_COMMAND_TOPIC,
@ -579,7 +595,7 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
async def async_set_swing_mode(self, swing_mode):
"""Set new swing mode."""
if (self._config[CONF_SEND_IF_OFF] or
self._current_operation != STATE_OFF):
self._current_operation != HVAC_MODE_OFF):
self._publish(CONF_SWING_MODE_COMMAND_TOPIC,
swing_mode)
@ -590,7 +606,7 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
async def async_set_fan_mode(self, fan_mode):
"""Set new target temperature."""
if (self._config[CONF_SEND_IF_OFF] or
self._current_operation != STATE_OFF):
self._current_operation != HVAC_MODE_OFF):
self._publish(CONF_FAN_MODE_COMMAND_TOPIC,
fan_mode)
@ -598,58 +614,83 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
self._current_fan_mode = fan_mode
self.async_write_ha_state()
async def async_set_operation_mode(self, operation_mode) -> None:
async def async_set_hvac_mode(self, hvac_mode) -> None:
"""Set new operation mode."""
if (self._current_operation == STATE_OFF and
operation_mode != STATE_OFF):
if (self._current_operation == HVAC_MODE_OFF and
hvac_mode != HVAC_MODE_OFF):
self._publish(CONF_POWER_COMMAND_TOPIC,
self._config[CONF_PAYLOAD_ON])
elif (self._current_operation != STATE_OFF and
operation_mode == STATE_OFF):
elif (self._current_operation != HVAC_MODE_OFF and
hvac_mode == HVAC_MODE_OFF):
self._publish(CONF_POWER_COMMAND_TOPIC,
self._config[CONF_PAYLOAD_OFF])
self._publish(CONF_MODE_COMMAND_TOPIC,
operation_mode)
hvac_mode)
if self._topic[CONF_MODE_STATE_TOPIC] is None:
self._current_operation = operation_mode
self._current_operation = hvac_mode
self.async_write_ha_state()
@property
def current_swing_mode(self):
def swing_mode(self):
"""Return the swing setting."""
return self._current_swing_mode
@property
def swing_list(self):
def swing_modes(self):
"""List of available swing modes."""
return self._config[CONF_SWING_MODE_LIST]
async def async_set_preset_mode(self, preset_mode):
"""Set a preset mode."""
if preset_mode == self.preset_mode:
return
# Track if we should optimistic update the state
optimistic_update = False
if self._away:
optimistic_update = optimistic_update or self._set_away_mode(False)
elif preset_mode == PRESET_AWAY:
optimistic_update = optimistic_update or self._set_away_mode(True)
if self._hold:
optimistic_update = optimistic_update or self._set_hold_mode(None)
elif preset_mode not in (None, PRESET_AWAY):
optimistic_update = (optimistic_update or
self._set_hold_mode(preset_mode))
if optimistic_update:
self.async_write_ha_state()
def _set_away_mode(self, state):
"""Set away mode.
Returns if we should optimistically write the state.
"""
self._publish(CONF_AWAY_MODE_COMMAND_TOPIC,
self._config[CONF_PAYLOAD_ON] if state
else self._config[CONF_PAYLOAD_OFF])
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None:
self._away = state
self.async_write_ha_state()
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None:
return False
async def async_turn_away_mode_on(self):
"""Turn away mode on."""
self._set_away_mode(True)
self._away = state
return True
async def async_turn_away_mode_off(self):
"""Turn away mode off."""
self._set_away_mode(False)
def _set_hold_mode(self, hold_mode):
"""Set hold mode.
async def async_set_hold_mode(self, hold_mode):
"""Update hold mode on."""
self._publish(CONF_HOLD_COMMAND_TOPIC, hold_mode)
Returns if we should optimistically write the state.
"""
self._publish(CONF_HOLD_COMMAND_TOPIC, hold_mode or "off")
if self._topic[CONF_HOLD_STATE_TOPIC] is None:
self._hold = hold_mode
self.async_write_ha_state()
if self._topic[CONF_HOLD_STATE_TOPIC] is not None:
return False
self._hold = hold_mode
return True
def _set_aux_heat(self, state):
self._publish(CONF_AUX_COMMAND_TOPIC,
@ -679,15 +720,11 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
if (self._topic[CONF_TEMP_LOW_STATE_TOPIC] is not None) or \
(self._topic[CONF_TEMP_LOW_COMMAND_TOPIC] is not None):
support |= SUPPORT_TARGET_TEMPERATURE_LOW
support |= SUPPORT_TARGET_TEMPERATURE_RANGE
if (self._topic[CONF_TEMP_HIGH_STATE_TOPIC] is not None) or \
(self._topic[CONF_TEMP_HIGH_COMMAND_TOPIC] is not None):
support |= SUPPORT_TARGET_TEMPERATURE_HIGH
if (self._topic[CONF_MODE_COMMAND_TOPIC] is not None) or \
(self._topic[CONF_MODE_STATE_TOPIC] is not None):
support |= SUPPORT_OPERATION_MODE
support |= SUPPORT_TARGET_TEMPERATURE_RANGE
if (self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None) or \
(self._topic[CONF_FAN_MODE_COMMAND_TOPIC] is not None):
@ -698,12 +735,10 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
support |= SUPPORT_SWING_MODE
if (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) or \
(self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None):
support |= SUPPORT_AWAY_MODE
if (self._topic[CONF_HOLD_STATE_TOPIC] is not None) or \
(self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None) or \
(self._topic[CONF_HOLD_STATE_TOPIC] is not None) or \
(self._topic[CONF_HOLD_COMMAND_TOPIC] is not None):
support |= SUPPORT_HOLD_MODE
support |= SUPPORT_PRESET_MODE
if (self._topic[CONF_AUX_STATE_TOPIC] is not None) or \
(self._topic[CONF_AUX_COMMAND_TOPIC] is not None):

View File

@ -2,28 +2,30 @@
from homeassistant.components import mysensors
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO,
STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, HVAC_MODE_AUTO,
HVAC_MODE_COOL, HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE,
HVAC_MODE_OFF)
from homeassistant.const import (
ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
DICT_HA_TO_MYS = {
STATE_AUTO: 'AutoChangeOver',
STATE_COOL: 'CoolOn',
STATE_HEAT: 'HeatOn',
STATE_OFF: 'Off',
HVAC_MODE_AUTO: 'AutoChangeOver',
HVAC_MODE_COOL: 'CoolOn',
HVAC_MODE_HEAT: 'HeatOn',
HVAC_MODE_OFF: 'Off',
}
DICT_MYS_TO_HA = {
'AutoChangeOver': STATE_AUTO,
'CoolOn': STATE_COOL,
'HeatOn': STATE_HEAT,
'Off': STATE_OFF,
'AutoChangeOver': HVAC_MODE_AUTO,
'CoolOn': HVAC_MODE_COOL,
'HeatOn': HVAC_MODE_HEAT,
'Off': HVAC_MODE_OFF,
}
FAN_LIST = ['Auto', 'Min', 'Normal', 'Max']
OPERATION_LIST = [STATE_OFF, STATE_AUTO, STATE_COOL, STATE_HEAT]
OPERATION_LIST = [HVAC_MODE_OFF, HVAC_MODE_AUTO, HVAC_MODE_COOL,
HVAC_MODE_HEAT]
async def async_setup_platform(
@ -40,15 +42,14 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice):
@property
def supported_features(self):
"""Return the list of supported features."""
features = SUPPORT_OPERATION_MODE
features = 0
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SPEED in self._values:
features = features | SUPPORT_FAN_MODE
if (set_req.V_HVAC_SETPOINT_COOL in self._values and
set_req.V_HVAC_SETPOINT_HEAT in self._values):
features = (
features | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW)
features | SUPPORT_TARGET_TEMPERATURE_RANGE)
else:
features = features | SUPPORT_TARGET_TEMPERATURE
return features
@ -102,22 +103,22 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice):
return float(temp) if temp is not None else None
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self._values.get(self.value_type)
@property
def operation_list(self):
def hvac_modes(self):
"""List of available operation modes."""
return OPERATION_LIST
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self._values.get(self.gateway.const.SetReq.V_HVAC_SPEED)
@property
def fan_list(self):
def fan_modes(self):
"""List of available fan modes."""
return FAN_LIST
@ -161,14 +162,14 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice):
self._values[set_req.V_HVAC_SPEED] = fan_mode
self.async_schedule_update_ha_state()
async def async_set_operation_mode(self, operation_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target temperature."""
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type,
DICT_HA_TO_MYS[operation_mode])
DICT_HA_TO_MYS[hvac_mode])
if self.gateway.optimistic:
# Optimistically assume that device has changed state
self._values[self.value_type] = operation_mode
self._values[self.value_type] = hvac_mode
self.async_schedule_update_ha_state()
async def async_update(self):

View File

@ -7,8 +7,6 @@ import threading
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components.climate.const import (
ATTR_AWAY_MODE, SERVICE_SET_AWAY_MODE)
from homeassistant.const import (
CONF_BINARY_SENSORS, CONF_FILENAME, CONF_MONITORED_CONDITIONS,
CONF_SENSORS, CONF_STRUCTURE, EVENT_HOMEASSISTANT_START,
@ -45,6 +43,9 @@ ATTR_TRIP_ID = 'trip_id'
AWAY_MODE_AWAY = 'away'
AWAY_MODE_HOME = 'home'
ATTR_AWAY_MODE = 'away_mode'
SERVICE_SET_AWAY_MODE = 'set_away_mode'
SENSOR_SCHEMA = vol.Schema({
vol.Optional(CONF_MONITORED_CONDITIONS): vol.All(cv.ensure_list),
})

View File

@ -5,13 +5,12 @@ import voluptuous as vol
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, STATE_AUTO, STATE_COOL,
STATE_ECO, STATE_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, FAN_AUTO, FAN_ON,
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF,
SUPPORT_PRESET_MODE, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE, PRESET_AWAY, PRESET_ECO)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_SCAN_INTERVAL, STATE_OFF, STATE_ON, TEMP_CELSIUS,
TEMP_FAHRENHEIT)
ATTR_TEMPERATURE, CONF_SCAN_INTERVAL, TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import DATA_NEST, DOMAIN as NEST_DOMAIN, SIGNAL_NEST_UPDATE
@ -24,6 +23,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
NEST_MODE_HEAT_COOL = 'heat-cool'
NEST_MODE_ECO = 'eco'
NEST_MODE_HEAT = 'heat'
NEST_MODE_COOL = 'cool'
NEST_MODE_OFF = 'off'
PRESET_MODES = [PRESET_AWAY, PRESET_ECO]
def setup_platform(hass, config, add_entities, discovery_info=None):
@ -53,29 +58,28 @@ class NestThermostat(ClimateDevice):
self._unit = temp_unit
self.structure = structure
self.device = device
self._fan_list = [STATE_ON, STATE_AUTO]
self._fan_modes = [FAN_ON, FAN_AUTO]
# Set the default supported features
self._support_flags = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE)
SUPPORT_PRESET_MODE)
# Not all nest devices support cooling and heating remove unused
self._operation_list = [STATE_OFF]
self._operation_list = []
if self.device.can_heat and self.device.can_cool:
self._operation_list.append(HVAC_MODE_AUTO)
self._support_flags = (self._support_flags |
SUPPORT_TARGET_TEMPERATURE_RANGE)
# Add supported nest thermostat features
if self.device.can_heat:
self._operation_list.append(STATE_HEAT)
self._operation_list.append(HVAC_MODE_HEAT)
if self.device.can_cool:
self._operation_list.append(STATE_COOL)
self._operation_list.append(HVAC_MODE_COOL)
if self.device.can_heat and self.device.can_cool:
self._operation_list.append(STATE_AUTO)
self._support_flags = (self._support_flags |
SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW)
self._operation_list.append(STATE_ECO)
self._operation_list.append(HVAC_MODE_OFF)
# feature of device
self._has_fan = self.device.has_fan
@ -151,25 +155,29 @@ class NestThermostat(ClimateDevice):
return self._temperature
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
if self._mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]:
if self._mode in \
(NEST_MODE_HEAT, NEST_MODE_COOL, NEST_MODE_OFF):
return self._mode
if self._mode == NEST_MODE_ECO:
# We assume the first operation in operation list is the main one
return self._operation_list[0]
if self._mode == NEST_MODE_HEAT_COOL:
return STATE_AUTO
return HVAC_MODE_AUTO
return None
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._mode not in (NEST_MODE_HEAT_COOL, STATE_ECO):
if self._mode not in (NEST_MODE_HEAT_COOL, NEST_MODE_ECO):
return self._target_temperature
return None
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
if self._mode == STATE_ECO:
if self._mode == NEST_MODE_ECO:
return self._eco_temperature[0]
if self._mode == NEST_MODE_HEAT_COOL:
return self._target_temperature[0]
@ -178,17 +186,12 @@ class NestThermostat(ClimateDevice):
@property
def target_temperature_high(self):
"""Return the upper bound temperature we try to reach."""
if self._mode == STATE_ECO:
if self._mode == NEST_MODE_ECO:
return self._eco_temperature[1]
if self._mode == NEST_MODE_HEAT_COOL:
return self._target_temperature[1]
return None
@property
def is_away_mode_on(self):
"""Return if away mode is on."""
return self._away
def set_temperature(self, **kwargs):
"""Set new target temperature."""
import nest
@ -211,46 +214,69 @@ class NestThermostat(ClimateDevice):
# restore target temperature
self.schedule_update_ha_state(True)
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set operation mode."""
if operation_mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]:
device_mode = operation_mode
elif operation_mode == STATE_AUTO:
if hvac_mode in (HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF):
device_mode = hvac_mode
elif hvac_mode == HVAC_MODE_AUTO:
device_mode = NEST_MODE_HEAT_COOL
else:
device_mode = STATE_OFF
device_mode = HVAC_MODE_OFF
_LOGGER.error(
"An error occurred while setting device mode. "
"Invalid operation mode: %s", operation_mode)
"Invalid operation mode: %s", hvac_mode)
self.device.mode = device_mode
@property
def operation_list(self):
def hvac_modes(self):
"""List of available operation modes."""
return self._operation_list
def turn_away_mode_on(self):
"""Turn away on."""
self.structure.away = True
@property
def preset_mode(self):
"""Return current preset mode."""
if self._away:
return PRESET_AWAY
def turn_away_mode_off(self):
"""Turn away off."""
self.structure.away = False
if self._mode == NEST_MODE_ECO:
return PRESET_ECO
return None
@property
def current_fan_mode(self):
def preset_modes(self):
"""Return preset modes."""
return PRESET_MODES
def set_preset_mode(self, preset_mode):
"""Set preset mode."""
if preset_mode == self.preset_mode:
return
if self._away:
self.structure.away = False
elif preset_mode == PRESET_AWAY:
self.structure.away = True
if self.preset_mode == PRESET_ECO:
self.device.mode = self._operation_list[0]
elif preset_mode == PRESET_ECO:
self.device.mode = NEST_MODE_ECO
@property
def fan_mode(self):
"""Return whether the fan is on."""
if self._has_fan:
# Return whether the fan is on
return STATE_ON if self._fan else STATE_AUTO
return FAN_ON if self._fan else FAN_AUTO
# No Fan available so disable slider
return None
@property
def fan_list(self):
def fan_modes(self):
"""List of available fan modes."""
if self._has_fan:
return self._fan_list
return self._fan_modes
return None
def set_fan_mode(self, fan_mode):

View File

@ -1,14 +1,13 @@
"""Support for Nest Thermostat sensors."""
import logging
from homeassistant.components.climate.const import STATE_COOL, STATE_HEAT
from homeassistant.const import (
CONF_MONITORED_CONDITIONS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE,
STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
from . import CONF_SENSORS, DATA_NEST, DATA_NEST_CONFIG, NestSensorDevice
SENSOR_TYPES = ['humidity', 'operation_mode', 'hvac_state']
SENSOR_TYPES = ['humidity', 'operation_mode', 'hvac_mode']
TEMP_SENSOR_TYPES = ['temperature', 'target']
@ -20,6 +19,9 @@ PROTECT_SENSOR_TYPES = ['co_status',
STRUCTURE_SENSOR_TYPES = ['eta']
STATE_HEAT = 'heat'
STATE_COOL = 'cool'
# security_state is structure level sensor, but only meaningful when
# Nest Cam exist
STRUCTURE_CAMERA_SENSOR_TYPES = ['security_state']
@ -34,7 +36,7 @@ SENSOR_DEVICE_CLASSES = {'humidity': DEVICE_CLASS_HUMIDITY}
VARIABLE_NAME_MAPPING = {'eta': 'eta_begin', 'operation_mode': 'mode'}
VALUE_MAPPING = {
'hvac_state': {
'hvac_mode': {
'heating': STATE_HEAT, 'cooling': STATE_COOL, 'off': STATE_OFF}}
SENSOR_TYPES_DEPRECATED = ['last_ip',

View File

@ -1,6 +1,7 @@
"""Support for Netatmo Smart thermostats."""
import logging
from datetime import timedelta
import logging
from typing import Optional, List
import requests
import voluptuous as vol
@ -8,21 +9,54 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate.const import (
STATE_HEAT, SUPPORT_ON_OFF, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, STATE_MANUAL, STATE_AUTO,
STATE_ECO, STATE_COOL)
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF,
PRESET_AWAY,
CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE
)
from homeassistant.const import (
STATE_OFF, TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_NAME)
TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_NAME, PRECISION_HALVES)
from homeassistant.util import Throttle
from .const import DATA_NETATMO_AUTH
_LOGGER = logging.getLogger(__name__)
PRESET_FROST_GUARD = 'frost_guard'
PRESET_MAX = 'max'
PRESET_SCHEDULE = 'schedule'
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE)
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF]
SUPPORT_PRESET = [
PRESET_AWAY, PRESET_FROST_GUARD, PRESET_SCHEDULE, PRESET_MAX,
]
STATE_NETATMO_SCHEDULE = 'schedule'
STATE_NETATMO_HG = 'hg'
STATE_NETATMO_MAX = PRESET_MAX
STATE_NETATMO_AWAY = PRESET_AWAY
STATE_NETATMO_OFF = "off"
STATE_NETATMO_MANUAL = 'manual'
HVAC_MAP_NETATMO = {
STATE_NETATMO_SCHEDULE: HVAC_MODE_AUTO,
STATE_NETATMO_HG: HVAC_MODE_AUTO,
STATE_NETATMO_MAX: HVAC_MODE_HEAT,
STATE_NETATMO_OFF: HVAC_MODE_OFF,
STATE_NETATMO_MANUAL: HVAC_MODE_AUTO,
STATE_NETATMO_AWAY: HVAC_MODE_AUTO
}
CURRENT_HVAC_MAP_NETATMO = {
True: CURRENT_HVAC_HEAT,
False: CURRENT_HVAC_IDLE,
}
CONF_HOMES = 'homes'
CONF_ROOMS = 'rooms'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
HOME_CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
@ -33,34 +67,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOMES): vol.All(cv.ensure_list, [HOME_CONFIG_SCHEMA])
})
STATE_NETATMO_SCHEDULE = 'schedule'
STATE_NETATMO_HG = 'hg'
STATE_NETATMO_MAX = 'max'
STATE_NETATMO_AWAY = 'away'
STATE_NETATMO_OFF = STATE_OFF
STATE_NETATMO_MANUAL = STATE_MANUAL
DICT_NETATMO_TO_HA = {
STATE_NETATMO_SCHEDULE: STATE_AUTO,
STATE_NETATMO_HG: STATE_COOL,
STATE_NETATMO_MAX: STATE_HEAT,
STATE_NETATMO_AWAY: STATE_ECO,
STATE_NETATMO_OFF: STATE_OFF,
STATE_NETATMO_MANUAL: STATE_MANUAL
}
DICT_HA_TO_NETATMO = {
STATE_AUTO: STATE_NETATMO_SCHEDULE,
STATE_COOL: STATE_NETATMO_HG,
STATE_HEAT: STATE_NETATMO_MAX,
STATE_ECO: STATE_NETATMO_AWAY,
STATE_OFF: STATE_NETATMO_OFF,
STATE_MANUAL: STATE_NETATMO_MANUAL
}
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_AWAY_MODE)
NA_THERM = 'NATherm1'
NA_VALVE = 'NRV'
@ -115,28 +121,26 @@ class NetatmoThermostat(ClimateDevice):
self._data = data
self._state = None
self._room_id = room_id
room_name = self._data.homedata.rooms[self._data.home][room_id]['name']
self._name = 'netatmo_{}'.format(room_name)
self._room_name = self._data.homedata.rooms[
self._data.home][room_id]['name']
self._name = 'netatmo_{}'.format(self._room_name)
self._current_temperature = None
self._target_temperature = None
self._preset = None
self._away = None
self._module_type = self._data.room_status[room_id]['module_type']
if self._module_type == NA_VALVE:
self._operation_list = [DICT_NETATMO_TO_HA[STATE_NETATMO_SCHEDULE],
DICT_NETATMO_TO_HA[STATE_NETATMO_MANUAL],
DICT_NETATMO_TO_HA[STATE_NETATMO_AWAY],
DICT_NETATMO_TO_HA[STATE_NETATMO_HG]]
self._support_flags = SUPPORT_FLAGS
elif self._module_type == NA_THERM:
self._operation_list = [DICT_NETATMO_TO_HA[STATE_NETATMO_SCHEDULE],
DICT_NETATMO_TO_HA[STATE_NETATMO_MANUAL],
DICT_NETATMO_TO_HA[STATE_NETATMO_AWAY],
DICT_NETATMO_TO_HA[STATE_NETATMO_HG],
DICT_NETATMO_TO_HA[STATE_NETATMO_MAX],
DICT_NETATMO_TO_HA[STATE_NETATMO_OFF]]
self._support_flags = SUPPORT_FLAGS | SUPPORT_ON_OFF
self._operation_mode = None
self._operation_list = [HVAC_MODE_AUTO, HVAC_MODE_HEAT]
self._support_flags = SUPPORT_FLAGS
self._hvac_mode = None
self.update_without_throttle = False
try:
self._module_type = self._data.room_status[room_id]['module_type']
except KeyError:
_LOGGER.error("Thermostat in %s not available", room_id)
if self._module_type == NA_THERM:
self._operation_list.append(HVAC_MODE_OFF)
@property
def supported_features(self):
"""Return the list of supported features."""
@ -155,113 +159,86 @@ class NetatmoThermostat(ClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
return self._data.room_status[self._room_id]['current_temperature']
return self._current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._data.room_status[self._room_id]['target_temperature']
return self._target_temperature
@property
def current_operation(self):
"""Return the current state of the thermostat."""
return self._operation_mode
def target_temperature_step(self) -> Optional[float]:
"""Return the supported step of target temperature."""
return PRECISION_HALVES
@property
def operation_list(self):
"""Return the operation modes list."""
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode."""
return self._hvac_mode
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes."""
return self._operation_list
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
module_type = self._data.room_status[self._room_id]['module_type']
if module_type not in (NA_THERM, NA_VALVE):
return {}
state_attributes = {
"home_id": self._data.homedata.gethomeId(self._data.home),
"room_id": self._room_id,
"setpoint_default_duration": self._data.setpoint_duration,
"away_temperature": self._data.away_temperature,
"hg_temperature": self._data.hg_temperature,
"operation_mode": self._operation_mode,
"module_type": module_type,
"module_id": self._data.room_status[self._room_id]['module_id']
}
if module_type == NA_THERM:
state_attributes["boiler_status"] = self._data.boilerstatus
elif module_type == NA_VALVE:
state_attributes["heating_power_request"] = \
self._data.room_status[self._room_id]['heating_power_request']
return state_attributes
def hvac_action(self) -> Optional[str]:
"""Return the current running hvac operation if supported."""
if self._module_type == NA_THERM:
return CURRENT_HVAC_MAP_NETATMO[self._data.boilerstatus]
# Maybe it is a valve
if self._data.room_status[self._room_id]['heating_power_request'] > 0:
return CURRENT_HVAC_HEAT
return CURRENT_HVAC_IDLE
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
def set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
mode = None
@property
def is_on(self):
"""Return true if on."""
return self.target_temperature > 0
if hvac_mode == HVAC_MODE_OFF:
mode = STATE_NETATMO_OFF
elif hvac_mode == HVAC_MODE_AUTO:
mode = STATE_NETATMO_SCHEDULE
elif hvac_mode == HVAC_MODE_HEAT:
mode = STATE_NETATMO_MAX
def turn_away_mode_on(self):
"""Turn away on."""
self.set_operation_mode(DICT_NETATMO_TO_HA[STATE_NETATMO_AWAY])
self.set_preset_mode(mode)
def turn_away_mode_off(self):
"""Turn away off."""
self.set_operation_mode(DICT_NETATMO_TO_HA[STATE_NETATMO_SCHEDULE])
def turn_off(self):
"""Turn Netatmo off."""
_LOGGER.debug("Switching off ...")
self.set_operation_mode(DICT_NETATMO_TO_HA[STATE_NETATMO_OFF])
self.update_without_throttle = True
self.schedule_update_ha_state()
def turn_on(self):
"""Turn Netatmo on."""
_LOGGER.debug("Switching on ...")
_LOGGER.debug("Setting temperature first to %d ...",
self._data.hg_temperature)
self._data.homestatus.setroomThermpoint(
self._data.homedata.gethomeId(self._data.home),
self._room_id, STATE_NETATMO_MANUAL, self._data.hg_temperature)
_LOGGER.debug("Setting operation mode to schedule ...")
self._data.homestatus.setThermmode(
self._data.homedata.gethomeId(self._data.home),
STATE_NETATMO_SCHEDULE)
self.update_without_throttle = True
self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
if not self.is_on:
self.turn_on()
if operation_mode in [DICT_NETATMO_TO_HA[STATE_NETATMO_MAX],
DICT_NETATMO_TO_HA[STATE_NETATMO_OFF]]:
def set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
if preset_mode is None:
self._data.homestatus.setroomThermpoint(
self._data.homedata.gethomeId(self._data.home),
self._room_id, DICT_HA_TO_NETATMO[operation_mode])
elif operation_mode in [DICT_NETATMO_TO_HA[STATE_NETATMO_HG],
DICT_NETATMO_TO_HA[STATE_NETATMO_SCHEDULE],
DICT_NETATMO_TO_HA[STATE_NETATMO_AWAY]]:
self._data.home_id, self._room_id, "off"
)
if preset_mode == STATE_NETATMO_MAX:
self._data.homestatus.setroomThermpoint(
self._data.home_id, self._room_id, preset_mode
)
elif preset_mode in [
STATE_NETATMO_SCHEDULE, STATE_NETATMO_HG, STATE_NETATMO_AWAY
]:
self._data.homestatus.setThermmode(
self._data.homedata.gethomeId(self._data.home),
DICT_HA_TO_NETATMO[operation_mode])
self.update_without_throttle = True
self.schedule_update_ha_state()
self._data.home_id, preset_mode
)
@property
def preset_mode(self) -> Optional[str]:
"""Return the current preset mode, e.g., home, away, temp."""
return self._preset
@property
def preset_modes(self) -> Optional[List[str]]:
"""Return a list of available preset modes."""
return SUPPORT_PRESET
def set_temperature(self, **kwargs):
"""Set new target temperature for 2 hours."""
temp = kwargs.get(ATTR_TEMPERATURE)
if temp is None:
return
mode = STATE_NETATMO_MANUAL
self._data.homestatus.setroomThermpoint(
self._data.homedata.gethomeId(self._data.home),
self._room_id, DICT_HA_TO_NETATMO[mode], temp)
self._room_id, STATE_NETATMO_MANUAL, temp)
self.update_without_throttle = True
self.schedule_update_ha_state()
@ -277,12 +254,20 @@ class NetatmoThermostat(ClimateDevice):
_LOGGER.error("NetatmoThermostat::update() "
"got exception.")
return
self._target_temperature = \
self._data.room_status[self._room_id]['target_temperature']
self._operation_mode = DICT_NETATMO_TO_HA[
self._data.room_status[self._room_id]['setpoint_mode']]
self._away = self._operation_mode == DICT_NETATMO_TO_HA[
STATE_NETATMO_AWAY]
try:
self._current_temperature = \
self._data.room_status[self._room_id]['current_temperature']
self._target_temperature = \
self._data.room_status[self._room_id]['target_temperature']
self._preset = \
self._data.room_status[self._room_id]["setpoint_mode"]
except KeyError:
_LOGGER.error(
"The thermostat in room %s seems to be out of reach.",
self._room_id
)
self._hvac_mode = HVAC_MAP_NETATMO[self._preset]
self._away = self._hvac_mode == HVAC_MAP_NETATMO[STATE_NETATMO_AWAY]
class HomeData:

View File

@ -6,8 +6,8 @@ import voluptuous as vol
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
DOMAIN, STATE_AUTO, STATE_HEAT, STATE_IDLE, SUPPORT_HOLD_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
@ -17,29 +17,26 @@ from . import DOMAIN as NUHEAT_DOMAIN
_LOGGER = logging.getLogger(__name__)
ICON = "mdi:thermometer"
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
# Hold modes
MODE_AUTO = STATE_AUTO # Run device schedule
MODE_AUTO = HVAC_MODE_AUTO # Run device schedule
MODE_HOLD_TEMPERATURE = "temperature"
MODE_TEMPORARY_HOLD = "temporary_temperature"
OPERATION_LIST = [STATE_HEAT, STATE_IDLE]
OPERATION_LIST = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
SCHEDULE_HOLD = 3
SCHEDULE_RUN = 1
SCHEDULE_TEMPORARY_HOLD = 2
SERVICE_RESUME_PROGRAM = "nuheat_resume_program"
SERVICE_RESUME_PROGRAM = "resume_program"
RESUME_PROGRAM_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids
})
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_HOLD_MODE |
SUPPORT_OPERATION_MODE)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE)
def setup_platform(hass, config, add_entities, discovery_info=None):
@ -70,7 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
thermostat.schedule_update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_RESUME_PROGRAM, resume_program_set_service,
NUHEAT_DOMAIN, SERVICE_RESUME_PROGRAM, resume_program_set_service,
schema=RESUME_PROGRAM_SCHEMA)
@ -88,11 +85,6 @@ class NuHeatThermostat(ClimateDevice):
"""Return the name of the thermostat."""
return self._thermostat.room
@property
def icon(self):
"""Return the icon to use in the frontend."""
return ICON
@property
def supported_features(self):
"""Return the list of supported features."""
@ -115,12 +107,12 @@ class NuHeatThermostat(ClimateDevice):
return self._thermostat.fahrenheit
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation. ie. heat, idle."""
if self._thermostat.heating:
return STATE_HEAT
return HVAC_MODE_HEAT
return STATE_IDLE
return HVAC_MODE_OFF
@property
def min_temp(self):
@ -147,8 +139,8 @@ class NuHeatThermostat(ClimateDevice):
return self._thermostat.target_fahrenheit
@property
def current_hold_mode(self):
"""Return current hold mode."""
def preset_mode(self):
"""Return current preset mode."""
schedule_mode = self._thermostat.schedule_mode
if schedule_mode == SCHEDULE_RUN:
return MODE_AUTO
@ -162,7 +154,15 @@ class NuHeatThermostat(ClimateDevice):
return MODE_AUTO
@property
def operation_list(self):
def preset_modes(self):
"""Return available preset modes."""
return [
MODE_HOLD_TEMPERATURE,
MODE_TEMPORARY_HOLD
]
@property
def hvac_modes(self):
"""Return list of possible operation modes."""
return OPERATION_LIST
@ -171,15 +171,15 @@ class NuHeatThermostat(ClimateDevice):
self._thermostat.resume_schedule()
self._force_update = True
def set_hold_mode(self, hold_mode):
def set_preset_mode(self, preset_mode):
"""Update the hold mode of the thermostat."""
if hold_mode == MODE_AUTO:
if preset_mode is None:
schedule_mode = SCHEDULE_RUN
if hold_mode == MODE_HOLD_TEMPERATURE:
elif preset_mode == MODE_HOLD_TEMPERATURE:
schedule_mode = SCHEDULE_HOLD
if hold_mode == MODE_TEMPORARY_HOLD:
elif preset_mode == MODE_TEMPORARY_HOLD:
schedule_mode = SCHEDULE_TEMPORARY_HOLD
self._thermostat.schedule_mode = schedule_mode

View File

@ -1,29 +1,21 @@
"""
OpenEnergyMonitor Thermostat Support.
This provides a climate component for the ESP8266 based thermostat sold by
OpenEnergyMonitor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.oem/
"""
"""OpenEnergyMonitor Thermostat Support."""
import logging
from oemthermostat import Thermostat
import requests
import voluptuous as vol
# Import the device class from the component that you want to support
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
STATE_HEAT, STATE_IDLE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_AWAY_MODE)
CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, HVAC_MODE_AUTO,
HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_USERNAME, CONF_PASSWORD,
CONF_PORT, TEMP_CELSIUS, CONF_NAME)
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT,
CONF_USERNAME, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_AWAY_TEMP = 'away_temp'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
@ -31,22 +23,19 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PORT, default=80): cv.port,
vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string,
vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string,
vol.Optional(CONF_AWAY_TEMP, default=14): vol.Coerce(float)
})
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
SUPPORT_HVAC = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF]
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the oemthermostat platform."""
from oemthermostat import Thermostat
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
away_temp = config.get(CONF_AWAY_TEMP)
try:
therm = Thermostat(
@ -54,36 +43,48 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
except (ValueError, AssertionError, requests.RequestException):
return False
add_entities((ThermostatDevice(hass, therm, name, away_temp), ), True)
add_entities((ThermostatDevice(therm, name), ), True)
class ThermostatDevice(ClimateDevice):
"""Interface class for the oemthermostat module."""
def __init__(self, hass, thermostat, name, away_temp):
def __init__(self, thermostat, name):
"""Initialize the device."""
self._name = name
self.hass = hass
# Away mode stuff
self._away = False
self._away_temp = away_temp
self._prev_temp = thermostat.setpoint
self.thermostat = thermostat
# Set the thermostat mode to manual
self.thermostat.mode = 2
# set up internal state varS
self._state = None
self._temperature = None
self._setpoint = None
self._mode = None
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if self._mode == 2:
return HVAC_MODE_HEAT
if self._mode == 1:
return HVAC_MODE_AUTO
return HVAC_MODE_OFF
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return SUPPORT_HVAC
@property
def name(self):
"""Return the name of this Thermostat."""
@ -95,11 +96,13 @@ class ThermostatDevice(ClimateDevice):
return TEMP_CELSIUS
@property
def current_operation(self):
"""Return current operation i.e. heat, cool, idle."""
def hvac_action(self):
"""Return current hvac i.e. heat, cool, idle."""
if not self._mode:
return CURRENT_HVAC_OFF
if self._state:
return STATE_HEAT
return STATE_IDLE
return CURRENT_HVAC_HEAT
return CURRENT_HVAC_IDLE
@property
def current_temperature(self):
@ -111,36 +114,23 @@ class ThermostatDevice(ClimateDevice):
"""Return the temperature we try to reach."""
return self._setpoint
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if hvac_mode == HVAC_MODE_AUTO:
self.thermostat.mode = 1
elif hvac_mode == HVAC_MODE_HEAT:
self.thermostat.mode = 2
elif hvac_mode == HVAC_MODE_OFF:
self.thermostat.mode = 0
def set_temperature(self, **kwargs):
"""Set the temperature."""
# If we are setting the temp, then we don't want away mode anymore.
self.turn_away_mode_off()
temp = kwargs.get(ATTR_TEMPERATURE)
self.thermostat.setpoint = temp
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
def turn_away_mode_on(self):
"""Turn away mode on."""
if not self._away:
self._prev_temp = self._setpoint
self.thermostat.setpoint = self._away_temp
self._away = True
def turn_away_mode_off(self):
"""Turn away mode off."""
if self._away:
self.thermostat.setpoint = self._prev_temp
self._away = False
def update(self):
"""Update local state."""
self._setpoint = self.thermostat.setpoint
self._temperature = self.thermostat.temperature
self._state = self.thermostat.state
self._mode = self.thermostat.mode

View File

@ -3,15 +3,15 @@ import logging
from pyotgw import vars as gw_vars
from homeassistant.components.climate import ClimateDevice, ENTITY_ID_FORMAT
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE,
PRESET_AWAY, SUPPORT_PRESET_MODE)
from homeassistant.const import (
ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE,
TEMP_CELSIUS)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import async_generate_entity_id
from .const import (
CONF_FLOOR_TEMP, CONF_PRECISION, DATA_GATEWAYS, DATA_OPENTHERM_GW)
@ -19,13 +19,14 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the opentherm_gw device."""
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info]
gateway = OpenThermClimate(gw_dev)
async_add_entities([gateway])
@ -36,12 +37,10 @@ class OpenThermClimate(ClimateDevice):
def __init__(self, gw_dev):
"""Initialize the device."""
self._gateway = gw_dev
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, gw_dev.gw_id, hass=gw_dev.hass)
self.friendly_name = gw_dev.name
self.floor_temp = gw_dev.climate_config[CONF_FLOOR_TEMP]
self.temp_precision = gw_dev.climate_config.get(CONF_PRECISION)
self._current_operation = STATE_IDLE
self._current_operation = HVAC_MODE_OFF
self._current_temperature = None
self._new_target_temperature = None
self._target_temperature = None
@ -63,13 +62,15 @@ class OpenThermClimate(ClimateDevice):
flame_on = status.get(gw_vars.DATA_SLAVE_FLAME_ON)
cooling_active = status.get(gw_vars.DATA_SLAVE_COOLING_ACTIVE)
if ch_active and flame_on:
self._current_operation = STATE_HEAT
self._current_operation = HVAC_MODE_HEAT
elif cooling_active:
self._current_operation = STATE_COOL
self._current_operation = HVAC_MODE_COOL
else:
self._current_operation = STATE_IDLE
self._current_operation = HVAC_MODE_OFF
self._current_temperature = status.get(gw_vars.DATA_ROOM_TEMP)
temp_upd = status.get(gw_vars.DATA_ROOM_SETPOINT)
if self._target_temperature != temp_upd:
self._new_target_temperature = None
self._target_temperature = temp_upd
@ -103,6 +104,11 @@ class OpenThermClimate(ClimateDevice):
"""Return the friendly name."""
return self.friendly_name
@property
def unique_id(self):
"""Return a unique ID."""
return self._gateway.gw_id
@property
def precision(self):
"""Return the precision of the system."""
@ -123,7 +129,7 @@ class OpenThermClimate(ClimateDevice):
return TEMP_CELSIUS
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
@ -151,9 +157,19 @@ class OpenThermClimate(ClimateDevice):
return self.precision
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away_state_a or self._away_state_b
def preset_mode(self):
"""Return current preset mode."""
if self._away_state_a or self._away_state_b:
return PRESET_AWAY
@property
def preset_modes(self):
"""Available preset modes to set."""
return [PRESET_AWAY]
def set_preset_mode(self, preset_mode):
"""Set the preset mode."""
_LOGGER.warning("Changing preset mode is not supported")
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""

View File

@ -3,7 +3,7 @@ import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate.const import (
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, PRECISION_TENTHS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE)
@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
pdp = proliphix.PDP(host, username, password)
add_entities([ProliphixThermostat(pdp)])
add_entities([ProliphixThermostat(pdp)], True)
class ProliphixThermostat(ClimateDevice):
@ -37,7 +37,6 @@ class ProliphixThermostat(ClimateDevice):
def __init__(self, pdp):
"""Initialize the thermostat."""
self._pdp = pdp
self._pdp.update()
self._name = self._pdp.name
@property
@ -91,15 +90,20 @@ class ProliphixThermostat(ClimateDevice):
return self._pdp.setback
@property
def current_operation(self):
def hvac_mode(self):
"""Return the current state of the thermostat."""
state = self._pdp.hvac_state
state = self._pdp.hvac_mode
if state in (1, 2):
return STATE_IDLE
return HVAC_MODE_OFF
if state == 3:
return STATE_HEAT
return HVAC_MODE_HEAT
if state == 6:
return STATE_COOL
return HVAC_MODE_COOL
@property
def hvac_modes(self):
"""Return available HVAC modes."""
return []
def set_temperature(self, **kwargs):
"""Set new target temperature."""

View File

@ -3,15 +3,15 @@ import datetime
import logging
import voluptuous as vol
import radiotherm
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE,
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF,
SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE)
SUPPORT_FAN_MODE)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, PRECISION_HALVES, TEMP_FAHRENHEIT, STATE_ON,
STATE_OFF)
ATTR_TEMPERATURE, CONF_HOST, PRECISION_HALVES, TEMP_FAHRENHEIT, STATE_ON)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -20,37 +20,35 @@ ATTR_FAN = 'fan'
ATTR_MODE = 'mode'
CONF_HOLD_TEMP = 'hold_temp'
CONF_AWAY_TEMPERATURE_HEAT = 'away_temperature_heat'
CONF_AWAY_TEMPERATURE_COOL = 'away_temperature_cool'
DEFAULT_AWAY_TEMPERATURE_HEAT = 60
DEFAULT_AWAY_TEMPERATURE_COOL = 85
STATE_CIRCULATE = "circulate"
OPERATION_LIST = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF]
CT30_FAN_OPERATION_LIST = [STATE_ON, STATE_AUTO]
CT80_FAN_OPERATION_LIST = [STATE_ON, STATE_CIRCULATE, STATE_AUTO]
OPERATION_LIST = [HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT,
HVAC_MODE_OFF]
CT30_FAN_OPERATION_LIST = [STATE_ON, HVAC_MODE_AUTO]
CT80_FAN_OPERATION_LIST = [STATE_ON, STATE_CIRCULATE, HVAC_MODE_AUTO]
# Mappings from radiotherm json data codes to and from HASS state
# flags. CODE is the thermostat integer code and these map to and
# from HASS state flags.
# Programmed temperature mode of the thermostat.
CODE_TO_TEMP_MODE = {0: STATE_OFF, 1: STATE_HEAT, 2: STATE_COOL, 3: STATE_AUTO}
CODE_TO_TEMP_MODE = {
0: HVAC_MODE_OFF, 1: HVAC_MODE_HEAT, 2: HVAC_MODE_COOL, 3: HVAC_MODE_AUTO
}
TEMP_MODE_TO_CODE = {v: k for k, v in CODE_TO_TEMP_MODE.items()}
# Programmed fan mode (circulate is supported by CT80 models)
CODE_TO_FAN_MODE = {0: STATE_AUTO, 1: STATE_CIRCULATE, 2: STATE_ON}
CODE_TO_FAN_MODE = {0: HVAC_MODE_AUTO, 1: STATE_CIRCULATE, 2: STATE_ON}
FAN_MODE_TO_CODE = {v: k for k, v in CODE_TO_FAN_MODE.items()}
# Active thermostat state (is it heating or cooling?). In the future
# this should probably made into heat and cool binary sensors.
CODE_TO_TEMP_STATE = {0: STATE_IDLE, 1: STATE_HEAT, 2: STATE_COOL}
CODE_TO_TEMP_STATE = {0: HVAC_MODE_OFF, 1: HVAC_MODE_HEAT, 2: HVAC_MODE_COOL}
# Active fan state. This is if the fan is actually on or not. In the
# future this should probably made into a binary sensor for the fan.
CODE_TO_FAN_STATE = {0: STATE_OFF, 1: STATE_ON}
CODE_TO_FAN_STATE = {0: HVAC_MODE_OFF, 1: STATE_ON}
def round_temp(temperature):
@ -65,22 +63,13 @@ def round_temp(temperature):
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean,
vol.Optional(CONF_AWAY_TEMPERATURE_HEAT,
default=DEFAULT_AWAY_TEMPERATURE_HEAT):
vol.All(vol.Coerce(float), round_temp),
vol.Optional(CONF_AWAY_TEMPERATURE_COOL,
default=DEFAULT_AWAY_TEMPERATURE_COOL):
vol.All(vol.Coerce(float), round_temp),
})
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_FAN_MODE | SUPPORT_AWAY_MODE)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Radio Thermostat."""
import radiotherm
hosts = []
if CONF_HOST in config:
hosts = config[CONF_HOST]
@ -92,16 +81,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
return False
hold_temp = config.get(CONF_HOLD_TEMP)
away_temps = [
config.get(CONF_AWAY_TEMPERATURE_HEAT),
config.get(CONF_AWAY_TEMPERATURE_COOL)
]
tstats = []
for host in hosts:
try:
tstat = radiotherm.get_thermostat(host)
tstats.append(RadioThermostat(tstat, hold_temp, away_temps))
tstats.append(RadioThermostat(tstat, hold_temp))
except OSError:
_LOGGER.exception("Unable to connect to Radio Thermostat: %s",
host)
@ -112,12 +97,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class RadioThermostat(ClimateDevice):
"""Representation of a Radio Thermostat."""
def __init__(self, device, hold_temp, away_temps):
def __init__(self, device, hold_temp):
"""Initialize the thermostat."""
self.device = device
self._target_temperature = None
self._current_temperature = None
self._current_operation = STATE_IDLE
self._current_operation = HVAC_MODE_OFF
self._name = None
self._fmode = None
self._fstate = None
@ -125,12 +110,9 @@ class RadioThermostat(ClimateDevice):
self._tstate = None
self._hold_temp = hold_temp
self._hold_set = False
self._away = False
self._away_temps = away_temps
self._prev_temp = None
# Fan circulate mode is only supported by the CT80 models.
import radiotherm
self._is_model_ct80 = isinstance(
self.device, radiotherm.thermostat.CT80)
@ -172,14 +154,14 @@ class RadioThermostat(ClimateDevice):
}
@property
def fan_list(self):
def fan_modes(self):
"""List of available fan modes."""
if self._is_model_ct80:
return CT80_FAN_OPERATION_LIST
return CT30_FAN_OPERATION_LIST
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return whether the fan is on."""
return self._fmode
@ -195,12 +177,12 @@ class RadioThermostat(ClimateDevice):
return self._current_temperature
@property
def current_operation(self):
def hvac_mode(self):
"""Return the current operation. head, cool idle."""
return self._current_operation
@property
def operation_list(self):
def hvac_modes(self):
"""Return the operation modes list."""
return OPERATION_LIST
@ -209,16 +191,6 @@ class RadioThermostat(ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temperature
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
@property
def is_on(self):
"""Return true if on."""
return self._tstate != STATE_IDLE
def update(self):
"""Update and validate the data from the thermostat."""
# Radio thermostats are very slow, and sometimes don't respond
@ -235,7 +207,6 @@ class RadioThermostat(ClimateDevice):
self._name = self.device.name['raw']
# Request the current state from the thermostat.
import radiotherm
try:
data = self.device.tstat['raw']
except radiotherm.validate.RadiothermTstatError:
@ -253,20 +224,20 @@ class RadioThermostat(ClimateDevice):
self._tstate = CODE_TO_TEMP_STATE[data['tstate']]
self._current_operation = self._tmode
if self._tmode == STATE_COOL:
if self._tmode == HVAC_MODE_COOL:
self._target_temperature = data['t_cool']
elif self._tmode == STATE_HEAT:
elif self._tmode == HVAC_MODE_HEAT:
self._target_temperature = data['t_heat']
elif self._tmode == STATE_AUTO:
elif self._tmode == HVAC_MODE_AUTO:
# This doesn't really work - tstate is only set if the HVAC is
# active. If it's idle, we don't know what to do with the target
# temperature.
if self._tstate == STATE_COOL:
if self._tstate == HVAC_MODE_COOL:
self._target_temperature = data['t_cool']
elif self._tstate == STATE_HEAT:
elif self._tstate == HVAC_MODE_HEAT:
self._target_temperature = data['t_heat']
else:
self._current_operation = STATE_IDLE
self._current_operation = HVAC_MODE_OFF
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@ -276,20 +247,20 @@ class RadioThermostat(ClimateDevice):
temperature = round_temp(temperature)
if self._current_operation == STATE_COOL:
if self._current_operation == HVAC_MODE_COOL:
self.device.t_cool = temperature
elif self._current_operation == STATE_HEAT:
elif self._current_operation == HVAC_MODE_HEAT:
self.device.t_heat = temperature
elif self._current_operation == STATE_AUTO:
if self._tstate == STATE_COOL:
elif self._current_operation == HVAC_MODE_AUTO:
if self._tstate == HVAC_MODE_COOL:
self.device.t_cool = temperature
elif self._tstate == STATE_HEAT:
elif self._tstate == HVAC_MODE_HEAT:
self.device.t_heat = temperature
# Only change the hold if requested or if hold mode was turned
# on and we haven't set it yet.
if kwargs.get('hold_changed', False) or not self._hold_set:
if self._hold_temp or self._away:
if self._hold_temp:
self.device.hold = 1
self._hold_set = True
else:
@ -306,34 +277,13 @@ class RadioThermostat(ClimateDevice):
'minute': now.minute
}
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set operation mode (auto, cool, heat, off)."""
if operation_mode in (STATE_OFF, STATE_AUTO):
self.device.tmode = TEMP_MODE_TO_CODE[operation_mode]
if hvac_mode in (HVAC_MODE_OFF, HVAC_MODE_AUTO):
self.device.tmode = TEMP_MODE_TO_CODE[hvac_mode]
# Setting t_cool or t_heat automatically changes tmode.
elif operation_mode == STATE_COOL:
elif hvac_mode == HVAC_MODE_COOL:
self.device.t_cool = self._target_temperature
elif operation_mode == STATE_HEAT:
elif hvac_mode == HVAC_MODE_HEAT:
self.device.t_heat = self._target_temperature
def turn_away_mode_on(self):
"""Turn away on.
The RTCOA app simulates away mode by using a hold.
"""
away_temp = None
if not self._away:
self._prev_temp = self._target_temperature
if self._current_operation == STATE_HEAT:
away_temp = self._away_temps[0]
elif self._current_operation == STATE_COOL:
away_temp = self._away_temps[1]
self._away = True
self.set_temperature(temperature=away_temp, hold_changed=True)
def turn_away_mode_off(self):
"""Turn away off."""
self._away = False
self.set_temperature(temperature=self._prev_temp, hold_changed=True)

View File

@ -6,27 +6,29 @@ import logging
import aiohttp
import async_timeout
import voluptuous as vol
import pysensibo
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
DOMAIN, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE,
SUPPORT_ON_OFF, STATE_HEAT, STATE_COOL, STATE_FAN_ONLY, STATE_DRY,
STATE_AUTO)
HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_STATE, ATTR_TEMPERATURE, CONF_API_KEY, CONF_ID,
STATE_ON, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.temperature import convert as convert_temperature
from .const import DOMAIN as SENSIBO_DOMAIN
_LOGGER = logging.getLogger(__name__)
ALL = ['all']
TIMEOUT = 10
SERVICE_ASSUME_STATE = 'sensibo_assume_state'
SERVICE_ASSUME_STATE = 'assume_state'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
@ -45,18 +47,16 @@ _INITIAL_FETCH_FIELDS = 'id,' + _FETCH_FIELDS
FIELD_TO_FLAG = {
'fanLevel': SUPPORT_FAN_MODE,
'mode': SUPPORT_OPERATION_MODE,
'swing': SUPPORT_SWING_MODE,
'targetTemperature': SUPPORT_TARGET_TEMPERATURE,
'on': SUPPORT_ON_OFF,
}
SENSIBO_TO_HA = {
"cool": STATE_COOL,
"heat": STATE_HEAT,
"fan": STATE_FAN_ONLY,
"auto": STATE_AUTO,
"dry": STATE_DRY
"cool": HVAC_MODE_COOL,
"heat": HVAC_MODE_HEAT,
"fan": HVAC_MODE_FAN_ONLY,
"auto": HVAC_MODE_HEAT_COOL,
"dry": HVAC_MODE_DRY
}
HA_TO_SENSIBO = {value: key for key, value in SENSIBO_TO_HA.items()}
@ -65,8 +65,6 @@ HA_TO_SENSIBO = {value: key for key, value in SENSIBO_TO_HA.items()}
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up Sensibo devices."""
import pysensibo
client = pysensibo.SensiboClient(
config[CONF_API_KEY], session=async_get_clientsession(hass),
timeout=TIMEOUT)
@ -82,29 +80,32 @@ async def async_setup_platform(hass, config, async_add_entities,
_LOGGER.exception('Failed to connect to Sensibo servers.')
raise PlatformNotReady
if devices:
async_add_entities(devices)
if not devices:
return
async def async_assume_state(service):
"""Set state according to external service call.."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
if entity_ids:
target_climate = [device for device in devices
if device.entity_id in entity_ids]
else:
target_climate = devices
async_add_entities(devices)
update_tasks = []
for climate in target_climate:
await climate.async_assume_state(
service.data.get(ATTR_STATE))
update_tasks.append(climate.async_update_ha_state(True))
async def async_assume_state(service):
"""Set state according to external service call.."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
if entity_ids:
target_climate = [device for device in devices
if device.entity_id in entity_ids]
else:
target_climate = devices
if update_tasks:
await asyncio.wait(update_tasks)
hass.services.async_register(
DOMAIN, SERVICE_ASSUME_STATE, async_assume_state,
schema=ASSUME_STATE_SCHEMA)
update_tasks = []
for climate in target_climate:
await climate.async_assume_state(
service.data.get(ATTR_STATE))
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
await asyncio.wait(update_tasks)
hass.services.async_register(
SENSIBO_DOMAIN, SERVICE_ASSUME_STATE, async_assume_state,
schema=ASSUME_STATE_SCHEMA)
class SensiboClimate(ClimateDevice):
@ -136,6 +137,7 @@ class SensiboClimate(ClimateDevice):
capabilities = data['remoteCapabilities']
self._operations = [SENSIBO_TO_HA[mode] for mode
in capabilities['modes']]
self._operations.append(HVAC_MODE_OFF)
self._current_capabilities = \
capabilities['modes'][self._ac_states['mode']]
temperature_unit_key = data.get('temperatureUnit') or \
@ -189,7 +191,7 @@ class SensiboClimate(ClimateDevice):
return None
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return SENSIBO_TO_HA.get(self._ac_states['mode'])
@ -214,27 +216,27 @@ class SensiboClimate(ClimateDevice):
self.temperature_unit)
@property
def operation_list(self):
def hvac_modes(self):
"""List of available operation modes."""
return self._operations
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self._ac_states.get('fanLevel')
@property
def fan_list(self):
def fan_modes(self):
"""List of available fan modes."""
return self._current_capabilities.get('fanLevels')
@property
def current_swing_mode(self):
def swing_mode(self):
"""Return the fan setting."""
return self._ac_states.get('swing')
@property
def swing_list(self):
def swing_modes(self):
"""List of available swing modes."""
return self._current_capabilities.get('swing')
@ -243,11 +245,6 @@ class SensiboClimate(ClimateDevice):
"""Return the name of the entity."""
return self._name
@property
def is_on(self):
"""Return true if AC is on."""
return self._ac_states['on']
@property
def min_temp(self):
"""Return the minimum temperature."""
@ -294,11 +291,23 @@ class SensiboClimate(ClimateDevice):
await self._client.async_set_ac_state_property(
self._id, 'fanLevel', fan_mode, self._ac_states)
async def async_set_operation_mode(self, operation_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
if hvac_mode == HVAC_MODE_OFF:
with async_timeout.timeout(TIMEOUT):
await self._client.async_set_ac_state_property(
self._id, 'on', False, self._ac_states)
return
# Turn on if not currently on.
if not self._ac_states['on']:
with async_timeout.timeout(TIMEOUT):
await self._client.async_set_ac_state_property(
self._id, 'on', True, self._ac_states)
with async_timeout.timeout(TIMEOUT):
await self._client.async_set_ac_state_property(
self._id, 'mode', HA_TO_SENSIBO[operation_mode],
self._id, 'mode', HA_TO_SENSIBO[hvac_mode],
self._ac_states)
async def async_set_swing_mode(self, swing_mode):
@ -307,40 +316,29 @@ class SensiboClimate(ClimateDevice):
await self._client.async_set_ac_state_property(
self._id, 'swing', swing_mode, self._ac_states)
async def async_turn_on(self):
"""Turn Sensibo unit on."""
with async_timeout.timeout(TIMEOUT):
await self._client.async_set_ac_state_property(
self._id, 'on', True, self._ac_states)
async def async_turn_off(self):
"""Turn Sensibo unit on."""
with async_timeout.timeout(TIMEOUT):
await self._client.async_set_ac_state_property(
self._id, 'on', False, self._ac_states)
async def async_assume_state(self, state):
"""Set external state."""
change_needed = (state != STATE_OFF and not self.is_on) \
or (state == STATE_OFF and self.is_on)
change_needed = \
(state != HVAC_MODE_OFF and not self._ac_states['on']) \
or (state == HVAC_MODE_OFF and self._ac_states['on'])
if change_needed:
with async_timeout.timeout(TIMEOUT):
await self._client.async_set_ac_state_property(
self._id,
'on',
state != STATE_OFF, # value
state != HVAC_MODE_OFF, # value
self._ac_states,
True # assumed_state
)
if state in [STATE_ON, STATE_OFF]:
if state in [STATE_ON, HVAC_MODE_OFF]:
self._external_state = None
else:
self._external_state = state
async def async_update(self):
"""Retrieve latest state."""
import pysensibo
try:
with async_timeout.timeout(TIMEOUT):
data = await self._client.async_get_device(

View File

@ -0,0 +1,3 @@
"""Constants for Sensibo."""
DOMAIN = "sensibo"

View File

@ -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'

View File

@ -8,51 +8,59 @@ from pysmartthings import Attribute, Capability
from homeassistant.components.climate import (
DOMAIN as CLIMATE_DOMAIN, ClimateDevice)
from homeassistant.components.climate.const import (
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW)
from homeassistant.const import (
ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, HVAC_MODE_AUTO,
HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
ATTR_OPERATION_STATE = 'operation_state'
MODE_TO_STATE = {
'auto': STATE_AUTO,
'cool': STATE_COOL,
'eco': STATE_ECO,
'rush hour': STATE_ECO,
'emergency heat': STATE_HEAT,
'heat': STATE_HEAT,
'off': STATE_OFF
'auto': HVAC_MODE_HEAT_COOL,
'cool': HVAC_MODE_COOL,
'eco': HVAC_MODE_AUTO,
'rush hour': HVAC_MODE_AUTO,
'emergency heat': HVAC_MODE_HEAT,
'heat': HVAC_MODE_HEAT,
'off': HVAC_MODE_OFF
}
STATE_TO_MODE = {
STATE_AUTO: 'auto',
STATE_COOL: 'cool',
STATE_ECO: 'eco',
STATE_HEAT: 'heat',
STATE_OFF: 'off'
HVAC_MODE_HEAT_COOL: 'auto',
HVAC_MODE_COOL: 'cool',
HVAC_MODE_HEAT: 'heat',
HVAC_MODE_OFF: 'off'
}
OPERATING_STATE_TO_ACTION = {
"cooling": CURRENT_HVAC_COOL,
"fan only": None,
"heating": CURRENT_HVAC_HEAT,
"idle": CURRENT_HVAC_IDLE,
"pending cool": CURRENT_HVAC_COOL,
"pending heat": CURRENT_HVAC_HEAT,
"vent economizer": None
}
AC_MODE_TO_STATE = {
'auto': STATE_AUTO,
'cool': STATE_COOL,
'dry': STATE_DRY,
'coolClean': STATE_COOL,
'dryClean': STATE_DRY,
'heat': STATE_HEAT,
'heatClean': STATE_HEAT,
'fanOnly': STATE_FAN_ONLY
'auto': HVAC_MODE_HEAT_COOL,
'cool': HVAC_MODE_COOL,
'dry': HVAC_MODE_DRY,
'coolClean': HVAC_MODE_COOL,
'dryClean': HVAC_MODE_DRY,
'heat': HVAC_MODE_HEAT,
'heatClean': HVAC_MODE_HEAT,
'fanOnly': HVAC_MODE_FAN_ONLY
}
STATE_TO_AC_MODE = {
STATE_AUTO: 'auto',
STATE_COOL: 'cool',
STATE_DRY: 'dry',
STATE_HEAT: 'heat',
STATE_FAN_ONLY: 'fanOnly'
HVAC_MODE_HEAT_COOL: 'auto',
HVAC_MODE_COOL: 'cool',
HVAC_MODE_DRY: 'dry',
HVAC_MODE_HEAT: 'heat',
HVAC_MODE_FAN_ONLY: 'fanOnly'
}
UNIT_MAP = {
@ -139,14 +147,13 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
"""Init the class."""
super().__init__(device)
self._supported_features = self._determine_features()
self._current_operation = None
self._operations = None
self._hvac_mode = None
self._hvac_modes = None
def _determine_features(self):
flags = SUPPORT_OPERATION_MODE \
| SUPPORT_TARGET_TEMPERATURE \
| SUPPORT_TARGET_TEMPERATURE_LOW \
| SUPPORT_TARGET_TEMPERATURE_HIGH
flags = \
SUPPORT_TARGET_TEMPERATURE \
| SUPPORT_TARGET_TEMPERATURE_RANGE
if self._device.get_capability(
Capability.thermostat_fan_mode, Capability.thermostat):
flags |= SUPPORT_FAN_MODE
@ -160,9 +167,9 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
# the entity state ahead of receiving the confirming push updates
self.async_schedule_update_ha_state(True)
async def async_set_operation_mode(self, operation_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
mode = STATE_TO_MODE[operation_mode]
mode = STATE_TO_MODE[hvac_mode]
await self._device.set_thermostat_mode(mode, set_status=True)
# State is set optimistically in the command above, therefore update
@ -172,7 +179,7 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
async def async_set_temperature(self, **kwargs):
"""Set new operation mode and target temperatures."""
# Operation state
operation_state = kwargs.get(ATTR_OPERATION_MODE)
operation_state = kwargs.get(ATTR_HVAC_MODE)
if operation_state:
mode = STATE_TO_MODE[operation_state]
await self._device.set_thermostat_mode(mode, set_status=True)
@ -181,9 +188,9 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
# Heat/cool setpoint
heating_setpoint = None
cooling_setpoint = None
if self.current_operation == STATE_HEAT:
if self.hvac_mode == HVAC_MODE_HEAT:
heating_setpoint = kwargs.get(ATTR_TEMPERATURE)
elif self.current_operation == STATE_COOL:
elif self.hvac_mode == HVAC_MODE_COOL:
cooling_setpoint = kwargs.get(ATTR_TEMPERATURE)
else:
heating_setpoint = kwargs.get(ATTR_TARGET_TEMP_LOW)
@ -204,10 +211,10 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
async def async_update(self):
"""Update the attributes of the climate device."""
thermostat_mode = self._device.status.thermostat_mode
self._current_operation = MODE_TO_STATE.get(thermostat_mode)
if self._current_operation is None:
self._hvac_mode = MODE_TO_STATE.get(thermostat_mode)
if self._hvac_mode is None:
_LOGGER.debug('Device %s (%s) returned an invalid'
'thermostat mode: %s', self._device.label,
'hvac mode: %s', self._device.label,
self._device.device_id, thermostat_mode)
supported_modes = self._device.status.supported_thermostat_modes
@ -222,49 +229,47 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
'supported thermostat mode: %s',
self._device.label, self._device.device_id,
mode)
self._operations = operations
self._hvac_modes = operations
else:
_LOGGER.debug('Device %s (%s) returned invalid supported '
'thermostat modes: %s', self._device.label,
self._device.device_id, supported_modes)
@property
def current_fan_mode(self):
"""Return the fan setting."""
return self._device.status.thermostat_fan_mode
@property
def current_humidity(self):
"""Return the current humidity."""
return self._device.status.humidity
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
@property
def current_temperature(self):
"""Return the current temperature."""
return self._device.status.temperature
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
return {
ATTR_OPERATION_STATE:
self._device.status.thermostat_operating_state
}
def fan_mode(self):
"""Return the fan setting."""
return self._device.status.thermostat_fan_mode
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return self._device.status.supported_thermostat_fan_modes
@property
def operation_list(self):
def hvac_action(self) -> Optional[str]:
"""Return the current running hvac operation if supported."""
return OPERATING_STATE_TO_ACTION.get(
self._device.status.thermostat_operating_state)
@property
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self._hvac_mode
@property
def hvac_modes(self):
"""Return the list of available operation modes."""
return self._operations
return self._hvac_modes
@property
def supported_features(self):
@ -274,23 +279,23 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.current_operation == STATE_COOL:
if self.hvac_mode == HVAC_MODE_COOL:
return self._device.status.cooling_setpoint
if self.current_operation == STATE_HEAT:
if self.hvac_mode == HVAC_MODE_HEAT:
return self._device.status.heating_setpoint
return None
@property
def target_temperature_high(self):
"""Return the highbound target temperature we try to reach."""
if self.current_operation == STATE_AUTO:
if self.hvac_mode == HVAC_MODE_HEAT_COOL:
return self._device.status.cooling_setpoint
return None
@property
def target_temperature_low(self):
"""Return the lowbound target temperature we try to reach."""
if self.current_operation == STATE_AUTO:
if self.hvac_mode == HVAC_MODE_HEAT_COOL:
return self._device.status.heating_setpoint
return None
@ -307,7 +312,7 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
def __init__(self, device):
"""Init the class."""
super().__init__(device)
self._operations = None
self._hvac_modes = None
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
@ -316,10 +321,10 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
# the entity state ahead of receiving the confirming push updates
self.async_schedule_update_ha_state()
async def async_set_operation_mode(self, operation_mode):
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
await self._device.set_air_conditioner_mode(
STATE_TO_AC_MODE[operation_mode], set_status=True)
STATE_TO_AC_MODE[hvac_mode], set_status=True)
# State is set optimistically in the command above, therefore update
# the entity state ahead of receiving the confirming push updates
self.async_schedule_update_ha_state()
@ -328,9 +333,9 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
"""Set new target temperature."""
tasks = []
# operation mode
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
operation_mode = kwargs.get(ATTR_HVAC_MODE)
if operation_mode:
tasks.append(self.async_set_operation_mode(operation_mode))
tasks.append(self.async_set_hvac_mode(operation_mode))
# temperature
tasks.append(self._device.set_cooling_setpoint(
kwargs[ATTR_TEMPERATURE], set_status=True))
@ -339,20 +344,6 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
# the entity state ahead of receiving the confirming push updates
self.async_schedule_update_ha_state()
async def async_turn_on(self):
"""Turn device on."""
await self._device.switch_on(set_status=True)
# State is set optimistically in the command above, therefore update
# the entity state ahead of receiving the confirming push updates
self.async_schedule_update_ha_state()
async def async_turn_off(self):
"""Turn device off."""
await self._device.switch_off(set_status=True)
# State is set optimistically in the command above, therefore update
# the entity state ahead of receiving the confirming push updates
self.async_schedule_update_ha_state()
async def async_update(self):
"""Update the calculated fields of the AC."""
operations = set()
@ -364,17 +355,7 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
_LOGGER.debug('Device %s (%s) returned an invalid supported '
'AC mode: %s', self._device.label,
self._device.device_id, mode)
self._operations = operations
@property
def current_fan_mode(self):
"""Return the fan setting."""
return self._device.status.fan_mode
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return AC_MODE_TO_STATE.get(self._device.status.air_conditioner_mode)
self._hvac_modes = operations
@property
def current_temperature(self):
@ -407,25 +388,30 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice):
return state_attributes
@property
def fan_list(self):
def fan_mode(self):
"""Return the fan setting."""
return self._device.status.fan_mode
@property
def fan_modes(self):
"""Return the list of available fan modes."""
return self._device.status.supported_ac_fan_modes
@property
def is_on(self):
"""Return true if on."""
return self._device.status.switch
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return AC_MODE_TO_STATE.get(self._device.status.air_conditioner_mode)
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return self._operations
return self._hvac_modes
@property
def supported_features(self):
"""Return the supported features."""
return SUPPORT_OPERATION_MODE | SUPPORT_TARGET_TEMPERATURE \
| SUPPORT_FAN_MODE | SUPPORT_ON_OFF
return SUPPORT_TARGET_TEMPERATURE \
| SUPPORT_FAN_MODE
@property
def target_temperature(self):

View File

@ -4,13 +4,13 @@ import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import DOMAIN as SPIDER_DOMAIN
FAN_LIST = [
SUPPORT_FAN = [
'Auto',
'Low',
'Medium',
@ -20,15 +20,15 @@ FAN_LIST = [
'Boost 30',
]
OPERATION_LIST = [
STATE_HEAT,
STATE_COOL,
SUPPORT_HVAC = [
HVAC_MODE_HEAT,
HVAC_MODE_COOL,
]
HA_STATE_TO_SPIDER = {
STATE_COOL: 'Cool',
STATE_HEAT: 'Heat',
STATE_IDLE: 'Idle',
HVAC_MODE_COOL: 'Cool',
HVAC_MODE_HEAT: 'Heat',
HVAC_MODE_OFF: 'Idle',
}
SPIDER_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_SPIDER.items()}
@ -59,9 +59,6 @@ class SpiderThermostat(ClimateDevice):
"""Return the list of supported features."""
supports = SUPPORT_TARGET_TEMPERATURE
if self.thermostat.has_operation_mode:
supports |= SUPPORT_OPERATION_MODE
if self.thermostat.has_fan_mode:
supports |= SUPPORT_FAN_MODE
@ -108,14 +105,14 @@ class SpiderThermostat(ClimateDevice):
return self.thermostat.maximum_temperature
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return SPIDER_STATE_TO_HA[self.thermostat.operation_mode]
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return OPERATION_LIST
return SUPPORT_HVAC
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@ -125,13 +122,13 @@ class SpiderThermostat(ClimateDevice):
self.thermostat.set_temperature(temperature)
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
self.thermostat.set_operation_mode(
HA_STATE_TO_SPIDER.get(operation_mode))
HA_STATE_TO_SPIDER.get(hvac_mode))
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self.thermostat.current_fan_speed
@ -140,9 +137,9 @@ class SpiderThermostat(ClimateDevice):
self.thermostat.set_fan_speed(fan_mode)
@property
def fan_list(self):
def fan_modes(self):
"""List of available fan modes."""
return FAN_LIST
return SUPPORT_FAN
def update(self):
"""Get the latest data."""

View File

@ -3,10 +3,9 @@ import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_ECO, STATE_MANUAL, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS)
HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_ECO,
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import DOMAIN as STE_DOMAIN
@ -14,21 +13,39 @@ DEPENDENCIES = ['stiebel_eltron']
_LOGGER = logging.getLogger(__name__)
PRESET_DAY = 'day'
PRESET_SETBACK = 'setback'
PRESET_EMERGENCY = 'emergency'
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
OPERATION_MODES = [STATE_AUTO, STATE_MANUAL, STATE_ECO, STATE_OFF]
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
SUPPORT_HVAC = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF]
SUPPORT_PRESET = [PRESET_ECO, PRESET_DAY, PRESET_EMERGENCY, PRESET_SETBACK]
# Mapping STIEBEL ELTRON states to homeassistant states.
STE_TO_HA_STATE = {'AUTOMATIC': STATE_AUTO,
'MANUAL MODE': STATE_MANUAL,
'STANDBY': STATE_ECO,
'DAY MODE': STATE_ON,
'SETBACK MODE': STATE_ON,
'DHW': STATE_OFF,
'EMERGENCY OPERATION': STATE_ON}
# Mapping STIEBEL ELTRON states to homeassistant states/preset.
STE_TO_HA_HVAC = {
'AUTOMATIC': HVAC_MODE_AUTO,
'MANUAL MODE': HVAC_MODE_HEAT,
'STANDBY': HVAC_MODE_AUTO,
'DAY MODE': HVAC_MODE_AUTO,
'SETBACK MODE': HVAC_MODE_AUTO,
'DHW': HVAC_MODE_OFF,
'EMERGENCY OPERATION': HVAC_MODE_AUTO
}
# Mapping homeassistant states to STIEBEL ELTRON states.
HA_TO_STE_STATE = {value: key for key, value in STE_TO_HA_STATE.items()}
STE_TO_HA_PRESET = {
'STANDBY': PRESET_ECO,
'DAY MODE': PRESET_DAY,
'SETBACK MODE': PRESET_SETBACK,
'EMERGENCY OPERATION': PRESET_EMERGENCY,
}
HA_TO_STE_HVAC = {
HVAC_MODE_AUTO: 'AUTOMATIC',
HVAC_MODE_HEAT: 'MANUAL MODE',
HVAC_MODE_OFF: 'DHW',
}
HA_TO_STE_PRESET = {k: i for i, k in STE_TO_HA_PRESET.items()}
def setup_platform(hass, config, add_entities, discovery_info=None):
@ -48,8 +65,7 @@ class StiebelEltron(ClimateDevice):
self._target_temperature = None
self._current_temperature = None
self._current_humidity = None
self._operation_modes = OPERATION_MODES
self._current_operation = None
self._operation = None
self._filter_alarm = None
self._force_update = False
self._ste_data = ste_data
@ -68,7 +84,7 @@ class StiebelEltron(ClimateDevice):
self._current_temperature = self._ste_data.api.get_current_temp()
self._current_humidity = self._ste_data.api.get_current_humidity()
self._filter_alarm = self._ste_data.api.get_filter_alarm_status()
self._current_operation = self._ste_data.api.get_operation()
self._operation = self._ste_data.api.get_operation()
_LOGGER.debug("Update %s, current temp: %s", self._name,
self._current_temperature)
@ -116,6 +132,41 @@ class StiebelEltron(ClimateDevice):
"""Return the maximum temperature."""
return 30.0
@property
def current_humidity(self):
"""Return the current humidity."""
return float("{0:.1f}".format(self._current_humidity))
@property
def hvac_modes(self):
"""List of the operation modes."""
return SUPPORT_HVAC
@property
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return STE_TO_HA_HVAC.get(self._operation)
@property
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp."""
return STE_TO_HA_PRESET.get(self._operation)
@property
def preset_modes(self):
"""Return a list of available preset modes."""
return SUPPORT_PRESET
def set_hvac_mode(self, hvac_mode):
"""Set new operation mode."""
if self.preset_mode:
return
new_mode = HA_TO_STE_HVAC.get(hvac_mode)
_LOGGER.debug("set_hvac_mode: %s -> %s", self._operation,
new_mode)
self._ste_data.api.set_operation(new_mode)
self._force_update = True
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temperature = kwargs.get(ATTR_TEMPERATURE)
@ -124,26 +175,10 @@ class StiebelEltron(ClimateDevice):
self._ste_data.api.set_target_temp(target_temperature)
self._force_update = True
@property
def current_humidity(self):
"""Return the current humidity."""
return float("{0:.1f}".format(self._current_humidity))
# Handle SUPPORT_OPERATION_MODE
@property
def operation_list(self):
"""List of the operation modes."""
return self._operation_modes
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return STE_TO_HA_STATE.get(self._current_operation)
def set_operation_mode(self, operation_mode):
"""Set new operation mode."""
new_mode = HA_TO_STE_STATE.get(operation_mode)
_LOGGER.debug("set_operation_mode: %s -> %s", self._current_operation,
def set_preset_mode(self, preset_mode: str):
"""Set new preset mode."""
new_mode = HA_TO_STE_PRESET.get(preset_mode)
_LOGGER.debug("set_hvac_mode: %s -> %s", self._operation,
new_mode)
self._ste_data.api.set_operation(new_mode)
self._force_update = True

View File

@ -3,7 +3,9 @@ import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_ON_OFF)
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, FAN_HIGH, FAN_LOW, FAN_MIDDLE,
FAN_OFF, HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY,
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS)
from homeassistant.util.temperature import convert as convert_temperature
@ -27,23 +29,24 @@ CONST_MODE_FAN_HIGH = 'HIGH'
CONST_MODE_FAN_MIDDLE = 'MIDDLE'
CONST_MODE_FAN_LOW = 'LOW'
FAN_MODES_LIST = {
CONST_MODE_FAN_HIGH: 'High',
CONST_MODE_FAN_MIDDLE: 'Middle',
CONST_MODE_FAN_LOW: 'Low',
CONST_MODE_OFF: 'Off',
FAN_MAP_TADO = {
'HIGH': FAN_HIGH,
'MIDDLE': FAN_MIDDLE,
'LOW': FAN_LOW,
}
OPERATION_LIST = {
CONST_OVERLAY_MANUAL: 'Manual',
CONST_OVERLAY_TIMER: 'Timer',
CONST_OVERLAY_TADO_MODE: 'Tado mode',
CONST_MODE_SMART_SCHEDULE: 'Smart schedule',
CONST_MODE_OFF: 'Off',
HVAC_MAP_TADO = {
'MANUAL': HVAC_MODE_HEAT,
'TIMER': HVAC_MODE_AUTO,
'TADO_MODE': HVAC_MODE_AUTO,
'SMART_SCHEDULE': HVAC_MODE_AUTO,
'OFF': HVAC_MODE_OFF
}
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_ON_OFF)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF]
SUPPORT_FAN = [FAN_HIGH, FAN_MIDDLE, FAN_HIGH, FAN_OFF]
SUPPORT_PRESET = [PRESET_AWAY]
def setup_platform(hass, config, add_entities, discovery_info=None):
@ -159,41 +162,62 @@ class TadoClimate(ClimateDevice):
return self._cur_temp
@property
def current_operation(self):
"""Return current readable operation mode."""
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
return HVAC_MAP_TADO.get(self._current_operation)
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return SUPPORT_HVAC
@property
def hvac_action(self):
"""Return the current running hvac operation if supported.
Need to be one of CURRENT_HVAC_*.
"""
if self._cooling:
return "Cooling"
return OPERATION_LIST.get(self._current_operation)
return CURRENT_HVAC_COOL
return CURRENT_HVAC_HEAT
@property
def operation_list(self):
"""Return the list of available operation modes (readable)."""
return list(OPERATION_LIST.values())
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
if self.ac_mode:
return FAN_MODES_LIST.get(self._current_fan)
return FAN_MAP_TADO.get(self._current_fan)
return None
@property
def fan_list(self):
def fan_modes(self):
"""List of available fan modes."""
if self.ac_mode:
return list(FAN_MODES_LIST.values())
return SUPPORT_FAN
return None
@property
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp."""
if self._is_away:
return PRESET_AWAY
return None
@property
def preset_modes(self):
"""Return a list of available preset modes."""
return SUPPORT_PRESET
@property
def temperature_unit(self):
"""Return the unit of measurement used by the platform."""
return self._unit
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._is_away
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
@ -204,27 +228,6 @@ class TadoClimate(ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temp
@property
def is_on(self):
"""Return true if heater is on."""
return self._device_is_active
def turn_off(self):
"""Turn device off."""
_LOGGER.info("Switching mytado.com to OFF for zone %s",
self.zone_name)
self._current_operation = CONST_MODE_OFF
self._control_heating()
def turn_on(self):
"""Turn device on."""
_LOGGER.info("Switching mytado.com to %s mode for zone %s",
self._overlay_mode, self.zone_name)
self._current_operation = self._overlay_mode
self._control_heating()
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
@ -236,20 +239,25 @@ class TadoClimate(ClimateDevice):
self._target_temp = temperature
self._control_heating()
# pylint: disable=arguments-differ
def set_operation_mode(self, readable_operation_mode):
"""Set new operation mode."""
operation_mode = CONST_MODE_SMART_SCHEDULE
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
mode = None
for mode, readable in OPERATION_LIST.items():
if readable == readable_operation_mode:
operation_mode = mode
break
if hvac_mode == HVAC_MODE_OFF:
mode = CONST_MODE_OFF
elif hvac_mode == HVAC_MODE_AUTO:
mode = CONST_MODE_SMART_SCHEDULE
elif hvac_mode == HVAC_MODE_HEAT:
mode = CONST_OVERLAY_MANUAL
self._current_operation = operation_mode
self._current_operation = mode
self._overlay_mode = None
self._control_heating()
def set_preset_mode(self, preset_mode):
"""Set new preset mode."""
pass
@property
def min_temp(self):
"""Return the minimum temperature."""

View File

@ -99,6 +99,11 @@ class TeslaDevice(Entity):
"""Return the name of the device."""
return self._name
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return self.tesla_id
@property
def should_poll(self):
"""Return the polling state."""

View File

@ -1,8 +1,7 @@
"""Support for Tesla binary sensor."""
import logging
from homeassistant.components.binary_sensor import (
ENTITY_ID_FORMAT, BinarySensorDevice)
from homeassistant.components.binary_sensor import BinarySensorDevice
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
@ -25,7 +24,6 @@ class TeslaBinarySensor(TeslaDevice, BinarySensorDevice):
"""Initialise of a Tesla binary sensor."""
super().__init__(tesla_device, controller)
self._state = False
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
self._sensor_type = sensor_type
@property

View File

@ -1,19 +1,16 @@
"""Support for Tesla HVAC system."""
import logging
from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT)
HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
OPERATION_LIST = [STATE_ON, STATE_OFF]
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
def setup_platform(hass, config, add_entities, discovery_info=None):
@ -29,27 +26,31 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
def __init__(self, tesla_device, controller):
"""Initialize the Tesla device."""
super().__init__(tesla_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
self._target_temperature = None
self._temperature = None
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
return SUPPORT_TARGET_TEMPERATURE
@property
def current_operation(self):
"""Return current operation ie. On or Off."""
mode = self.tesla_device.is_hvac_enabled()
if mode:
return OPERATION_LIST[0] # On
return OPERATION_LIST[1] # Off
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if self.tesla_device.is_hvac_enabled():
return HVAC_MODE_HEAT
return HVAC_MODE_OFF
@property
def operation_list(self):
"""List of available operation modes."""
return OPERATION_LIST
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return SUPPORT_HVAC
def update(self):
"""Call by the Tesla device callback to update state."""
@ -84,10 +85,10 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
if temperature:
self.tesla_device.set_temperature(temperature)
def set_operation_mode(self, operation_mode):
"""Set HVAC mode (auto, cool, heat, off)."""
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
_LOGGER.debug("Setting mode for: %s", self._name)
if operation_mode == OPERATION_LIST[1]: # off
if hvac_mode == HVAC_MODE_OFF:
self.tesla_device.set_status(False)
elif operation_mode == OPERATION_LIST[0]: # heat
elif hvac_mode == HVAC_MODE_HEAT:
self.tesla_device.set_status(True)

View File

@ -1,7 +1,7 @@
"""Support for Tesla door locks."""
import logging
from homeassistant.components.lock import ENTITY_ID_FORMAT, LockDevice
from homeassistant.components.lock import LockDevice
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
@ -23,7 +23,6 @@ class TeslaLock(TeslaDevice, LockDevice):
"""Initialise of the lock."""
self._state = None
super().__init__(tesla_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
def lock(self, **kwargs):
"""Send the lock command."""

View File

@ -2,7 +2,6 @@
from datetime import timedelta
import logging
from homeassistant.components.sensor import ENTITY_ID_FORMAT
from homeassistant.const import (
LENGTH_KILOMETERS, LENGTH_MILES, TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.helpers.entity import Entity
@ -41,10 +40,13 @@ class TeslaSensor(TeslaDevice, Entity):
if self.type:
self._name = '{} ({})'.format(self.tesla_device.name, self.type)
self.entity_id = ENTITY_ID_FORMAT.format(
'{}_{}'.format(self.tesla_id, self.type))
else:
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
@property
def unique_id(self) -> str:
"""Return a unique ID."""
if self.type:
return "{}_{}".format(self.tesla_id, self.type)
return self.tesla_id
@property
def state(self):

View File

@ -1,7 +1,7 @@
"""Support for Tesla charger switches."""
import logging
from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import STATE_OFF, STATE_ON
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
@ -28,7 +28,6 @@ class ChargerSwitch(TeslaDevice, SwitchDevice):
"""Initialise of the switch."""
self._state = None
super().__init__(tesla_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
def turn_on(self, **kwargs):
"""Send the on command."""
@ -60,7 +59,6 @@ class RangeSwitch(TeslaDevice, SwitchDevice):
"""Initialise of the switch."""
self._state = None
super().__init__(tesla_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
def turn_on(self, **kwargs):
"""Send the on command."""

View File

@ -3,13 +3,15 @@ from concurrent import futures
from datetime import timedelta
import logging
from pytfiac import Tfiac
import voluptuous as vol
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT,
SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE)
FAN_AUTO, FAN_HIGH, FAN_LOW, FAN_MEDIUM, HVAC_MODE_AUTO, HVAC_MODE_COOL,
HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF,
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE,
SWING_BOTH, SWING_HORIZONTAL, SWING_OFF, SWING_VERTICAL)
from homeassistant.const import ATTR_TEMPERATURE, CONF_HOST, TEMP_FAHRENHEIT
import homeassistant.helpers.config_validation as cv
@ -23,22 +25,23 @@ _LOGGER = logging.getLogger(__name__)
MIN_TEMP = 61
MAX_TEMP = 88
OPERATION_MAP = {
STATE_HEAT: 'heat',
STATE_AUTO: 'selfFeel',
STATE_DRY: 'dehumi',
STATE_FAN_ONLY: 'fan',
STATE_COOL: 'cool',
HVAC_MAP = {
HVAC_MODE_HEAT: 'heat',
HVAC_MODE_AUTO: 'selfFeel',
HVAC_MODE_DRY: 'dehumi',
HVAC_MODE_FAN_ONLY: 'fan',
HVAC_MODE_COOL: 'cool',
HVAC_MODE_OFF: 'off'
}
OPERATION_MAP_REV = {
v: k for k, v in OPERATION_MAP.items()}
FAN_LIST = ['Auto', 'Low', 'Middle', 'High']
SWING_LIST = [
'Off',
'Vertical',
'Horizontal',
'Both',
]
HVAC_MAP_REV = {v: k for k, v in HVAC_MAP.items()}
SUPPORT_FAN = [FAN_AUTO, FAN_HIGH, FAN_MEDIUM, FAN_LOW]
SUPPORT_SWING = [SWING_OFF, SWING_HORIZONTAL, SWING_VERTICAL, SWING_BOTH]
SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_SWING_MODE |
SUPPORT_TARGET_TEMPERATURE)
CURR_TEMP = 'current_temp'
TARGET_TEMP = 'target_temp'
@ -51,8 +54,6 @@ ON_MODE = 'is_on'
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the TFIAC climate device."""
from pytfiac import Tfiac
tfiac_client = Tfiac(config[CONF_HOST])
try:
await tfiac_client.update()
@ -86,8 +87,7 @@ class TfiacClimate(ClimateDevice):
@property
def supported_features(self):
"""Return the list of supported features."""
return (SUPPORT_FAN_MODE | SUPPORT_ON_OFF | SUPPORT_OPERATION_MODE
| SUPPORT_SWING_MODE | SUPPORT_TARGET_TEMPERATURE)
return SUPPORT_FLAGS
@property
def min_temp(self):
@ -120,64 +120,62 @@ class TfiacClimate(ClimateDevice):
return self._client.status['current_temp']
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
operation = self._client.status['operation']
return OPERATION_MAP_REV.get(operation, operation)
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if self._client.status[ON_MODE] != 'on':
return HVAC_MODE_OFF
state = self._client.status['operation']
return HVAC_MAP_REV.get(state)
@property
def is_on(self):
"""Return true if on."""
return self._client.status[ON_MODE] == 'on'
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return list(HVAC_MAP)
@property
def operation_list(self):
"""Return the list of available operation modes."""
return sorted(OPERATION_MAP)
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self._client.status['fan_mode']
return self._client.status['fan_mode'].lower()
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return FAN_LIST
return SUPPORT_FAN
@property
def current_swing_mode(self):
def swing_mode(self):
"""Return the swing setting."""
return self._client.status['swing_mode']
return self._client.status['swing_mode'].lower()
@property
def swing_list(self):
def swing_modes(self):
"""List of available swing modes."""
return SWING_LIST
return SUPPORT_SWING
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
await self._client.set_state(TARGET_TEMP,
kwargs.get(ATTR_TEMPERATURE))
temp = kwargs.get(ATTR_TEMPERATURE)
if temp is not None:
await self._client.set_state(TARGET_TEMP, temp)
async def async_set_operation_mode(self, operation_mode):
"""Set new operation mode."""
await self._client.set_state(OPERATION_MODE,
OPERATION_MAP[operation_mode])
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if hvac_mode == HVAC_MODE_OFF:
await self._client.set_state(ON_MODE, 'off')
else:
await self._client.set_state(OPERATION_MODE, HVAC_MAP[hvac_mode])
async def async_set_fan_mode(self, fan_mode):
"""Set new fan mode."""
await self._client.set_state(FAN_MODE, fan_mode)
await self._client.set_state(FAN_MODE, fan_mode.capitalize())
async def async_set_swing_mode(self, swing_mode):
"""Set new swing mode."""
await self._client.set_swing(swing_mode)
async def async_turn_on(self):
"""Turn device on."""
await self._client.set_state(ON_MODE, 'on')
async def async_turn_off(self):
"""Turn device off."""
await self._client.set_state(ON_MODE, 'off')
await self._client.set_swing(swing_mode.capitalize())

View File

@ -6,8 +6,8 @@ from typing import Any, Dict, List
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_COOL, STATE_ECO, STATE_HEAT, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_HEAT, PRESET_AWAY, PRESET_COMFORT, PRESET_HOME, PRESET_SLEEP,
SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.helpers.typing import HomeAssistantType
@ -17,20 +17,12 @@ from .const import DATA_TOON_CLIENT, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
SUPPORT_PRESET = [PRESET_AWAY, PRESET_COMFORT, PRESET_HOME, PRESET_SLEEP]
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
SCAN_INTERVAL = timedelta(seconds=300)
HA_TOON = {
STATE_AUTO: 'Comfort',
STATE_HEAT: 'Home',
STATE_ECO: 'Away',
STATE_COOL: 'Sleep',
}
TOON_HA = {value: key for key, value in HA_TOON.items()}
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry,
async_add_entities) -> None:
@ -64,20 +56,36 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateDevice):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
return HVAC_MODE_HEAT
@property
def hvac_modes(self) -> List[str]:
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return [HVAC_MODE_HEAT]
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_operation(self) -> str:
"""Return current operation i.e. comfort, home, away."""
return TOON_HA.get(self._state)
def preset_mode(self) -> str:
"""Return the current preset mode, e.g., home, away, temp."""
return self._state.lower()
@property
def operation_list(self) -> List[str]:
"""Return a list of available operation modes."""
return list(HA_TOON.keys())
def preset_modes(self) -> List[str]:
"""Return a list of available preset modes."""
return SUPPORT_PRESET
@property
def current_temperature(self) -> float:
@ -111,9 +119,13 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateDevice):
temperature = kwargs.get(ATTR_TEMPERATURE)
self.toon.thermostat = temperature
def set_operation_mode(self, operation_mode: str) -> None:
"""Set new operation mode."""
self.toon.thermostat_state = HA_TOON[operation_mode]
def set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
self.toon.thermostat_state = preset_mode
def set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
pass
def update(self) -> None:
"""Update local state."""

View File

@ -1,11 +1,12 @@
"""Platform for Roth Touchline heat pump controller."""
import logging
from typing import List
import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE)
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_HEAT)
from homeassistant.const import CONF_HOST, TEMP_CELSIUS, ATTR_TEMPERATURE
import homeassistant.helpers.config_validation as cv
@ -52,6 +53,22 @@ class Touchline(ClimateDevice):
self._current_temperature = self.unit.get_current_temperature()
self._target_temperature = self.unit.get_target_temperature()
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
return HVAC_MODE_HEAT
@property
def hvac_modes(self) -> List[str]:
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return [HVAC_MODE_HEAT]
@property
def should_poll(self):
"""Return the polling state."""

View File

@ -1,9 +1,8 @@
"""Support for the Tuya climate devices."""
from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_COOL, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT,
SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF)
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
from homeassistant.const import (
ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
@ -13,11 +12,10 @@ from . import DATA_TUYA, TuyaDevice
DEVICE_TYPE = 'climate'
HA_STATE_TO_TUYA = {
STATE_AUTO: 'auto',
STATE_COOL: 'cold',
STATE_ECO: 'eco',
STATE_FAN_ONLY: 'wind',
STATE_HEAT: 'hot',
HVAC_MODE_AUTO: 'auto',
HVAC_MODE_COOL: 'cold',
HVAC_MODE_FAN_ONLY: 'wind',
HVAC_MODE_HEAT: 'hot',
}
TUYA_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_TUYA.items()}
@ -47,7 +45,7 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
"""Init climate device."""
super().__init__(tuya)
self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id())
self.operations = []
self.operations = [HVAC_MODE_OFF]
async def async_added_to_hass(self):
"""Create operation list when add to hass."""
@ -55,15 +53,11 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
modes = self.tuya.operation_list()
if modes is None:
return
for mode in modes:
if mode in TUYA_STATE_TO_HA:
self.operations.append(TUYA_STATE_TO_HA[mode])
@property
def is_on(self):
"""Return true if climate is on."""
return self.tuya.state()
@property
def precision(self):
"""Return the precision of the system."""
@ -73,22 +67,23 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
def temperature_unit(self):
"""Return the unit of measurement used by the platform."""
unit = self.tuya.temperature_unit()
if unit == 'CELSIUS':
return TEMP_CELSIUS
if unit == 'FAHRENHEIT':
return TEMP_FAHRENHEIT
return TEMP_CELSIUS
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
if not self.tuya.state():
return HVAC_MODE_OFF
mode = self.tuya.current_operation()
if mode is None:
return None
return TUYA_STATE_TO_HA.get(mode)
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return self.operations
@ -108,14 +103,14 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
return self.tuya.target_temperature_step()
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self.tuya.current_fan_mode()
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return self.tuya.fan_list()
return self.tuya.fan_modes()
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@ -126,26 +121,22 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice):
"""Set new target fan mode."""
self.tuya.set_fan_mode(fan_mode)
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
self.tuya.set_operation_mode(HA_STATE_TO_TUYA.get(operation_mode))
if hvac_mode == HVAC_MODE_OFF:
self.tuya.turn_off()
def turn_on(self):
"""Turn device on."""
self.tuya.turn_on()
if not self.tuya.state():
self.tuya.turn_on()
def turn_off(self):
"""Turn device off."""
self.tuya.turn_off()
self.tuya.set_operation_mode(HA_STATE_TO_TUYA.get(hvac_mode))
@property
def supported_features(self):
"""Return the list of supported features."""
supports = SUPPORT_ON_OFF
supports = 0
if self.tuya.support_target_temperature():
supports = supports | SUPPORT_TARGET_TEMPERATURE
if self.tuya.support_mode():
supports = supports | SUPPORT_OPERATION_MODE
if self.tuya.support_wind_speed():
supports = supports | SUPPORT_FAN_MODE
return supports

View File

@ -3,15 +3,13 @@ import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_HEAT, SUPPORT_TARGET_TEMPERATURE)
HVAC_MODE_HEAT, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from . import DOMAIN as VELBUS_DOMAIN, VelbusEntity
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
@ -34,7 +32,7 @@ class VelbusClimate(VelbusEntity, ClimateDevice):
@property
def supported_features(self):
"""Return the list off supported features."""
return SUPPORT_FLAGS
return SUPPORT_TARGET_TEMPERATURE
@property
def temperature_unit(self):
@ -49,9 +47,20 @@ class VelbusClimate(VelbusEntity, ClimateDevice):
return self._module.get_state(self._channel)
@property
def current_operation(self):
"""Return current operation."""
return STATE_HEAT
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
return HVAC_MODE_HEAT
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return [HVAC_MODE_HEAT]
@property
def target_temperature(self):
@ -65,3 +74,7 @@ class VelbusClimate(VelbusEntity, ClimateDevice):
return
self._module.set_temp(temp)
self.schedule_update_ha_state()
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
pass

View File

@ -5,29 +5,30 @@ import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate.const import (
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_HUMIDITY, SUPPORT_AWAY_MODE,
SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW,
SUPPORT_HOLD_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
SUPPORT_TARGET_HUMIDITY, SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE, PRESET_AWAY,
SUPPORT_TARGET_TEMPERATURE_RANGE,
HVAC_MODE_OFF)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_TIMEOUT,
CONF_USERNAME, PRECISION_WHOLE, STATE_OFF, STATE_ON, TEMP_CELSIUS,
CONF_USERNAME, PRECISION_WHOLE, STATE_ON, TEMP_CELSIUS,
TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
ATTR_FAN_STATE = 'fan_state'
ATTR_HVAC_STATE = 'hvac_state'
ATTR_HVAC_STATE = 'hvac_mode'
CONF_HUMIDIFIER = 'humidifier'
DEFAULT_SSL = False
VALID_FAN_STATES = [STATE_ON, STATE_AUTO]
VALID_THERMOSTAT_MODES = [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_AUTO]
VALID_FAN_STATES = [STATE_ON, HVAC_MODE_AUTO]
VALID_THERMOSTAT_MODES = [HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF,
HVAC_MODE_AUTO]
HOLD_MODE_OFF = 'off'
HOLD_MODE_TEMPERATURE = 'temperature'
@ -84,18 +85,14 @@ class VenstarThermostat(ClimateDevice):
def supported_features(self):
"""Return the list of supported features."""
features = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE |
SUPPORT_HOLD_MODE)
SUPPORT_PRESET_MODE)
if self._client.mode == self._client.MODE_AUTO:
features |= (SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW)
features |= (SUPPORT_TARGET_TEMPERATURE_RANGE)
if (self._humidifier and
hasattr(self._client, 'hum_active')):
features |= (SUPPORT_TARGET_HUMIDITY |
SUPPORT_TARGET_HUMIDITY_HIGH |
SUPPORT_TARGET_HUMIDITY_LOW)
features |= SUPPORT_TARGET_HUMIDITY
return features
@ -121,12 +118,12 @@ class VenstarThermostat(ClimateDevice):
return TEMP_CELSIUS
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return VALID_FAN_STATES
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return VALID_THERMOSTAT_MODES
@ -141,21 +138,21 @@ class VenstarThermostat(ClimateDevice):
return self._client.get_indoor_humidity()
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
if self._client.mode == self._client.MODE_HEAT:
return STATE_HEAT
return HVAC_MODE_HEAT
if self._client.mode == self._client.MODE_COOL:
return STATE_COOL
return HVAC_MODE_COOL
if self._client.mode == self._client.MODE_AUTO:
return STATE_AUTO
return STATE_OFF
return HVAC_MODE_AUTO
return HVAC_MODE_OFF
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
if self._client.fan == self._client.FAN_AUTO:
return STATE_AUTO
return HVAC_MODE_AUTO
return STATE_ON
@property
@ -205,24 +202,28 @@ class VenstarThermostat(ClimateDevice):
return 60
@property
def is_away_mode_on(self):
"""Return the status of away mode."""
return self._client.away == self._client.AWAY_AWAY
@property
def current_hold_mode(self):
"""Return the status of hold mode."""
def preset_mode(self):
"""Return current preset."""
if self._client.away:
return PRESET_AWAY
if self._client.schedule == 0:
return HOLD_MODE_TEMPERATURE
return HOLD_MODE_OFF
@property
def preset_modes(self):
"""Return valid preset modes."""
return [
PRESET_AWAY,
HOLD_MODE_TEMPERATURE,
]
def _set_operation_mode(self, operation_mode):
"""Change the operation mode (internal)."""
if operation_mode == STATE_HEAT:
if operation_mode == HVAC_MODE_HEAT:
success = self._client.set_mode(self._client.MODE_HEAT)
elif operation_mode == STATE_COOL:
elif operation_mode == HVAC_MODE_COOL:
success = self._client.set_mode(self._client.MODE_COOL)
elif operation_mode == STATE_AUTO:
elif operation_mode == HVAC_MODE_AUTO:
success = self._client.set_mode(self._client.MODE_AUTO)
else:
success = self._client.set_mode(self._client.MODE_OFF)
@ -234,7 +235,7 @@ class VenstarThermostat(ClimateDevice):
def set_temperature(self, **kwargs):
"""Set a new target temperature."""
set_temp = True
operation_mode = kwargs.get(ATTR_OPERATION_MODE, self._client.mode)
operation_mode = kwargs.get(ATTR_HVAC_MODE, self._client.mode)
temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
temperature = kwargs.get(ATTR_TEMPERATURE)
@ -268,9 +269,9 @@ class VenstarThermostat(ClimateDevice):
if not success:
_LOGGER.error("Failed to change the fan mode")
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
self._set_operation_mode(operation_mode)
self._set_operation_mode(hvac_mode)
def set_humidity(self, humidity):
"""Set new target humidity."""
@ -279,29 +280,21 @@ class VenstarThermostat(ClimateDevice):
if not success:
_LOGGER.error("Failed to change the target humidity level")
def set_hold_mode(self, hold_mode):
def set_preset_mode(self, preset_mode):
"""Set the hold mode."""
if hold_mode == HOLD_MODE_TEMPERATURE:
if preset_mode == PRESET_AWAY:
success = self._client.set_away(self._client.AWAY_AWAY)
elif preset_mode == HOLD_MODE_TEMPERATURE:
success = self._client.set_schedule(0)
elif hold_mode == HOLD_MODE_OFF:
success = self._client.set_schedule(1)
elif preset_mode is None:
success = False
if self._client.away:
success = self._client.set_away(self._client.AWAY_HOME)
if self._client.schedule == 0:
success = success and self._client.set_schedule(1)
else:
_LOGGER.error("Unknown hold mode: %s", hold_mode)
_LOGGER.error("Unknown hold mode: %s", preset_mode)
success = False
if not success:
_LOGGER.error("Failed to change the schedule/hold state")
def turn_away_mode_on(self):
"""Activate away mode."""
success = self._client.set_away(self._client.AWAY_AWAY)
if not success:
_LOGGER.error("Failed to activate away mode")
def turn_away_mode_off(self):
"""Deactivate away mode."""
success = self._client.set_away(self._client.AWAY_HOME)
if not success:
_LOGGER.error("Failed to deactivate away mode")

View File

@ -3,21 +3,22 @@ import logging
from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
FAN_AUTO, FAN_ON, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT)
ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.util import convert
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
_LOGGER = logging.getLogger(__name__)
OPERATION_LIST = [STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_OFF]
FAN_OPERATION_LIST = [STATE_ON, STATE_AUTO]
FAN_OPERATION_LIST = [FAN_ON, FAN_AUTO]
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_FAN_MODE)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
SUPPORT_HVAC = [
HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF
]
def setup_platform(hass, config, add_entities_callback, discovery_info=None):
@ -41,42 +42,44 @@ class VeraThermostat(VeraDevice, ClimateDevice):
return SUPPORT_FLAGS
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
mode = self.vera_device.get_hvac_mode()
if mode == 'HeatOn':
return OPERATION_LIST[0] # Heat
return HVAC_MODE_HEAT
if mode == 'CoolOn':
return OPERATION_LIST[1] # Cool
return HVAC_MODE_COOL
if mode == 'AutoChangeOver':
return OPERATION_LIST[2] # Auto
if mode == 'Off':
return OPERATION_LIST[3] # Off
return 'Off'
return HVAC_MODE_HEAT_COOL
return HVAC_MODE_OFF
@property
def operation_list(self):
"""Return the list of available operation modes."""
return OPERATION_LIST
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return SUPPORT_HVAC
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
mode = self.vera_device.get_fan_mode()
if mode == "ContinuousOn":
return FAN_OPERATION_LIST[0] # on
if mode == "Auto":
return FAN_OPERATION_LIST[1] # auto
return "Auto"
return FAN_ON
return FAN_AUTO
@property
def fan_list(self):
def fan_modes(self):
"""Return a list of available fan modes."""
return FAN_OPERATION_LIST
def set_fan_mode(self, fan_mode):
"""Set new target temperature."""
if fan_mode == FAN_OPERATION_LIST[0]:
if fan_mode == FAN_ON:
self.vera_device.fan_on()
else:
self.vera_device.fan_auto()
@ -107,7 +110,7 @@ class VeraThermostat(VeraDevice, ClimateDevice):
@property
def operation(self):
"""Return current operation ie. heat, cool, idle."""
return self.vera_device.get_hvac_state()
return self.vera_device.get_hvac_mode()
@property
def target_temperature(self):
@ -119,21 +122,13 @@ class VeraThermostat(VeraDevice, ClimateDevice):
if kwargs.get(ATTR_TEMPERATURE) is not None:
self.vera_device.set_temperature(kwargs.get(ATTR_TEMPERATURE))
def set_operation_mode(self, operation_mode):
"""Set HVAC mode (auto, cool, heat, off)."""
if operation_mode == OPERATION_LIST[3]: # off
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if hvac_mode == HVAC_MODE_OFF:
self.vera_device.turn_off()
elif operation_mode == OPERATION_LIST[2]: # auto
elif hvac_mode == HVAC_MODE_HEAT_COOL:
self.vera_device.turn_auto_on()
elif operation_mode == OPERATION_LIST[1]: # cool
elif hvac_mode == HVAC_MODE_COOL:
self.vera_device.turn_cool_on()
elif operation_mode == OPERATION_LIST[0]: # heat
elif hvac_mode == HVAC_MODE_HEAT:
self.vera_device.turn_heat_on()
def turn_fan_on(self):
"""Turn fan on."""
self.vera_device.fan_on()
def turn_fan_off(self):
"""Turn fan off."""
self.vera_device.fan_auto()

View File

@ -1,16 +1,18 @@
"""Support for Wink thermostats and Air Conditioners."""
import logging
import pywink
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
ATTR_CURRENT_HUMIDITY, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
STATE_AUTO, STATE_COOL, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
SUPPORT_AUX_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL,
CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, FAN_AUTO, FAN_HIGH,
FAN_LOW, FAN_MEDIUM, FAN_ON, HVAC_MODE_AUTO, HVAC_MODE_COOL,
HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_ECO,
SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE)
from homeassistant.const import (
ATTR_TEMPERATURE, PRECISION_TENTHS, STATE_OFF, STATE_ON, STATE_UNKNOWN,
TEMP_CELSIUS)
ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS)
from homeassistant.helpers.temperature import display_temp as show_temp
from . import DOMAIN, WinkDevice
@ -23,36 +25,30 @@ ATTR_OCCUPIED = 'occupied'
ATTR_SCHEDULE_ENABLED = 'schedule_enabled'
ATTR_SMART_TEMPERATURE = 'smart_temperature'
ATTR_TOTAL_CONSUMPTION = 'total_consumption'
ATTR_HEAT_ON = 'heat_on'
ATTR_COOL_ON = 'cool_on'
SPEED_LOW = 'low'
SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high'
HA_STATE_TO_WINK = {
STATE_AUTO: 'auto',
STATE_COOL: 'cool_only',
STATE_ECO: 'eco',
STATE_FAN_ONLY: 'fan_only',
STATE_HEAT: 'heat_only',
STATE_OFF: 'off',
HA_HVAC_TO_WINK = {
HVAC_MODE_AUTO: 'auto',
HVAC_MODE_COOL: 'cool_only',
HVAC_MODE_FAN_ONLY: 'fan_only',
HVAC_MODE_HEAT: 'heat_only',
HVAC_MODE_OFF: 'off',
}
WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()}
WINK_HVAC_TO_HA = {value: key for key, value in HA_HVAC_TO_WINK.items()}
SUPPORT_FLAGS_THERMOSTAT = (
SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_OPERATION_MODE |
SUPPORT_AWAY_MODE | SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT)
SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_RANGE |
SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT)
SUPPORT_FAN_THERMOSTAT = [FAN_AUTO, FAN_ON]
SUPPORT_PRESET_THERMOSTAT = [PRESET_AWAY, PRESET_ECO]
SUPPORT_FLAGS_AC = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_FAN_MODE)
SUPPORT_FLAGS_AC = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
SUPPORT_FAN_AC = [FAN_HIGH, FAN_LOW, FAN_MEDIUM]
SUPPORT_PRESET_AC = [PRESET_ECO]
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink climate devices."""
import pywink
for climate in pywink.get_thermostats():
_id = climate.object_id() + climate.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
@ -85,17 +81,6 @@ class WinkThermostat(WinkDevice, ClimateDevice):
def device_state_attributes(self):
"""Return the optional device state attributes."""
data = {}
target_temp_high = self.target_temperature_high
target_temp_low = self.target_temperature_low
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.hass, self.target_temperature_high, self.temperature_unit,
PRECISION_TENTHS)
if target_temp_low is not None:
data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit,
PRECISION_TENTHS)
if self.external_temperature is not None:
data[ATTR_EXTERNAL_TEMPERATURE] = show_temp(
self.hass, self.external_temperature, self.temperature_unit,
@ -110,16 +95,6 @@ class WinkThermostat(WinkDevice, ClimateDevice):
if self.eco_target is not None:
data[ATTR_ECO_TARGET] = self.eco_target
if self.heat_on is not None:
data[ATTR_HEAT_ON] = self.heat_on
if self.cool_on is not None:
data[ATTR_COOL_ON] = self.cool_on
current_humidity = self.current_humidity
if current_humidity is not None:
data[ATTR_CURRENT_HUMIDITY] = current_humidity
return data
@property
@ -160,27 +135,19 @@ class WinkThermostat(WinkDevice, ClimateDevice):
return self.wink.occupied()
@property
def heat_on(self):
"""Return whether or not the heat is actually heating."""
return self.wink.heat_on()
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp."""
mode = self.wink.current_mode()
if mode == "eco":
return PRESET_ECO
if self.wink.away():
return PRESET_AWAY
return None
@property
def cool_on(self):
"""Return whether or not the heat is actually heating."""
return self.wink.cool_on()
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if not self.wink.is_on():
current_op = STATE_OFF
else:
current_op = WINK_STATE_TO_HA.get(self.wink.current_hvac_mode())
if current_op == 'aux':
return STATE_HEAT
if current_op is None:
current_op = STATE_UNKNOWN
return current_op
def preset_modes(self):
"""Return a list of available preset modes."""
return SUPPORT_PRESET_THERMOSTAT
@property
def target_humidity(self):
@ -199,51 +166,96 @@ class WinkThermostat(WinkDevice, ClimateDevice):
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.current_operation != STATE_AUTO and not self.is_away_mode_on:
if self.current_operation == STATE_COOL:
if self.hvac_mode != HVAC_MODE_AUTO and not self.wink.away():
if self.hvac_mode == HVAC_MODE_COOL:
return self.wink.current_max_set_point()
if self.current_operation == STATE_HEAT:
if self.hvac_mode == HVAC_MODE_HEAT:
return self.wink.current_min_set_point()
return None
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
if self.hvac_mode == HVAC_MODE_AUTO:
return self.wink.current_min_set_point()
return None
@property
def target_temperature_high(self):
"""Return the higher bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
if self.hvac_mode == HVAC_MODE_AUTO:
return self.wink.current_max_set_point()
return None
@property
def is_away_mode_on(self):
"""Return if away mode is on."""
return self.wink.away()
@property
def is_aux_heat_on(self):
def is_aux_heat(self):
"""Return true if aux heater."""
if 'aux' not in self.wink.hvac_modes():
return None
if self.wink.current_hvac_mode() == 'aux':
if self.wink.hvac_action_mode() == 'aux':
return True
return False
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if not self.wink.is_on():
return HVAC_MODE_OFF
wink_mode = self.wink.current_mode()
if wink_mode == "aux":
return HVAC_MODE_HEAT
if wink_mode == "eco":
return HVAC_MODE_AUTO
return WINK_HVAC_TO_HA.get(wink_mode)
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
hvac_list = [HVAC_MODE_OFF]
modes = self.wink.modes()
for mode in modes:
if mode in ("eco", "aux"):
continue
try:
ha_mode = WINK_HVAC_TO_HA[mode]
hvac_list.append(ha_mode)
except KeyError:
_LOGGER.error(
"Invalid operation mode mapping. %s doesn't map. "
"Please report this.", mode)
return hvac_list
@property
def hvac_action(self):
"""Return the current running hvac operation if supported.
Need to be one of CURRENT_HVAC_*.
"""
if not self.wink.is_on():
return CURRENT_HVAC_OFF
if self.wink.cool_on:
return CURRENT_HVAC_COOL
if self.wink.heat_on:
return CURRENT_HVAC_HEAT
return CURRENT_HVAC_IDLE
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if target_temp is not None:
if self.current_operation == STATE_COOL:
if self.hvac_mode == HVAC_MODE_COOL:
target_temp_high = target_temp
if self.current_operation == STATE_HEAT:
if self.hvac_mode == HVAC_MODE_HEAT:
target_temp_low = target_temp
if target_temp_low is not None:
target_temp_low = target_temp_low
@ -251,54 +263,37 @@ class WinkThermostat(WinkDevice, ClimateDevice):
target_temp_high = target_temp_high
self.wink.set_temperature(target_temp_low, target_temp_high)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
# The only way to disable aux heat is with the toggle
if self.is_aux_heat_on and op_mode_to_set == STATE_HEAT:
return
self.wink.set_operation_mode(op_mode_to_set)
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
hvac_mode_to_set = HA_HVAC_TO_WINK.get(hvac_mode)
self.wink.set_operation_mode(hvac_mode_to_set)
def set_preset_mode(self, preset_mode):
"""Set new preset mode."""
# Away
if preset_mode != PRESET_AWAY and self.wink.away():
self.wink.set_away_mode(False)
elif preset_mode == PRESET_AWAY:
self.wink.set_away_mode()
if preset_mode == PRESET_ECO:
self.wink.set_operation_mode("eco")
@property
def operation_list(self):
"""List of available operation modes."""
op_list = ['off']
modes = self.wink.hvac_modes()
for mode in modes:
if mode == 'aux':
continue
ha_mode = WINK_STATE_TO_HA.get(mode)
if ha_mode is not None:
op_list.append(ha_mode)
else:
error = "Invalid operation mode mapping. " + mode + \
" doesn't map. Please report this."
_LOGGER.error(error)
return op_list
def turn_away_mode_on(self):
"""Turn away on."""
self.wink.set_away_mode()
def turn_away_mode_off(self):
"""Turn away off."""
self.wink.set_away_mode(False)
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return whether the fan is on."""
if self.wink.current_fan_mode() == 'on':
return STATE_ON
return FAN_ON
if self.wink.current_fan_mode() == 'auto':
return STATE_AUTO
return FAN_AUTO
# No Fan available so disable slider
return None
@property
def fan_list(self):
def fan_modes(self):
"""List of available fan modes."""
if self.wink.has_fan():
return self.wink.fan_modes()
return SUPPORT_FAN_THERMOSTAT
return None
def set_fan_mode(self, fan_mode):
@ -311,7 +306,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
def turn_aux_heat_off(self):
"""Turn auxiliary heater off."""
self.set_operation_mode(STATE_HEAT)
self.wink.set_operation_mode('heat_only')
@property
def min_temp(self):
@ -319,17 +314,17 @@ class WinkThermostat(WinkDevice, ClimateDevice):
minimum = 7 # Default minimum
min_min = self.wink.min_min_set_point()
min_max = self.wink.min_max_set_point()
if self.current_operation == STATE_HEAT:
if self.hvac_mode == HVAC_MODE_HEAT:
if min_min:
return_value = min_min
else:
return_value = minimum
elif self.current_operation == STATE_COOL:
elif self.hvac_mode == HVAC_MODE_COOL:
if min_max:
return_value = min_max
else:
return_value = minimum
elif self.current_operation == STATE_AUTO:
elif self.hvac_mode == HVAC_MODE_AUTO:
if min_min and min_max:
return_value = min(min_min, min_max)
else:
@ -344,17 +339,17 @@ class WinkThermostat(WinkDevice, ClimateDevice):
maximum = 35 # Default maximum
max_min = self.wink.max_min_set_point()
max_max = self.wink.max_max_set_point()
if self.current_operation == STATE_HEAT:
if self.hvac_mode == HVAC_MODE_HEAT:
if max_min:
return_value = max_min
else:
return_value = maximum
elif self.current_operation == STATE_COOL:
elif self.hvac_mode == HVAC_MODE_COOL:
if max_max:
return_value = max_max
else:
return_value = maximum
elif self.current_operation == STATE_AUTO:
elif self.hvac_mode == HVAC_MODE_AUTO:
if max_min and max_max:
return_value = min(max_min, max_max)
else:
@ -382,16 +377,6 @@ class WinkAC(WinkDevice, ClimateDevice):
def device_state_attributes(self):
"""Return the optional device state attributes."""
data = {}
target_temp_high = self.target_temperature_high
target_temp_low = self.target_temperature_low
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.hass, self.target_temperature_high, self.temperature_unit,
PRECISION_TENTHS)
if target_temp_low is not None:
data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit,
PRECISION_TENTHS)
data[ATTR_TOTAL_CONSUMPTION] = self.wink.total_consumption()
data[ATTR_SCHEDULE_ENABLED] = self.wink.schedule_enabled()
@ -403,47 +388,67 @@ class WinkAC(WinkDevice, ClimateDevice):
return self.wink.current_temperature()
@property
def current_operation(self):
"""Return current operation ie. auto_eco, cool_only, fan_only."""
if not self.wink.is_on():
current_op = STATE_OFF
else:
wink_mode = self.wink.current_mode()
if wink_mode == "auto_eco":
wink_mode = "eco"
current_op = WINK_STATE_TO_HA.get(wink_mode)
if current_op is None:
current_op = STATE_UNKNOWN
return current_op
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp."""
mode = self.wink.current_mode()
if mode == "auto_eco":
return PRESET_ECO
return None
@property
def operation_list(self):
"""List of available operation modes."""
op_list = ['off']
def preset_modes(self):
"""Return a list of available preset modes."""
return SUPPORT_PRESET_AC
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if not self.wink.is_on():
return HVAC_MODE_OFF
wink_mode = self.wink.current_mode()
if wink_mode == "auto_eco":
return HVAC_MODE_AUTO
return WINK_HVAC_TO_HA.get(wink_mode)
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
hvac_list = [HVAC_MODE_OFF]
modes = self.wink.modes()
for mode in modes:
if mode == "auto_eco":
mode = "eco"
ha_mode = WINK_STATE_TO_HA.get(mode)
if ha_mode is not None:
op_list.append(ha_mode)
else:
error = "Invalid operation mode mapping. " + mode + \
" doesn't map. Please report this."
_LOGGER.error(error)
return op_list
continue
try:
ha_mode = WINK_HVAC_TO_HA[mode]
hvac_list.append(ha_mode)
except KeyError:
_LOGGER.error(
"Invalid operation mode mapping. %s doesn't map. "
"Please report this.", mode)
return hvac_list
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
self.wink.set_temperature(target_temp)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
if op_mode_to_set == 'eco':
op_mode_to_set = 'auto_eco'
self.wink.set_operation_mode(op_mode_to_set)
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
hvac_mode_to_set = HA_HVAC_TO_WINK.get(hvac_mode)
self.wink.set_operation_mode(hvac_mode_to_set)
def set_preset_mode(self, preset_mode):
"""Set new preset mode."""
if preset_mode == PRESET_ECO:
self.wink.set_operation_mode("auto_eco")
@property
def target_temperature(self):
@ -451,7 +456,7 @@ class WinkAC(WinkDevice, ClimateDevice):
return self.wink.current_max_set_point()
@property
def current_fan_mode(self):
def fan_mode(self):
"""
Return the current fan mode.
@ -460,15 +465,15 @@ class WinkAC(WinkDevice, ClimateDevice):
"""
speed = self.wink.current_fan_speed()
if speed <= 0.33:
return SPEED_LOW
return FAN_LOW
if speed <= 0.66:
return SPEED_MEDIUM
return SPEED_HIGH
return FAN_MEDIUM
return FAN_HIGH
@property
def fan_list(self):
def fan_modes(self):
"""Return a list of available fan modes."""
return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
return SUPPORT_FAN_AC
def set_fan_mode(self, fan_mode):
"""
@ -477,10 +482,10 @@ class WinkAC(WinkDevice, ClimateDevice):
The official Wink app only supports 3 modes [low, medium, high]
which are equal to [0.33, 0.66, 1.0] respectively.
"""
if fan_mode == SPEED_LOW:
if fan_mode == FAN_LOW:
speed = 0.33
elif fan_mode == SPEED_MEDIUM:
elif fan_mode == FAN_MEDIUM:
speed = 0.66
elif fan_mode == SPEED_HIGH:
elif fan_mode == FAN_HIGH:
speed = 1.0
self.wink.set_ac_fan_speed(speed)

View File

@ -1,9 +1,9 @@
"""Support for the EZcontrol XS1 gateway."""
import asyncio
from functools import partial
import logging
import voluptuous as vol
import xs1_api_client
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_USERNAME)
@ -40,20 +40,7 @@ XS1_COMPONENTS = [
UPDATE_LOCK = asyncio.Lock()
def _create_controller_api(host, port, ssl, user, password):
"""Create an api instance to use for communication."""
import xs1_api_client
try:
return xs1_api_client.XS1(
host=host, port=port, ssl=ssl, user=user, password=password)
except ConnectionError as error:
_LOGGER.error("Failed to create XS1 API client "
"because of a connection error: %s", error)
return None
async def async_setup(hass, config):
def setup(hass, config):
"""Set up XS1 Component."""
_LOGGER.debug("Initializing XS1")
@ -64,9 +51,12 @@ async def async_setup(hass, config):
password = config[DOMAIN].get(CONF_PASSWORD)
# initialize XS1 API
xs1 = await hass.async_add_executor_job(
partial(_create_controller_api, host, port, ssl, user, password))
if xs1 is None:
try:
xs1 = xs1_api_client.XS1(
host=host, port=port, ssl=ssl, user=user, password=password)
except ConnectionError as error:
_LOGGER.error("Failed to create XS1 API client "
"because of a connection error: %s", error)
return False
_LOGGER.debug(
@ -74,10 +64,8 @@ async def async_setup(hass, config):
hass.data[DOMAIN] = {}
actuators = await hass.async_add_executor_job(
partial(xs1.get_all_actuators, enabled=True))
sensors = await hass.async_add_executor_job(
partial(xs1.get_all_sensors, enabled=True))
actuators = xs1.get_all_actuators(enabled=True)
sensors = xs1.get_all_sensors(enabled=True)
hass.data[DOMAIN][ACTUATORS] = actuators
hass.data[DOMAIN][SENSORS] = sensors
@ -85,9 +73,7 @@ async def async_setup(hass, config):
_LOGGER.debug("Loading components for XS1 platform...")
# Load components for supported devices
for component in XS1_COMPONENTS:
hass.async_create_task(
discovery.async_load_platform(
hass, component, DOMAIN, {}, config))
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
@ -102,5 +88,4 @@ class XS1DeviceEntity(Entity):
async def async_update(self):
"""Retrieve latest device state."""
async with UPDATE_LOCK:
await self.hass.async_add_executor_job(
partial(self.device.update))
await self.hass.async_add_executor_job(self.device.update)

View File

@ -1,9 +1,11 @@
"""Support for XS1 climate devices."""
from functools import partial
import logging
from xs1_api_client.api_constants import ActuatorType
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE
from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_HEAT)
from homeassistant.const import ATTR_TEMPERATURE
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity
@ -13,12 +15,11 @@ _LOGGER = logging.getLogger(__name__)
MIN_TEMP = 8
MAX_TEMP = 25
SUPPORT_HVAC = [HVAC_MODE_HEAT]
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the XS1 thermostat platform."""
from xs1_api_client.api_constants import ActuatorType
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
sensors = hass.data[COMPONENT_DOMAIN][SENSORS]
@ -37,7 +38,7 @@ async def async_setup_platform(
thermostat_entities.append(
XS1ThermostatEntity(actuator, matching_sensor))
async_add_entities(thermostat_entities)
add_entities(thermostat_entities)
class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice):
@ -58,6 +59,22 @@ class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice):
"""Flag supported features."""
return SUPPORT_TARGET_TEMPERATURE
@property
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
return HVAC_MODE_HEAT
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return SUPPORT_HVAC
@property
def current_temperature(self):
"""Return the current temperature."""
@ -95,9 +112,12 @@ class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice):
if self.sensor is not None:
self.schedule_update_ha_state()
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
pass
async def async_update(self):
"""Also update the sensor when available."""
await super().async_update()
if self.sensor is not None:
await self.hass.async_add_executor_job(
partial(self.sensor.update))
if self.sensor is None:
await self.hass.async_add_executor_job(self.sensor.update)

View File

@ -1,6 +1,8 @@
"""Support for XS1 sensors."""
import logging
from xs1_api_client.api_constants import ActuatorType
from homeassistant.helpers.entity import Entity
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity
@ -8,11 +10,8 @@ from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the XS1 sensor platform."""
from xs1_api_client.api_constants import ActuatorType
sensors = hass.data[COMPONENT_DOMAIN][SENSORS]
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
@ -28,7 +27,7 @@ async def async_setup_platform(
if not belongs_to_climate_actuator:
sensor_entities.append(XS1Sensor(sensor))
async_add_entities(sensor_entities)
add_entities(sensor_entities)
class XS1Sensor(XS1DeviceEntity, Entity):

View File

@ -1,6 +1,8 @@
"""Support for XS1 switches."""
import logging
from xs1_api_client.api_constants import ActuatorType
from homeassistant.helpers.entity import ToggleEntity
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity
@ -8,11 +10,8 @@ from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the XS1 switch platform."""
from xs1_api_client.api_constants import ActuatorType
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
switch_entities = []
@ -21,7 +20,7 @@ async def async_setup_platform(
(actuator.type() == ActuatorType.DIMMER):
switch_entities.append(XS1SwitchEntity(actuator))
async_add_entities(switch_entities)
add_entities(switch_entities)
class XS1SwitchEntity(XS1DeviceEntity, ToggleEntity):

View File

@ -3,16 +3,17 @@ import logging
import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
ATTR_OPERATION_MODE, STATE_COOL, STATE_DRY,
STATE_FAN_ONLY, STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (ATTR_TEMPERATURE, CONF_HOST, CONF_PORT,
EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS)
ATTR_HVAC_MODE, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP,
TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (async_dispatcher_connect,
async_dispatcher_send)
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
_LOGGER = logging.getLogger(__name__)
@ -31,6 +32,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
cv.positive_int,
})
SUPPORT_HVAC = [HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY]
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the ZhongHong HVAC platform."""
@ -86,7 +90,6 @@ class ZhongHongClimate(ClimateDevice):
self._current_temperature = None
self._target_temperature = None
self._current_fan_mode = None
self._is_on = None
self.is_initialized = False
async def async_added_to_hass(self):
@ -106,7 +109,6 @@ class ZhongHongClimate(ClimateDevice):
self._current_fan_mode = self._device.current_fan_mode
if self._device.target_temperature:
self._target_temperature = self._device.target_temperature
self._is_on = self._device.is_on
self.schedule_update_ha_state()
@property
@ -128,8 +130,7 @@ class ZhongHongClimate(ClimateDevice):
@property
def supported_features(self):
"""Return the list of supported features."""
return (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
| SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF)
return SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
@property
def temperature_unit(self):
@ -137,14 +138,14 @@ class ZhongHongClimate(ClimateDevice):
return TEMP_CELSIUS
@property
def current_operation(self):
def hvac_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
@property
def operation_list(self):
def hvac_modes(self):
"""Return the list of available operation modes."""
return [STATE_COOL, STATE_HEAT, STATE_DRY, STATE_FAN_ONLY]
return SUPPORT_HVAC
@property
def current_temperature(self):
@ -167,12 +168,12 @@ class ZhongHongClimate(ClimateDevice):
return self._device.is_on
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan setting."""
return self._current_fan_mode
@property
def fan_list(self):
def fan_modes(self):
"""Return the list of available fan modes."""
return self._device.fan_list
@ -200,13 +201,13 @@ class ZhongHongClimate(ClimateDevice):
if temperature is not None:
self._device.set_temperature(temperature)
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
operation_mode = kwargs.get(ATTR_HVAC_MODE)
if operation_mode is not None:
self.set_operation_mode(operation_mode)
self.set_hvac_mode(operation_mode)
def set_operation_mode(self, operation_mode):
def set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
self._device.set_operation_mode(operation_mode.upper())
self._device.set_operation_mode(hvac_mode.upper())
def set_fan_mode(self, fan_mode):
"""Set new target fan mode."""

View File

@ -1,15 +1,16 @@
"""Support for Z-Wave climate devices."""
# Because we do not compile openzwave on CI
import logging
from homeassistant.core import callback
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
DOMAIN, STATE_AUTO, STATE_COOL, STATE_HEAT,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE)
from homeassistant.const import (
STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF,
DOMAIN, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF,
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import ZWaveDeviceEntity
_LOGGER = logging.getLogger(__name__)
@ -29,13 +30,21 @@ DEVICE_MAPPINGS = {
REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120
}
STATE_MAPPINGS = {
'Off': STATE_OFF,
'Heat': STATE_HEAT,
'Heat Mode': STATE_HEAT,
'Heat (Default)': STATE_HEAT,
'Cool': STATE_COOL,
'Auto': STATE_AUTO,
HVAC_STATE_MAPPINGS = {
'Off': HVAC_MODE_OFF,
'Heat': HVAC_MODE_HEAT,
'Heat Mode': HVAC_MODE_HEAT,
'Heat (Default)': HVAC_MODE_HEAT,
'Cool': HVAC_MODE_COOL,
'Auto': HVAC_MODE_HEAT_COOL,
}
HVAC_CURRENT_MAPPINGS = {
"Idle": CURRENT_HVAC_IDLE,
"Heat": CURRENT_HVAC_HEAT,
"Cool": CURRENT_HVAC_COOL,
"Off": CURRENT_HVAC_OFF,
}
@ -69,15 +78,15 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self._target_temperature = None
self._current_temperature = None
self._current_operation = None
self._operation_list = None
self._operation_mapping = None
self._operating_state = None
self._hvac_action = None
self._hvac_list = None
self._hvac_mapping = None
self._hvac_mode = None
self._current_fan_mode = None
self._fan_list = None
self._fan_modes = None
self._fan_state = None
self._current_swing_mode = None
self._swing_list = None
self._swing_modes = None
self._unit = temp_unit
_LOGGER.debug("temp_unit is %s", self._unit)
self._zxt_120 = None
@ -100,8 +109,6 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
support = SUPPORT_TARGET_TEMPERATURE
if self.values.fan_mode:
support |= SUPPORT_FAN_MODE
if self.values.mode:
support |= SUPPORT_OPERATION_MODE
if self._zxt_120 == 1 and self.values.zxt_120_swing_mode:
support |= SUPPORT_SWING_MODE
return support
@ -110,23 +117,23 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Handle the data changes for node values."""
# Operation Mode
if self.values.mode:
self._operation_list = []
self._operation_mapping = {}
operation_list = self.values.mode.data_items
if operation_list:
for mode in operation_list:
ha_mode = STATE_MAPPINGS.get(mode)
if ha_mode and ha_mode not in self._operation_mapping:
self._operation_mapping[ha_mode] = mode
self._operation_list.append(ha_mode)
self._hvac_list = []
self._hvac_mapping = {}
hvac_list = self.values.mode.data_items
if hvac_list:
for mode in hvac_list:
ha_mode = HVAC_STATE_MAPPINGS.get(mode)
if ha_mode and ha_mode not in self._hvac_mapping:
self._hvac_mapping[ha_mode] = mode
self._hvac_list.append(ha_mode)
continue
self._operation_list.append(mode)
self._hvac_list.append(mode)
current_mode = self.values.mode.data
self._current_operation = next(
(key for key, value in self._operation_mapping.items()
self._hvac_mode = next(
(key for key, value in self._hvac_mapping.items()
if value == current_mode), current_mode)
_LOGGER.debug("self._operation_list=%s", self._operation_list)
_LOGGER.debug("self._current_operation=%s", self._current_operation)
_LOGGER.debug("self._hvac_list=%s", self._hvac_list)
_LOGGER.debug("self._hvac_action=%s", self._hvac_action)
# Current Temp
if self.values.temperature:
@ -138,20 +145,20 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
# Fan Mode
if self.values.fan_mode:
self._current_fan_mode = self.values.fan_mode.data
fan_list = self.values.fan_mode.data_items
if fan_list:
self._fan_list = list(fan_list)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
fan_modes = self.values.fan_mode.data_items
if fan_modes:
self._fan_modes = list(fan_modes)
_LOGGER.debug("self._fan_modes=%s", self._fan_modes)
_LOGGER.debug("self._current_fan_mode=%s",
self._current_fan_mode)
# Swing mode
if self._zxt_120 == 1:
if self.values.zxt_120_swing_mode:
self._current_swing_mode = self.values.zxt_120_swing_mode.data
swing_list = self.values.zxt_120_swing_mode.data_items
if swing_list:
self._swing_list = list(swing_list)
_LOGGER.debug("self._swing_list=%s", self._swing_list)
swing_modes = self.values.zxt_120_swing_mode.data_items
if swing_modes:
self._swing_modes = list(swing_modes)
_LOGGER.debug("self._swing_modes=%s", self._swing_modes)
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
# Set point
@ -168,31 +175,32 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
# Operating state
if self.values.operating_state:
self._operating_state = self.values.operating_state.data
mode = self.values.operating_state.data
self._hvac_action = HVAC_CURRENT_MAPPINGS.get(mode)
# Fan operating state
if self.values.fan_state:
self._fan_state = self.values.fan_state.data
@property
def current_fan_mode(self):
def fan_mode(self):
"""Return the fan speed set."""
return self._current_fan_mode
@property
def fan_list(self):
def fan_modes(self):
"""Return a list of available fan modes."""
return self._fan_list
return self._fan_modes
@property
def current_swing_mode(self):
def swing_mode(self):
"""Return the swing mode set."""
return self._current_swing_mode
@property
def swing_list(self):
def swing_modes(self):
"""Return a list of available swing modes."""
return self._swing_list
return self._swing_modes
@property
def temperature_unit(self):
@ -209,14 +217,30 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
return self._current_temperature
@property
def current_operation(self):
"""Return the current operation mode."""
return self._current_operation
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if self.values.mode:
return self._hvac_mode
return HVAC_MODE_HEAT
@property
def operation_list(self):
"""Return a list of available operation modes."""
return self._operation_list
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return self._hvac_list
@property
def hvac_action(self):
"""Return the current running hvac operation if supported.
Need to be one of CURRENT_HVAC_*.
"""
return self._hvac_action
@property
def target_temperature(self):
@ -225,36 +249,24 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
temperature = kwargs.get(ATTR_TEMPERATURE)
else:
if kwargs.get(ATTR_TEMPERATURE) is None:
return
self.values.primary.data = temperature
self.values.primary.data = kwargs.get(ATTR_TEMPERATURE)
def set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
if self.values.fan_mode:
self.values.fan_mode.data = fan_mode
if not self.values.fan_mode:
return
self.values.fan_mode.data = fan_mode
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
if self.values.mode:
self.values.mode.data = self._operation_mapping.get(
operation_mode, operation_mode)
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if not self.values.mode:
return
self.values.mode.data = self._hvac_mapping.get(hvac_mode, hvac_mode)
def set_swing_mode(self, swing_mode):
"""Set new target swing mode."""
if self._zxt_120 == 1:
if self.values.zxt_120_swing_mode:
self.values.zxt_120_swing_mode.data = swing_mode
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
data = super().device_state_attributes
if self._operating_state:
data[ATTR_OPERATING_STATE] = self._operating_state
if self._fan_state:
data[ATTR_FAN_STATE] = self._fan_state
return data

View File

@ -218,15 +218,11 @@ def state_as_number(state: State) -> float:
Raises ValueError if this is not possible.
"""
from homeassistant.components.climate.const import (
STATE_HEAT, STATE_COOL, STATE_IDLE)
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON,
STATE_OPEN, STATE_HOME, STATE_HEAT, STATE_COOL):
STATE_OPEN, STATE_HOME):
return 1
if state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
STATE_BELOW_HORIZON, STATE_CLOSED, STATE_NOT_HOME,
STATE_IDLE):
STATE_BELOW_HORIZON, STATE_CLOSED, STATE_NOT_HOME):
return 0
return float(state.state)

View File

@ -10,7 +10,7 @@ certifi>=2019.6.16
cryptography==2.7
distro==1.4.0
hass-nabucasa==0.15
home-assistant-frontend==20190702.0
home-assistant-frontend==20190705.0
importlib-metadata==0.18
jinja2>=2.10.1
netdisco==2.6.0

View File

@ -442,8 +442,7 @@ eternalegypt==0.0.7
# evdev==0.6.1
# homeassistant.components.evohome
# homeassistant.components.honeywell
evohomeclient==0.3.2
evohomeclient==0.3.3
# homeassistant.components.dlib_face_detect
# homeassistant.components.dlib_face_identify
@ -602,7 +601,7 @@ hole==0.3.0
holidays==0.9.10
# homeassistant.components.frontend
home-assistant-frontend==20190702.0
home-assistant-frontend==20190705.0
# homeassistant.components.zwave
homeassistant-pyozw==0.1.4
@ -611,7 +610,7 @@ homeassistant-pyozw==0.1.4
homekit[IP]==0.14.0
# homeassistant.components.homematicip_cloud
homematicip==0.10.7
homematicip==0.10.9
# homeassistant.components.horizon
horimote==0.4.1

View File

@ -109,8 +109,7 @@ enocean==0.50
ephem==3.7.6.0
# homeassistant.components.evohome
# homeassistant.components.honeywell
evohomeclient==0.3.2
evohomeclient==0.3.3
# homeassistant.components.feedreader
feedparser-homeassistant==5.2.2.dev1
@ -160,13 +159,13 @@ hdate==0.8.8
holidays==0.9.10
# homeassistant.components.frontend
home-assistant-frontend==20190702.0
home-assistant-frontend==20190705.0
# homeassistant.components.homekit_controller
homekit[IP]==0.14.0
# homeassistant.components.homematicip_cloud
homematicip==0.10.7
homematicip==0.10.9
# homeassistant.components.google
# homeassistant.components.remember_the_milk

View File

@ -823,14 +823,15 @@ async def test_thermostat(hass):
'climate.test_thermostat',
'cool',
{
'operation_mode': 'cool',
'temperature': 70.0,
'target_temp_high': 80.0,
'target_temp_low': 60.0,
'current_temperature': 75.0,
'friendly_name': "Test Thermostat",
'supported_features': 1 | 2 | 4 | 128,
'operation_list': ['heat', 'cool', 'auto', 'off'],
'hvac_modes': ['heat', 'cool', 'auto', 'off'],
'preset_mode': None,
'preset_modes': ['eco'],
'min_temp': 50,
'max_temp': 90,
}
@ -948,22 +949,22 @@ async def test_thermostat(hass):
# Setting mode, the payload can be an object with a value attribute...
call, msg = await assert_request_calls_service(
'Alexa.ThermostatController', 'SetThermostatMode',
'climate#test_thermostat', 'climate.set_operation_mode',
'climate#test_thermostat', 'climate.set_hvac_mode',
hass,
payload={'thermostatMode': {'value': 'HEAT'}}
)
assert call.data['operation_mode'] == 'heat'
assert call.data['hvac_mode'] == 'heat'
properties = ReportedProperties(msg['context']['properties'])
properties.assert_equal(
'Alexa.ThermostatController', 'thermostatMode', 'HEAT')
call, msg = await assert_request_calls_service(
'Alexa.ThermostatController', 'SetThermostatMode',
'climate#test_thermostat', 'climate.set_operation_mode',
'climate#test_thermostat', 'climate.set_hvac_mode',
hass,
payload={'thermostatMode': {'value': 'COOL'}}
)
assert call.data['operation_mode'] == 'cool'
assert call.data['hvac_mode'] == 'cool'
properties = ReportedProperties(msg['context']['properties'])
properties.assert_equal(
'Alexa.ThermostatController', 'thermostatMode', 'COOL')
@ -971,18 +972,18 @@ async def test_thermostat(hass):
# ...it can also be just the mode.
call, msg = await assert_request_calls_service(
'Alexa.ThermostatController', 'SetThermostatMode',
'climate#test_thermostat', 'climate.set_operation_mode',
'climate#test_thermostat', 'climate.set_hvac_mode',
hass,
payload={'thermostatMode': 'HEAT'}
)
assert call.data['operation_mode'] == 'heat'
assert call.data['hvac_mode'] == 'heat'
properties = ReportedProperties(msg['context']['properties'])
properties.assert_equal(
'Alexa.ThermostatController', 'thermostatMode', 'HEAT')
msg = await assert_request_fails(
'Alexa.ThermostatController', 'SetThermostatMode',
'climate#test_thermostat', 'climate.set_operation_mode',
'climate#test_thermostat', 'climate.set_hvac_mode',
hass,
payload={'thermostatMode': {'value': 'INVALID'}}
)
@ -991,11 +992,20 @@ async def test_thermostat(hass):
call, _ = await assert_request_calls_service(
'Alexa.ThermostatController', 'SetThermostatMode',
'climate#test_thermostat', 'climate.set_operation_mode',
'climate#test_thermostat', 'climate.set_hvac_mode',
hass,
payload={'thermostatMode': 'OFF'}
)
assert call.data['operation_mode'] == 'off'
assert call.data['hvac_mode'] == 'off'
# Assert we can call presets
call, msg = await assert_request_calls_service(
'Alexa.ThermostatController', 'SetThermostatMode',
'climate#test_thermostat', 'climate.set_preset_mode',
hass,
payload={'thermostatMode': 'ECO'}
)
assert call.data['preset_mode'] == 'eco'
async def test_exclude_filters(hass):

View File

@ -5,66 +5,39 @@ components. Instead call the service directly.
"""
from homeassistant.components.climate import _LOGGER
from homeassistant.components.climate.const import (
ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_FAN_MODE, ATTR_HOLD_MODE,
ATTR_HUMIDITY, ATTR_OPERATION_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AWAY_MODE, SERVICE_SET_HOLD_MODE,
SERVICE_SET_AUX_HEAT, SERVICE_SET_TEMPERATURE, SERVICE_SET_HUMIDITY,
SERVICE_SET_FAN_MODE, SERVICE_SET_OPERATION_MODE, SERVICE_SET_SWING_MODE)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE)
ATTR_AUX_HEAT, ATTR_FAN_MODE, ATTR_HUMIDITY, ATTR_HVAC_MODE,
ATTR_PRESET_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE,
SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE,
SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE
from homeassistant.loader import bind_hass
async def async_set_away_mode(hass, away_mode, entity_id=None):
"""Turn all or specified climate devices away mode on."""
async def async_set_preset_mode(hass, preset_mode, entity_id=None):
"""Set new preset mode."""
data = {
ATTR_AWAY_MODE: away_mode
ATTR_PRESET_MODE: preset_mode
}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
await hass.services.async_call(
DOMAIN, SERVICE_SET_AWAY_MODE, data, blocking=True)
DOMAIN, SERVICE_SET_PRESET_MODE, data, blocking=True)
@bind_hass
def set_away_mode(hass, away_mode, entity_id=None):
"""Turn all or specified climate devices away mode on."""
def set_preset_mode(hass, preset_mode, entity_id=None):
"""Set new preset mode."""
data = {
ATTR_AWAY_MODE: away_mode
ATTR_PRESET_MODE: preset_mode
}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
async def async_set_hold_mode(hass, hold_mode, entity_id=None):
"""Set new hold mode."""
data = {
ATTR_HOLD_MODE: hold_mode
}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
await hass.services.async_call(
DOMAIN, SERVICE_SET_HOLD_MODE, data, blocking=True)
@bind_hass
def set_hold_mode(hass, hold_mode, entity_id=None):
"""Set new hold mode."""
data = {
ATTR_HOLD_MODE: hold_mode
}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SET_HOLD_MODE, data)
hass.services.call(DOMAIN, SERVICE_SET_PRESET_MODE, data)
async def async_set_aux_heat(hass, aux_heat, entity_id=None):
@ -95,7 +68,7 @@ def set_aux_heat(hass, aux_heat, entity_id=None):
async def async_set_temperature(hass, temperature=None, entity_id=None,
target_temp_high=None, target_temp_low=None,
operation_mode=None):
hvac_mode=None):
"""Set new target temperature."""
kwargs = {
key: value for key, value in [
@ -103,7 +76,7 @@ async def async_set_temperature(hass, temperature=None, entity_id=None,
(ATTR_TARGET_TEMP_HIGH, target_temp_high),
(ATTR_TARGET_TEMP_LOW, target_temp_low),
(ATTR_ENTITY_ID, entity_id),
(ATTR_OPERATION_MODE, operation_mode)
(ATTR_HVAC_MODE, hvac_mode)
] if value is not None
}
_LOGGER.debug("set_temperature start data=%s", kwargs)
@ -114,7 +87,7 @@ async def async_set_temperature(hass, temperature=None, entity_id=None,
@bind_hass
def set_temperature(hass, temperature=None, entity_id=None,
target_temp_high=None, target_temp_low=None,
operation_mode=None):
hvac_mode=None):
"""Set new target temperature."""
kwargs = {
key: value for key, value in [
@ -122,7 +95,7 @@ def set_temperature(hass, temperature=None, entity_id=None,
(ATTR_TARGET_TEMP_HIGH, target_temp_high),
(ATTR_TARGET_TEMP_LOW, target_temp_low),
(ATTR_ENTITY_ID, entity_id),
(ATTR_OPERATION_MODE, operation_mode)
(ATTR_HVAC_MODE, hvac_mode)
] if value is not None
}
_LOGGER.debug("set_temperature start data=%s", kwargs)
@ -173,26 +146,26 @@ def set_fan_mode(hass, fan, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_FAN_MODE, data)
async def async_set_operation_mode(hass, operation_mode, entity_id=None):
async def async_set_hvac_mode(hass, hvac_mode, entity_id=None):
"""Set new target operation mode."""
data = {ATTR_OPERATION_MODE: operation_mode}
data = {ATTR_HVAC_MODE: hvac_mode}
if entity_id is not None:
data[ATTR_ENTITY_ID] = entity_id
await hass.services.async_call(
DOMAIN, SERVICE_SET_OPERATION_MODE, data, blocking=True)
DOMAIN, SERVICE_SET_HVAC_MODE, data, blocking=True)
@bind_hass
def set_operation_mode(hass, operation_mode, entity_id=None):
def set_operation_mode(hass, hvac_mode, entity_id=None):
"""Set new target operation mode."""
data = {ATTR_OPERATION_MODE: operation_mode}
data = {ATTR_HVAC_MODE: hvac_mode}
if entity_id is not None:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SET_OPERATION_MODE, data)
hass.services.call(DOMAIN, SERVICE_SET_HVAC_MODE, data)
async def async_set_swing_mode(hass, swing_mode, entity_id=None):

View File

@ -1,5 +1,4 @@
"""The tests for the climate component."""
import asyncio
import pytest
import voluptuous as vol
@ -8,24 +7,22 @@ from homeassistant.components.climate import SET_TEMPERATURE_SCHEMA
from tests.common import async_mock_service
@asyncio.coroutine
def test_set_temp_schema_no_req(hass, caplog):
async def test_set_temp_schema_no_req(hass, caplog):
"""Test the set temperature schema with missing required data."""
domain = 'climate'
service = 'test_set_temperature'
schema = SET_TEMPERATURE_SCHEMA
calls = async_mock_service(hass, domain, service, schema)
data = {'operation_mode': 'test', 'entity_id': ['climate.test_id']}
data = {'hvac_mode': 'off', 'entity_id': ['climate.test_id']}
with pytest.raises(vol.Invalid):
yield from hass.services.async_call(domain, service, data)
yield from hass.async_block_till_done()
await hass.services.async_call(domain, service, data)
await hass.async_block_till_done()
assert len(calls) == 0
@asyncio.coroutine
def test_set_temp_schema(hass, caplog):
async def test_set_temp_schema(hass, caplog):
"""Test the set temperature schema with ok required data."""
domain = 'climate'
service = 'test_set_temperature'
@ -33,10 +30,10 @@ def test_set_temp_schema(hass, caplog):
calls = async_mock_service(hass, domain, service, schema)
data = {
'temperature': 20.0, 'operation_mode': 'test',
'temperature': 20.0, 'hvac_mode': 'heat',
'entity_id': ['climate.test_id']}
yield from hass.services.async_call(domain, service, data)
yield from hass.async_block_till_done()
await hass.services.async_call(domain, service, data)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[-1].data == data

View File

@ -4,13 +4,12 @@ import pytest
from homeassistant.components.climate import async_reproduce_states
from homeassistant.components.climate.const import (
ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_HOLD_MODE, ATTR_HUMIDITY,
ATTR_OPERATION_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AUX_HEAT, SERVICE_SET_AWAY_MODE,
SERVICE_SET_HOLD_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_OPERATION_MODE,
SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, STATE_HEAT)
from homeassistant.const import (
ATTR_TEMPERATURE, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, STATE_ON)
ATTR_AUX_HEAT, ATTR_HUMIDITY, ATTR_PRESET_MODE, ATTR_SWING_MODE,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, HVAC_MODE_AUTO,
HVAC_MODE_HEAT, HVAC_MODE_OFF, SERVICE_SET_AUX_HEAT, SERVICE_SET_HUMIDITY,
SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.core import Context, State
from tests.common import async_mock_service
@ -20,13 +19,11 @@ ENTITY_2 = 'climate.test2'
@pytest.mark.parametrize(
'service,state', [
(SERVICE_TURN_ON, STATE_ON),
(SERVICE_TURN_OFF, STATE_OFF),
])
async def test_state(hass, service, state):
"""Test that we can turn a state into a service call."""
calls_1 = async_mock_service(hass, DOMAIN, service)
'state', [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF]
)
async def test_with_hvac_mode(hass, state):
"""Test that state different hvac states."""
calls = async_mock_service(hass, DOMAIN, SERVICE_SET_HVAC_MODE)
await async_reproduce_states(hass, [
State(ENTITY_1, state)
@ -34,110 +31,66 @@ async def test_state(hass, service, state):
await hass.async_block_till_done()
assert len(calls_1) == 1
assert calls_1[0].data == {'entity_id': ENTITY_1}
assert len(calls) == 1
assert calls[0].data == {'entity_id': ENTITY_1, 'hvac_mode': state}
async def test_turn_on_with_mode(hass):
"""Test that state with additional attributes call multiple services."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_SET_OPERATION_MODE)
async def test_multiple_state(hass):
"""Test that multiple states gets calls."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_SET_HVAC_MODE)
await async_reproduce_states(hass, [
State(ENTITY_1, 'on',
{ATTR_OPERATION_MODE: STATE_HEAT})
])
await hass.async_block_till_done()
assert len(calls_1) == 1
assert calls_1[0].data == {'entity_id': ENTITY_1}
assert len(calls_2) == 1
assert calls_2[0].data == {'entity_id': ENTITY_1,
ATTR_OPERATION_MODE: STATE_HEAT}
async def test_multiple_same_state(hass):
"""Test that multiple states with same state gets calls."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
await async_reproduce_states(hass, [
State(ENTITY_1, 'on'),
State(ENTITY_2, 'on'),
State(ENTITY_1, HVAC_MODE_HEAT),
State(ENTITY_2, HVAC_MODE_AUTO),
])
await hass.async_block_till_done()
assert len(calls_1) == 2
# order is not guaranteed
assert any(call.data == {'entity_id': ENTITY_1} for call in calls_1)
assert any(call.data == {'entity_id': ENTITY_2} for call in calls_1)
assert any(
call.data == {'entity_id': ENTITY_1, 'hvac_mode': HVAC_MODE_HEAT}
for call in calls_1)
assert any(
call.data == {'entity_id': ENTITY_2, 'hvac_mode': HVAC_MODE_AUTO}
for call in calls_1)
async def test_multiple_different_state(hass):
"""Test that multiple states with different state gets calls."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF)
async def test_state_with_none(hass):
"""Test that none is not a hvac state."""
calls = async_mock_service(hass, DOMAIN, SERVICE_SET_HVAC_MODE)
await async_reproduce_states(hass, [
State(ENTITY_1, 'on'),
State(ENTITY_2, 'off'),
State(ENTITY_1, None)
])
await hass.async_block_till_done()
assert len(calls_1) == 1
assert calls_1[0].data == {'entity_id': ENTITY_1}
assert len(calls_2) == 1
assert calls_2[0].data == {'entity_id': ENTITY_2}
assert len(calls) == 0
async def test_state_with_context(hass):
"""Test that context is forwarded."""
calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls = async_mock_service(hass, DOMAIN, SERVICE_SET_HVAC_MODE)
context = Context()
await async_reproduce_states(hass, [
State(ENTITY_1, 'on')
State(ENTITY_1, HVAC_MODE_HEAT)
], context)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data == {'entity_id': ENTITY_1}
assert calls[0].data == {'entity_id': ENTITY_1,
'hvac_mode': HVAC_MODE_HEAT}
assert calls[0].context == context
async def test_attribute_no_state(hass):
"""Test that no state service call is made with none state."""
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF)
calls_3 = async_mock_service(hass, DOMAIN, SERVICE_SET_OPERATION_MODE)
value = "dummy"
await async_reproduce_states(hass, [
State(ENTITY_1, None,
{ATTR_OPERATION_MODE: value})
])
await hass.async_block_till_done()
assert len(calls_1) == 0
assert len(calls_2) == 0
assert len(calls_3) == 1
assert calls_3[0].data == {'entity_id': ENTITY_1,
ATTR_OPERATION_MODE: value}
@pytest.mark.parametrize(
'service,attribute', [
(SERVICE_SET_OPERATION_MODE, ATTR_OPERATION_MODE),
(SERVICE_SET_AUX_HEAT, ATTR_AUX_HEAT),
(SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE),
(SERVICE_SET_HOLD_MODE, ATTR_HOLD_MODE),
(SERVICE_SET_PRESET_MODE, ATTR_PRESET_MODE),
(SERVICE_SET_SWING_MODE, ATTR_SWING_MODE),
(SERVICE_SET_HUMIDITY, ATTR_HUMIDITY),
(SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE),

View File

@ -107,7 +107,8 @@ async def test_climate_devices(hass):
{'state': {'on': False}})
await hass.services.async_call(
'climate', 'turn_on', {'entity_id': 'climate.climate_1_name'},
'climate', 'set_hvac_mode',
{'entity_id': 'climate.climate_1_name', 'hvac_mode': 'heat'},
blocking=True
)
gateway.api.session.put.assert_called_with(
@ -116,7 +117,8 @@ async def test_climate_devices(hass):
)
await hass.services.async_call(
'climate', 'turn_off', {'entity_id': 'climate.climate_1_name'},
'climate', 'set_hvac_mode',
{'entity_id': 'climate.climate_1_name', 'hvac_mode': 'off'},
blocking=True
)
gateway.api.session.put.assert_called_with(
@ -143,7 +145,7 @@ async def test_verify_state_update(hass):
assert "climate.climate_1_name" in gateway.deconz_ids
thermostat = hass.states.get('climate.climate_1_name')
assert thermostat.state == 'on'
assert thermostat.state == 'off'
state_update = {
"t": "event",

View File

@ -1,284 +1,281 @@
"""The tests for the demo climate component."""
import unittest
import pytest
import voluptuous as vol
from homeassistant.util.unit_system import (
METRIC_SYSTEM
)
from homeassistant.setup import setup_component
from homeassistant.components.climate import (
DOMAIN, SERVICE_TURN_OFF, SERVICE_TURN_ON)
from homeassistant.const import (ATTR_ENTITY_ID)
from homeassistant.components.climate.const import (
ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_HVAC_ACTIONS,
ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, ATTR_HUMIDITY, ATTR_HVAC_MODES,
ATTR_MAX_HUMIDITY, ATTR_MAX_TEMP, ATTR_MIN_HUMIDITY, ATTR_MIN_TEMP,
ATTR_PRESET_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, DOMAIN,
HVAC_MODE_COOL, HVAC_MODE_HEAT, PRESET_AWAY, PRESET_ECO)
from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, STATE_ON
from homeassistant.setup import async_setup_component
from homeassistant.util.unit_system import METRIC_SYSTEM
from tests.common import get_test_home_assistant
from tests.components.climate import common
ENTITY_CLIMATE = 'climate.hvac'
ENTITY_ECOBEE = 'climate.ecobee'
ENTITY_HEATPUMP = 'climate.heatpump'
class TestDemoClimate(unittest.TestCase):
"""Test the demo climate hvac."""
@pytest.fixture(autouse=True)
async def setup_demo_climate(hass):
"""Initialize setup demo climate."""
hass.config.units = METRIC_SYSTEM
assert await async_setup_component(hass, DOMAIN, {
'climate': {
'platform': 'demo',
}
})
def setUp(self): # pylint: disable=invalid-name
"""Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.units = METRIC_SYSTEM
assert setup_component(self.hass, DOMAIN, {
'climate': {
'platform': 'demo',
}})
def tearDown(self): # pylint: disable=invalid-name
"""Stop down everything that was started."""
self.hass.stop()
def test_setup_params(hass):
"""Test the initial parameters."""
state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_COOL
assert 21 == state.attributes.get(ATTR_TEMPERATURE)
assert 22 == state.attributes.get(ATTR_CURRENT_TEMPERATURE)
assert "On High" == state.attributes.get(ATTR_FAN_MODE)
assert 67 == state.attributes.get(ATTR_HUMIDITY)
assert 54 == state.attributes.get(ATTR_CURRENT_HUMIDITY)
assert "Off" == state.attributes.get(ATTR_SWING_MODE)
assert STATE_OFF == state.attributes.get(ATTR_AUX_HEAT)
assert state.attributes.get(ATTR_HVAC_MODES) == \
['off', 'heat', 'cool', 'auto', 'dry', 'fan_only']
def test_setup_params(self):
"""Test the initial parameters."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert 21 == state.attributes.get('temperature')
assert 'on' == state.attributes.get('away_mode')
assert 22 == state.attributes.get('current_temperature')
assert "On High" == state.attributes.get('fan_mode')
assert 67 == state.attributes.get('humidity')
assert 54 == state.attributes.get('current_humidity')
assert "Off" == state.attributes.get('swing_mode')
assert "cool" == state.attributes.get('operation_mode')
assert 'off' == state.attributes.get('aux_heat')
def test_default_setup_params(self):
"""Test the setup with default parameters."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert 7 == state.attributes.get('min_temp')
assert 35 == state.attributes.get('max_temp')
assert 30 == state.attributes.get('min_humidity')
assert 99 == state.attributes.get('max_humidity')
def test_default_setup_params(hass):
"""Test the setup with default parameters."""
state = hass.states.get(ENTITY_CLIMATE)
assert 7 == state.attributes.get(ATTR_MIN_TEMP)
assert 35 == state.attributes.get(ATTR_MAX_TEMP)
assert 30 == state.attributes.get(ATTR_MIN_HUMIDITY)
assert 99 == state.attributes.get(ATTR_MAX_HUMIDITY)
def test_set_only_target_temp_bad_attr(self):
"""Test setting the target temperature without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert 21 == state.attributes.get('temperature')
with pytest.raises(vol.Invalid):
common.set_temperature(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
assert 21 == state.attributes.get('temperature')
def test_set_only_target_temp(self):
"""Test the setting of the target temperature."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert 21 == state.attributes.get('temperature')
common.set_temperature(self.hass, 30, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert 30.0 == state.attributes.get('temperature')
async def test_set_only_target_temp_bad_attr(hass):
"""Test setting the target temperature without required attribute."""
state = hass.states.get(ENTITY_CLIMATE)
assert 21 == state.attributes.get(ATTR_TEMPERATURE)
def test_set_only_target_temp_with_convert(self):
"""Test the setting of the target temperature."""
state = self.hass.states.get(ENTITY_HEATPUMP)
assert 20 == state.attributes.get('temperature')
common.set_temperature(self.hass, 21, ENTITY_HEATPUMP)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_HEATPUMP)
assert 21.0 == state.attributes.get('temperature')
with pytest.raises(vol.Invalid):
await common.async_set_temperature(hass, None, ENTITY_CLIMATE)
def test_set_target_temp_range(self):
"""Test the setting of the target temperature with range."""
state = self.hass.states.get(ENTITY_ECOBEE)
assert state.attributes.get('temperature') is None
assert 21.0 == state.attributes.get('target_temp_low')
assert 24.0 == state.attributes.get('target_temp_high')
common.set_temperature(self.hass, target_temp_high=25,
target_temp_low=20, entity_id=ENTITY_ECOBEE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE)
assert state.attributes.get('temperature') is None
assert 20.0 == state.attributes.get('target_temp_low')
assert 25.0 == state.attributes.get('target_temp_high')
await hass.async_block_till_done()
assert 21 == state.attributes.get(ATTR_TEMPERATURE)
def test_set_target_temp_range_bad_attr(self):
"""Test setting the target temperature range without attribute."""
state = self.hass.states.get(ENTITY_ECOBEE)
assert state.attributes.get('temperature') is None
assert 21.0 == state.attributes.get('target_temp_low')
assert 24.0 == state.attributes.get('target_temp_high')
with pytest.raises(vol.Invalid):
common.set_temperature(self.hass, temperature=None,
entity_id=ENTITY_ECOBEE,
target_temp_low=None,
target_temp_high=None)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE)
assert state.attributes.get('temperature') is None
assert 21.0 == state.attributes.get('target_temp_low')
assert 24.0 == state.attributes.get('target_temp_high')
def test_set_target_humidity_bad_attr(self):
"""Test setting the target humidity without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert 67 == state.attributes.get('humidity')
with pytest.raises(vol.Invalid):
common.set_humidity(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert 67 == state.attributes.get('humidity')
async def test_set_only_target_temp(hass):
"""Test the setting of the target temperature."""
state = hass.states.get(ENTITY_CLIMATE)
assert 21 == state.attributes.get(ATTR_TEMPERATURE)
def test_set_target_humidity(self):
"""Test the setting of the target humidity."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert 67 == state.attributes.get('humidity')
common.set_humidity(self.hass, 64, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert 64.0 == state.attributes.get('humidity')
await common.async_set_temperature(hass, 30, ENTITY_CLIMATE)
await hass.async_block_till_done()
def test_set_fan_mode_bad_attr(self):
"""Test setting fan mode without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert "On High" == state.attributes.get('fan_mode')
with pytest.raises(vol.Invalid):
common.set_fan_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert "On High" == state.attributes.get('fan_mode')
state = hass.states.get(ENTITY_CLIMATE)
assert 30.0 == state.attributes.get(ATTR_TEMPERATURE)
def test_set_fan_mode(self):
"""Test setting of new fan mode."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert "On High" == state.attributes.get('fan_mode')
common.set_fan_mode(self.hass, "On Low", ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert "On Low" == state.attributes.get('fan_mode')
def test_set_swing_mode_bad_attr(self):
"""Test setting swing mode without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert "Off" == state.attributes.get('swing_mode')
with pytest.raises(vol.Invalid):
common.set_swing_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert "Off" == state.attributes.get('swing_mode')
async def test_set_only_target_temp_with_convert(hass):
"""Test the setting of the target temperature."""
state = hass.states.get(ENTITY_HEATPUMP)
assert 20 == state.attributes.get(ATTR_TEMPERATURE)
def test_set_swing(self):
"""Test setting of new swing mode."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert "Off" == state.attributes.get('swing_mode')
common.set_swing_mode(self.hass, "Auto", ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert "Auto" == state.attributes.get('swing_mode')
await common.async_set_temperature(hass, 21, ENTITY_HEATPUMP)
await hass.async_block_till_done()
def test_set_operation_bad_attr_and_state(self):
"""Test setting operation mode without required attribute.
state = hass.states.get(ENTITY_HEATPUMP)
assert 21.0 == state.attributes.get(ATTR_TEMPERATURE)
Also check the state.
"""
state = self.hass.states.get(ENTITY_CLIMATE)
assert "cool" == state.attributes.get('operation_mode')
assert "cool" == state.state
with pytest.raises(vol.Invalid):
common.set_operation_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert "cool" == state.attributes.get('operation_mode')
assert "cool" == state.state
def test_set_operation(self):
"""Test setting of new operation mode."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert "cool" == state.attributes.get('operation_mode')
assert "cool" == state.state
common.set_operation_mode(self.hass, "heat", ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert "heat" == state.attributes.get('operation_mode')
assert "heat" == state.state
async def test_set_target_temp_range(hass):
"""Test the setting of the target temperature with range."""
state = hass.states.get(ENTITY_ECOBEE)
assert state.attributes.get(ATTR_TEMPERATURE) is None
assert 21.0 == state.attributes.get(ATTR_TARGET_TEMP_LOW)
assert 24.0 == state.attributes.get(ATTR_TARGET_TEMP_HIGH)
def test_set_away_mode_bad_attr(self):
"""Test setting the away mode without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert 'on' == state.attributes.get('away_mode')
with pytest.raises(vol.Invalid):
common.set_away_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
assert 'on' == state.attributes.get('away_mode')
await common.async_set_temperature(
hass, target_temp_high=25, target_temp_low=20, entity_id=ENTITY_ECOBEE)
await hass.async_block_till_done()
def test_set_away_mode_on(self):
"""Test setting the away mode on/true."""
common.set_away_mode(self.hass, True, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert 'on' == state.attributes.get('away_mode')
state = hass.states.get(ENTITY_ECOBEE)
assert state.attributes.get(ATTR_TEMPERATURE) is None
assert 20.0 == state.attributes.get(ATTR_TARGET_TEMP_LOW)
assert 25.0 == state.attributes.get(ATTR_TARGET_TEMP_HIGH)
def test_set_away_mode_off(self):
"""Test setting the away mode off/false."""
common.set_away_mode(self.hass, False, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert 'off' == state.attributes.get('away_mode')
def test_set_hold_mode_home(self):
"""Test setting the hold mode home."""
common.set_hold_mode(self.hass, 'home', ENTITY_ECOBEE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE)
assert 'home' == state.attributes.get('hold_mode')
async def test_set_target_temp_range_bad_attr(hass):
"""Test setting the target temperature range without attribute."""
state = hass.states.get(ENTITY_ECOBEE)
assert state.attributes.get(ATTR_TEMPERATURE) is None
assert 21.0 == state.attributes.get(ATTR_TARGET_TEMP_LOW)
assert 24.0 == state.attributes.get(ATTR_TARGET_TEMP_HIGH)
def test_set_hold_mode_away(self):
"""Test setting the hold mode away."""
common.set_hold_mode(self.hass, 'away', ENTITY_ECOBEE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE)
assert 'away' == state.attributes.get('hold_mode')
with pytest.raises(vol.Invalid):
await common.async_set_temperature(
hass, temperature=None, entity_id=ENTITY_ECOBEE,
target_temp_low=None, target_temp_high=None)
await hass.async_block_till_done()
def test_set_hold_mode_none(self):
"""Test setting the hold mode off/false."""
common.set_hold_mode(self.hass, 'off', ENTITY_ECOBEE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE)
assert 'off' == state.attributes.get('hold_mode')
state = hass.states.get(ENTITY_ECOBEE)
assert state.attributes.get(ATTR_TEMPERATURE) is None
assert 21.0 == state.attributes.get(ATTR_TARGET_TEMP_LOW)
assert 24.0 == state.attributes.get(ATTR_TARGET_TEMP_HIGH)
def test_set_aux_heat_bad_attr(self):
"""Test setting the auxiliary heater without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE)
assert 'off' == state.attributes.get('aux_heat')
with pytest.raises(vol.Invalid):
common.set_aux_heat(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
assert 'off' == state.attributes.get('aux_heat')
def test_set_aux_heat_on(self):
"""Test setting the axillary heater on/true."""
common.set_aux_heat(self.hass, True, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert 'on' == state.attributes.get('aux_heat')
async def test_set_target_humidity_bad_attr(hass):
"""Test setting the target humidity without required attribute."""
state = hass.states.get(ENTITY_CLIMATE)
assert 67 == state.attributes.get(ATTR_HUMIDITY)
def test_set_aux_heat_off(self):
"""Test setting the auxiliary heater off/false."""
common.set_aux_heat(self.hass, False, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
assert 'off' == state.attributes.get('aux_heat')
with pytest.raises(vol.Invalid):
await common.async_set_humidity(hass, None, ENTITY_CLIMATE)
await hass.async_block_till_done()
def test_set_on_off(self):
"""Test on/off service."""
state = self.hass.states.get(ENTITY_ECOBEE)
assert 'auto' == state.state
state = hass.states.get(ENTITY_CLIMATE)
assert 67 == state.attributes.get(ATTR_HUMIDITY)
self.hass.services.call(DOMAIN, SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: ENTITY_ECOBEE})
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE)
assert 'off' == state.state
self.hass.services.call(DOMAIN, SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_ECOBEE})
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE)
assert 'auto' == state.state
async def test_set_target_humidity(hass):
"""Test the setting of the target humidity."""
state = hass.states.get(ENTITY_CLIMATE)
assert 67 == state.attributes.get(ATTR_HUMIDITY)
await common.async_set_humidity(hass, 64, ENTITY_CLIMATE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_CLIMATE)
assert 64.0 == state.attributes.get(ATTR_HUMIDITY)
async def test_set_fan_mode_bad_attr(hass):
"""Test setting fan mode without required attribute."""
state = hass.states.get(ENTITY_CLIMATE)
assert "On High" == state.attributes.get(ATTR_FAN_MODE)
with pytest.raises(vol.Invalid):
await common.async_set_fan_mode(hass, None, ENTITY_CLIMATE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_CLIMATE)
assert "On High" == state.attributes.get(ATTR_FAN_MODE)
async def test_set_fan_mode(hass):
"""Test setting of new fan mode."""
state = hass.states.get(ENTITY_CLIMATE)
assert "On High" == state.attributes.get(ATTR_FAN_MODE)
await common.async_set_fan_mode(hass, "On Low", ENTITY_CLIMATE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_CLIMATE)
assert "On Low" == state.attributes.get(ATTR_FAN_MODE)
async def test_set_swing_mode_bad_attr(hass):
"""Test setting swing mode without required attribute."""
state = hass.states.get(ENTITY_CLIMATE)
assert "Off" == state.attributes.get(ATTR_SWING_MODE)
with pytest.raises(vol.Invalid):
await common.async_set_swing_mode(hass, None, ENTITY_CLIMATE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_CLIMATE)
assert "Off" == state.attributes.get(ATTR_SWING_MODE)
async def test_set_swing(hass):
"""Test setting of new swing mode."""
state = hass.states.get(ENTITY_CLIMATE)
assert "Off" == state.attributes.get(ATTR_SWING_MODE)
await common.async_set_swing_mode(hass, "Auto", ENTITY_CLIMATE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_CLIMATE)
assert "Auto" == state.attributes.get(ATTR_SWING_MODE)
async def test_set_hvac_bad_attr_and_state(hass):
"""Test setting hvac mode without required attribute.
Also check the state.
"""
state = hass.states.get(ENTITY_CLIMATE)
assert state.attributes.get(ATTR_HVAC_ACTIONS) == CURRENT_HVAC_COOL
assert state.state == HVAC_MODE_COOL
with pytest.raises(vol.Invalid):
await common.async_set_hvac_mode(hass, None, ENTITY_CLIMATE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_CLIMATE)
assert state.attributes.get(ATTR_HVAC_ACTIONS) == CURRENT_HVAC_COOL
assert state.state == HVAC_MODE_COOL
async def test_set_hvac(hass):
"""Test setting of new hvac mode."""
state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_COOL
await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT, ENTITY_CLIMATE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_HEAT
async def test_set_hold_mode_away(hass):
"""Test setting the hold mode away."""
await common.async_set_preset_mode(hass, PRESET_AWAY, ENTITY_ECOBEE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ECOBEE)
assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY
async def test_set_hold_mode_eco(hass):
"""Test setting the hold mode eco."""
await common.async_set_preset_mode(hass, PRESET_ECO, ENTITY_ECOBEE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ECOBEE)
assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ECO
async def test_set_aux_heat_bad_attr(hass):
"""Test setting the auxiliary heater without required attribute."""
state = hass.states.get(ENTITY_CLIMATE)
assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF
with pytest.raises(vol.Invalid):
await common.async_set_aux_heat(hass, None, ENTITY_CLIMATE)
await hass.async_block_till_done()
assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF
async def test_set_aux_heat_on(hass):
"""Test setting the axillary heater on/true."""
await common.async_set_aux_heat(hass, True, ENTITY_CLIMATE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_CLIMATE)
assert state.attributes.get(ATTR_AUX_HEAT) == STATE_ON
async def test_set_aux_heat_off(hass):
"""Test setting the auxiliary heater off/false."""
await common.async_set_aux_heat(hass, False, ENTITY_CLIMATE)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_CLIMATE)
assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF

View File

@ -230,45 +230,45 @@ class DysonTest(unittest.TestCase):
entity = dyson.DysonPureHotCoolLinkDevice(device)
assert not entity.should_poll
entity.set_fan_mode(dyson.STATE_FOCUS)
entity.set_fan_mode(dyson.FAN_FOCUS)
set_config = device.set_configuration
set_config.assert_called_with(focus_mode=FocusMode.FOCUS_ON)
entity.set_fan_mode(dyson.STATE_DIFFUSE)
entity.set_fan_mode(dyson.FAN_DIFFUSE)
set_config = device.set_configuration
set_config.assert_called_with(focus_mode=FocusMode.FOCUS_OFF)
def test_dyson_fan_list(self):
def test_dyson_fan_modes(self):
"""Test get fan list."""
device = _get_device_heat_on()
entity = dyson.DysonPureHotCoolLinkDevice(device)
assert len(entity.fan_list) == 2
assert dyson.STATE_FOCUS in entity.fan_list
assert dyson.STATE_DIFFUSE in entity.fan_list
assert len(entity.fan_modes) == 2
assert dyson.FAN_FOCUS in entity.fan_modes
assert dyson.FAN_DIFFUSE in entity.fan_modes
def test_dyson_fan_mode_focus(self):
"""Test fan focus mode."""
device = _get_device_focus()
entity = dyson.DysonPureHotCoolLinkDevice(device)
assert entity.current_fan_mode == dyson.STATE_FOCUS
assert entity.fan_mode == dyson.FAN_FOCUS
def test_dyson_fan_mode_diffuse(self):
"""Test fan diffuse mode."""
device = _get_device_diffuse()
entity = dyson.DysonPureHotCoolLinkDevice(device)
assert entity.current_fan_mode == dyson.STATE_DIFFUSE
assert entity.fan_mode == dyson.FAN_DIFFUSE
def test_dyson_set_operation_mode(self):
def test_dyson_set_hvac_mode(self):
"""Test set operation mode."""
device = _get_device_heat_on()
entity = dyson.DysonPureHotCoolLinkDevice(device)
assert not entity.should_poll
entity.set_operation_mode(dyson.STATE_HEAT)
entity.set_hvac_mode(dyson.HVAC_MODE_HEAT)
set_config = device.set_configuration
set_config.assert_called_with(heat_mode=HeatMode.HEAT_ON)
entity.set_operation_mode(dyson.STATE_COOL)
entity.set_hvac_mode(dyson.HVAC_MODE_COOL)
set_config = device.set_configuration
set_config.assert_called_with(heat_mode=HeatMode.HEAT_OFF)
@ -276,15 +276,15 @@ class DysonTest(unittest.TestCase):
"""Test get operation list."""
device = _get_device_heat_on()
entity = dyson.DysonPureHotCoolLinkDevice(device)
assert len(entity.operation_list) == 2
assert dyson.STATE_HEAT in entity.operation_list
assert dyson.STATE_COOL in entity.operation_list
assert len(entity.hvac_modes) == 2
assert dyson.HVAC_MODE_HEAT in entity.hvac_modes
assert dyson.HVAC_MODE_COOL in entity.hvac_modes
def test_dyson_heat_off(self):
"""Test turn off heat."""
device = _get_device_heat_off()
entity = dyson.DysonPureHotCoolLinkDevice(device)
entity.set_operation_mode(dyson.STATE_COOL)
entity.set_hvac_mode(dyson.HVAC_MODE_COOL)
set_config = device.set_configuration
set_config.assert_called_with(heat_mode=HeatMode.HEAT_OFF)
@ -292,7 +292,7 @@ class DysonTest(unittest.TestCase):
"""Test turn on heat."""
device = _get_device_heat_on()
entity = dyson.DysonPureHotCoolLinkDevice(device)
entity.set_operation_mode(dyson.STATE_HEAT)
entity.set_hvac_mode(dyson.HVAC_MODE_HEAT)
set_config = device.set_configuration
set_config.assert_called_with(heat_mode=HeatMode.HEAT_ON)
@ -300,19 +300,20 @@ class DysonTest(unittest.TestCase):
"""Test get heat value on."""
device = _get_device_heat_on()
entity = dyson.DysonPureHotCoolLinkDevice(device)
assert entity.current_operation == dyson.STATE_HEAT
assert entity.hvac_mode == dyson.HVAC_MODE_HEAT
def test_dyson_heat_value_off(self):
"""Test get heat value off."""
device = _get_device_cool()
entity = dyson.DysonPureHotCoolLinkDevice(device)
assert entity.current_operation == dyson.STATE_COOL
assert entity.hvac_mode == dyson.HVAC_MODE_COOL
def test_dyson_heat_value_idle(self):
"""Test get heat value idle."""
device = _get_device_heat_off()
entity = dyson.DysonPureHotCoolLinkDevice(device)
assert entity.current_operation == dyson.STATE_IDLE
assert entity.hvac_mode == dyson.HVAC_MODE_HEAT
assert entity.hvac_action == dyson.CURRENT_HVAC_IDLE
def test_on_message(self):
"""Test when message is received."""

Some files were not shown because too many files have changed in this diff Show More