Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional persistence in generic_thermostat #10636

Closed
27 changes: 24 additions & 3 deletions homeassistant/components/climate/generic_thermostat.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
from homeassistant.core import DOMAIN as HA_DOMAIN
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA,
STATE_AUTO)
STATE_AUTO, ATTR_OPERATION_MODE)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
from homeassistant.helpers import condition
from homeassistant.helpers.event import (
async_track_state_change, async_track_time_interval)
from homeassistant.helpers.restore_state import async_get_last_state
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)
Expand All @@ -39,7 +40,10 @@
CONF_COLD_TOLERANCE = 'cold_tolerance'
CONF_HOT_TOLERANCE = 'hot_tolerance'
CONF_KEEP_ALIVE = 'keep_alive'
CONF_PERSISTENCE = 'persistence'

ATTR_NONE = 'none'
ATTR_BOTH = 'both'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HEATER): cv.entity_id,
Expand All @@ -56,6 +60,8 @@
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
vol.Optional(CONF_KEEP_ALIVE): vol.All(
cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_PERSISTENCE, default=ATTR_NONE): vol.In(
[ATTR_NONE, ATTR_BOTH, CONF_TARGET_TEMP, ATTR_OPERATION_MODE]),
})


Expand All @@ -73,19 +79,20 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
cold_tolerance = config.get(CONF_COLD_TOLERANCE)
hot_tolerance = config.get(CONF_HOT_TOLERANCE)
keep_alive = config.get(CONF_KEEP_ALIVE)
persistence = config.get(CONF_PERSISTENCE)

async_add_devices([GenericThermostat(
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
target_temp, ac_mode, min_cycle_duration, cold_tolerance,
hot_tolerance, keep_alive)])
hot_tolerance, keep_alive, persistence)])


class GenericThermostat(ClimateDevice):
"""Representation of a Generic Thermostat device."""

def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
cold_tolerance, hot_tolerance, keep_alive):
cold_tolerance, hot_tolerance, keep_alive, persistence):
"""Initialize the thermostat."""
self.hass = hass
self._name = name
Expand All @@ -102,6 +109,7 @@ def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
self._min_temp = min_temp
self._max_temp = max_temp
self._target_temp = target_temp
self._persistence = persistence
self._unit = hass.config.units.temperature_unit

async_track_state_change(
Expand Down Expand Up @@ -214,6 +222,19 @@ def _async_sensor_changed(self, entity_id, old_state, new_state):
self._async_control_heating()
yield from self.async_update_ha_state()

@asyncio.coroutine

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redefinition of unused 'async_added_to_hass' from line 129

def async_added_to_hass(self):
"""Handle all entity which are about to be added."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if not state:
return
if self._persistence in [ATTR_BOTH, CONF_TARGET_TEMP]:
self._target_temp = state.attributes[ATTR_TEMPERATURE]
if (self._persistence in [ATTR_BOTH, ATTR_OPERATION_MODE] and
state.attributes[ATTR_OPERATION_MODE] == STATE_OFF):
self.set_operation_mode(STATE_OFF)
return

@callback
def _async_switch_changed(self, entity_id, old_state, new_state):
"""Handle heater switch state changes."""
Expand Down
24 changes: 24 additions & 0 deletions tests/components/climate/test_generic_thermostat.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,3 +892,27 @@ def test_custom_setup_params(hass):
assert state.attributes.get('min_temp') == MIN_TEMP
assert state.attributes.get('max_temp') == MAX_TEMP
assert state.attributes.get('temperature') == TARGET_TEMP


@asyncio.coroutine
def test_restore_state(hass):
"""Ensure states are restored on startup."""
mock_restore_cache(hass, (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined name 'mock_restore_cache'

State('climate.test_thermostat', '0', {ATTR_TEMPERATURE: "20"}),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined name 'State'
undefined name 'ATTR_TEMPERATURE'

State('climate.test_thermostat', '0', {'operation_mode': STATE_OFF}),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined name 'State'

))

hass.state = CoreState.starting

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined name 'CoreState'


yield from async_setup_component(
hass, climate.DOMAIN, {'climate': {
'platform': 'generic_thermostat',
'name': 'test_thermostat',
'heater': ENT_SWITCH,
'target_sensor': ENT_SENSOR,
'persistence': 'both',
}})

state = hass.states.get('climate.test_thermostat')
assert(state.attributes[ATTR_TEMPERATURE] == 20)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined name 'ATTR_TEMPERATURE'

assert(state.attributes['operation_mode'] == STATE_OFF)