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
119 changed files with 5240 additions and 5525 deletions

View File

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

5
.gitignore vendored
View File

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

92
.vscode/tasks.json vendored Normal file
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 ( from .const import (
API_TEMP_UNITS, API_TEMP_UNITS,
API_THERMOSTAT_MODES, API_THERMOSTAT_MODES,
API_THERMOSTAT_PRESETS,
DATE_FORMAT, DATE_FORMAT,
PERCENTAGE_FAN_MAP, PERCENTAGE_FAN_MAP,
) )
@@ -180,9 +181,13 @@ class AlexaPowerController(AlexaCapibility):
if name != 'powerState': if name != 'powerState':
raise UnsupportedProperty(name) raise UnsupportedProperty(name)
if self.entity.state == STATE_OFF: if self.entity.domain == climate.DOMAIN:
return 'OFF' is_on = self.entity.state != climate.HVAC_MODE_OFF
return 'ON'
else:
is_on = self.entity.state != STATE_OFF
return 'ON' if is_on else 'OFF'
class AlexaLockController(AlexaCapibility): class AlexaLockController(AlexaCapibility):
@@ -546,16 +551,13 @@ class AlexaThermostatController(AlexaCapibility):
def properties_supported(self): def properties_supported(self):
"""Return what properties this entity supports.""" """Return what properties this entity supports."""
properties = [] properties = [{'name': 'thermostatMode'}]
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & climate.SUPPORT_TARGET_TEMPERATURE: if supported & climate.SUPPORT_TARGET_TEMPERATURE:
properties.append({'name': 'targetSetpoint'}) properties.append({'name': 'targetSetpoint'})
if supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW: if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE:
properties.append({'name': 'lowerSetpoint'}) properties.append({'name': 'lowerSetpoint'})
if supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH:
properties.append({'name': 'upperSetpoint'}) properties.append({'name': 'upperSetpoint'})
if supported & climate.SUPPORT_OPERATION_MODE:
properties.append({'name': 'thermostatMode'})
return properties return properties
def properties_proactively_reported(self): def properties_proactively_reported(self):
@@ -569,12 +571,17 @@ class AlexaThermostatController(AlexaCapibility):
def get_property(self, name): def get_property(self, name):
"""Read and return a property.""" """Read and return a property."""
if name == 'thermostatMode': if name == 'thermostatMode':
ha_mode = self.entity.attributes.get(climate.ATTR_OPERATION_MODE) preset = self.entity.attributes.get(climate.ATTR_PRESET_MODE)
mode = API_THERMOSTAT_MODES.get(ha_mode)
if preset in API_THERMOSTAT_PRESETS:
mode = API_THERMOSTAT_PRESETS[preset]
else:
mode = API_THERMOSTAT_MODES.get(self.entity.state)
if mode is None: if mode is None:
_LOGGER.error("%s (%s) has unsupported %s value '%s'", _LOGGER.error(
"%s (%s) has unsupported state value '%s'",
self.entity.entity_id, type(self.entity), self.entity.entity_id, type(self.entity),
climate.ATTR_OPERATION_MODE, ha_mode) self.entity.state)
raise UnsupportedProperty(name) raise UnsupportedProperty(name)
return mode return mode

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,38 +1,39 @@
"""Support for (EMEA/EU-based) Honeywell evohome systems.""" """Support for (EMEA/EU-based) Honeywell TCC climate systems.
# Glossary:
# TCS - temperature control system (a.k.a. Controller, Parent), which can Such systems include evohome (multi-zone), and Round Thermostat (single zone).
# have up to 13 Children: """
# 0-12 Heating zones (a.k.a. Zone), and
# 0-1 DHW controller, (a.k.a. Boiler)
# The TCS & Zones are implemented as Climate devices, Boiler as a WaterHeater
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
from typing import Any, Dict, Tuple
from dateutil.tz import tzlocal
import requests.exceptions import requests.exceptions
import voluptuous as vol import voluptuous as vol
import evohomeclient2 import evohomeclient2
from homeassistant.const import ( from homeassistant.const import (
CONF_SCAN_INTERVAL, CONF_USERNAME, CONF_PASSWORD, CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME,
EVENT_HOMEASSISTANT_START, HTTP_SERVICE_UNAVAILABLE, HTTP_TOO_MANY_REQUESTS, TEMP_CELSIUS)
HTTP_SERVICE_UNAVAILABLE, HTTP_TOO_MANY_REQUESTS,
PRECISION_HALVES, TEMP_CELSIUS)
from homeassistant.core import callback from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send) async_dispatcher_connect, async_dispatcher_send)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import (
async_track_point_in_utc_time, async_track_time_interval)
from homeassistant.util.dt import as_utc, parse_datetime, utcnow
from .const import ( from .const import DOMAIN, EVO_STRFTIME, STORAGE_VERSION, STORAGE_KEY, GWS, TCS
DOMAIN, DATA_EVOHOME, DISPATCHER_EVOHOME, GWS, TCS)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_ACCESS_TOKEN_EXPIRES = 'access_token_expires'
CONF_REFRESH_TOKEN = 'refresh_token'
CONF_LOCATION_IDX = 'location_idx' CONF_LOCATION_IDX = 'location_idx'
SCAN_INTERVAL_DEFAULT = timedelta(seconds=300) SCAN_INTERVAL_DEFAULT = timedelta(seconds=300)
SCAN_INTERVAL_MINIMUM = timedelta(seconds=180) SCAN_INTERVAL_MINIMUM = timedelta(seconds=60)
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({ DOMAIN: vol.Schema({
@@ -44,229 +45,314 @@ CONFIG_SCHEMA = vol.Schema({
}), }),
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
CONF_SECRETS = [
CONF_USERNAME, CONF_PASSWORD,
]
# bit masks for dispatcher packets def _local_dt_to_utc(dt_naive: datetime) -> datetime:
EVO_PARENT = 0x01 dt_aware = as_utc(dt_naive.replace(microsecond=0, tzinfo=tzlocal()))
EVO_CHILD = 0x02 return dt_aware.replace(tzinfo=None)
def setup(hass, hass_config): def _handle_exception(err):
"""Create a (EMEA/EU-based) Honeywell evohome system.
Currently, only the Controller and the Zones are implemented here.
"""
evo_data = hass.data[DATA_EVOHOME] = {}
evo_data['timers'] = {}
# use a copy, since scan_interval is rounded up to nearest 60s
evo_data['params'] = dict(hass_config[DOMAIN])
scan_interval = evo_data['params'][CONF_SCAN_INTERVAL]
scan_interval = timedelta(
minutes=(scan_interval.total_seconds() + 59) // 60)
try:
client = evo_data['client'] = evohomeclient2.EvohomeClient(
evo_data['params'][CONF_USERNAME],
evo_data['params'][CONF_PASSWORD],
debug=False
)
except evohomeclient2.AuthenticationError as err:
_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",
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."
)
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
evo_data['status'] = {}
# 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'
# 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
)
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 '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)
return True
class EvoDevice(Entity):
"""Base for any Honeywell evohome device.
Such devices include the Controller, (up to 12) Heating Zones and
(optionally) a DHW controller.
"""
def __init__(self, evo_data, client, obj_ref):
"""Initialize the evohome entity."""
self._client = client
self._obj = obj_ref
self._name = None
self._icon = None
self._type = None
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()
@callback
def _connect(self, packet):
if packet['to'] & self._type and packet['signal'] == 'refresh':
self.async_schedule_update_ha_state(force_refresh=True)
def _handle_exception(self, err):
try: try:
raise err raise err
except evohomeclient2.AuthenticationError: except evohomeclient2.AuthenticationError:
_LOGGER.error( _LOGGER.error(
"Failed to (re)authenticate with the vendor's server. " "Failed to (re)authenticate with the vendor's server. "
"This may be a temporary error. Message is: %s", "Check that your username and password are correct. "
"Message is: %s",
err err
) )
return False
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
# this appears to be common with Honeywell's servers # this appears to be common with Honeywell's servers
_LOGGER.warning( _LOGGER.warning(
"Unable to connect with the vendor's server. " "Unable to connect with the vendor's server. "
"Check your network and the vendor's status page." "Check your network and the vendor's status page."
"Message is: %s",
err
) )
return False
except requests.exceptions.HTTPError: except requests.exceptions.HTTPError:
if err.response.status_code == HTTP_SERVICE_UNAVAILABLE: if err.response.status_code == HTTP_SERVICE_UNAVAILABLE:
_LOGGER.warning( _LOGGER.warning(
"Vendor says their server is currently unavailable. " "Vendor says their server is currently unavailable. "
"This may be temporary; check the vendor's status page." "Check the vendor's status page."
) )
return False
elif err.response.status_code == HTTP_TOO_MANY_REQUESTS: if err.response.status_code == HTTP_TOO_MANY_REQUESTS:
_LOGGER.warning( _LOGGER.warning(
"The vendor's API rate limit has been exceeded. " "The vendor's API rate limit has been exceeded. "
"So will cease polling, and will resume after %s seconds.", "Consider increasing the %s.", CONF_SCAN_INTERVAL
(self._params[CONF_SCAN_INTERVAL] * 3).total_seconds()
) )
self._timers['statusUpdated'] = datetime.now() + \ return False
self._params[CONF_SCAN_INTERVAL] * 3
else:
raise # we don't expect/handle any other HTTPErrors raise # we don't expect/handle any other HTTPErrors
# These properties, methods are from the Entity class
async def async_added_to_hass(self): async def async_setup(hass, hass_config):
"""Run when entity about to be added.""" """Create a (EMEA/EU-based) Honeywell evohome system."""
async_dispatcher_connect(self.hass, DISPATCHER_EVOHOME, self._connect) broker = EvoBroker(hass, hass_config[DOMAIN])
if not await broker.init_client():
return False
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.")
async_track_time_interval(
hass, broker.update, hass_config[DOMAIN][CONF_SCAN_INTERVAL]
)
return True
class EvoBroker:
"""Container for evohome client and data."""
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_broker, evo_device) -> None:
"""Initialize the evohome entity."""
self._evo_device = evo_device
self._evo_tcs = evo_broker.tcs
self._name = self._icon = self._precision = None
self._state_attributes = []
self._supported_features = None
self._setpoints = None
@callback
def _refresh(self, packet):
if packet['signal'] == 'refresh':
self.async_schedule_update_ha_state(force_refresh=True)
def get_setpoints(self) -> Dict[str, Any]:
"""Return the current/next scheduled switchpoints.
Only Zones & DHW controllers (but not the TCS) have schedules.
"""
switchpoints = {}
schedule = self._evo_device.schedule()
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:
break
# 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 @property
def should_poll(self) -> bool: def should_poll(self) -> bool:
"""Most evohome devices push their state to HA. """Evohome entities should not be polled."""
Only the Controller should be polled.
"""
return False return False
@property @property
def name(self) -> str: def name(self) -> str:
"""Return the name to use in the frontend UI.""" """Return the name of the Evohome entity."""
return self._name return self._name
@property @property
def device_state_attributes(self): def device_state_attributes(self) -> Dict[str, Any]:
"""Return the device state attributes of the evohome device. """Return the Evohome-specific state attributes."""
status = {}
for attr in self._state_attributes:
if attr != 'setpoints':
status[attr] = getattr(self._evo_device, attr)
This is state data that is not available otherwise, due to the if 'setpoints' in self._state_attributes:
restrictions placed upon ClimateDevice properties, etc. by HA. status['setpoints'] = self._setpoints
"""
return {'status': self._status} return {'status': status}
@property @property
def icon(self): def icon(self) -> str:
"""Return the icon to use in the frontend UI.""" """Return the icon to use in the frontend UI."""
return self._icon return self._icon
@property @property
def available(self) -> bool: def supported_features(self) -> int:
"""Return True if the device is currently available.""" """Get the flag of supported features of the device."""
return self._available
@property
def supported_features(self):
"""Get the list of supported features of the device."""
return self._supported_features return self._supported_features
# These properties are common to ClimateDevice, WaterHeaterDevice classes async def async_added_to_hass(self) -> None:
@property """Run when entity about to be added to hass."""
def precision(self): async_dispatcher_connect(self.hass, DOMAIN, self._refresh)
"""Return the temperature precision to use in the frontend UI."""
return PRECISION_HALVES
@property @property
def temperature_unit(self): def precision(self) -> float:
"""Return the temperature precision to use in the frontend UI."""
return self._precision
@property
def temperature_unit(self) -> str:
"""Return the temperature unit to use in the frontend UI.""" """Return the temperature unit to use in the frontend UI."""
return TEMP_CELSIUS return TEMP_CELSIUS
@property def update(self) -> None:
def operation_list(self): """Get the latest state data."""
"""Return the list of available operations.""" self._setpoints = self.get_setpoints()
return self._operation_list

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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