E-thermostaat | ICY


#1

Is it possible to a to add support for the E-thermostaat | ICY?

2 sites with how they connected the device:
http://www.domoticz.com/wiki/Essent
http://forum.micasaverde.com/index.php/topic,13722.45.html


#2

Hi,

I did i quick first try. It seems to work so far. Just copy the code into

<config_dir>/custom_components/climate/e_thermostaat.py

afterwards edit your config to include:

climate:
  - platform: e_thermostaat
    username: USERNAME
    password: PASSWORD

Here is the code:

import logging

import voluptuous as vol

from homeassistant.components.climate import (
    ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
    ATTR_TEMPERATURE,
    CONF_USERNAME, CONF_PASSWORD, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv

import requests


_LOGGER = logging.getLogger(__name__)

URL_LOGIN = "https://portal.icy.nl/login"
URL_DATA = "https://portal.icy.nl/data"

DEFAULT_NAME = 'E-Thermostaat'

CONF_NAME = 'name'
CONF_TARGET_TEMP = 'target_temp'
CONF_AWAY_TEMPERATURE = 'away_temperature'
CONF_COMFORT_TEMPERATURE = 'comfort_temperature'

CONF_UUID = 'uuid'

DEFAULT_AWAY_TEMPERATURE = 14
DEFAULT_COMFORT_TEMPERATURE = 19

# Values reverse engineered
HOME = 32
AWAY = 64


PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
    vol.Required(CONF_USERNAME): cv.string,
    vol.Required(CONF_PASSWORD): cv.string,
    vol.Optional(CONF_AWAY_TEMPERATURE, default=DEFAULT_AWAY_TEMPERATURE):
        vol.Coerce(float),
    vol.Optional(CONF_COMFORT_TEMPERATURE, default=DEFAULT_COMFORT_TEMPERATURE):
        vol.Coerce(float),
    vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
})


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Setup the e thermostat."""
    name = config.get(CONF_NAME)
    target_temp = config.get(CONF_TARGET_TEMP)
    away_temp = config.get(CONF_AWAY_TEMPERATURE)
    comfort_temp = config.get(CONF_COMFORT_TEMPERATURE)
    username = config.get(CONF_USERNAME)
    password = config.get(CONF_PASSWORD)

    add_devices([EThermostaat(
        name, username, password, away_temp, comfort_temp,
        target_temp)])


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

    def __init__(self, name, username, password, away_temp, comfort_temp, target_temp):
        """Initialize the thermostat."""
        self._name = name
        self._username = username
        self._password = password
        self._comfort_temp = comfort_temp
        self._away = False
        self._away_temp = away_temp
        self._current_temperature = None
        self._target_temperature = target_temp
        self._old_conf = None
        self.update()

    @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."""
        return self._target_temperature

    def set_temperature(self, **kwargs):
        """Set new target temperature."""
        temperature = kwargs.get(ATTR_TEMPERATURE)
        if temperature is None:
            return

        self._set_temperature(temperature)

    def _set_temperature(self, temperature, home=True):
        token, uid = self.get_token_and_uid()
        header = {'Session-token': token}  # , 'content-type': 'application/json'}

        payload_new = [("uid", uid),
                       ("temperature1", temperature)]

        if self._old_conf is not None:
            if home:
                payload_new.append(("configuration[]", HOME))
            else:
                payload_new.append(("configuration[]", AWAY))
            for i in self._old_conf[1:]:
                payload_new.append(('configuration[]', i))
        r = requests.post(URL_DATA, data=payload_new, headers=header)
        try:
            if not r.json()['status']['code'] == 200:
                _LOGGER.error("Could not set temperature")
        except Exception as e:
            _LOGGER.error(e)
            return

    def get_token_and_uid(self):
        payload = {'username': self._username, 'password': self._password}

        with requests.Session() as s:
            s.get(URL_LOGIN)
            r = s.post(URL_LOGIN, data=payload)
            try:
                res = r.json()
                token = res['token']
                uid = res['serialthermostat1']
            except Exception as e:
                _LOGGER.error("Could not get token and uid: %s" % e)
                return None
        return token, uid

    def _get_data(self):
        token, uid = self.get_token_and_uid()

        header = {'Session-token': token}
        payload = {'username': self._username, 'password': self._password}

        r = requests.get(URL_DATA, data=payload, headers=header)
        try:
            data = r.json()

            self._target_temperature = data['temperature1']
            self._current_temperature = data['temperature2']

            self._old_conf = data['configuration']

            if self._old_conf[0] >= AWAY:
                self._away = True
            else:
                self._away = False
        except Exception as e:
            _LOGGER.error("Could not get data from e-Thermostaat: %s" % e)

    @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 on.
        """
        self._away = True
        self._set_temperature(self._away_temp, home=False)

    def turn_away_mode_off(self):
        """Turn away off."""
        self._away = False
        self._set_temperature(self._comfort_temp, home=True)

    def update(self):
        """Get the latest data."""
        self._get_data()

#3

I started a pull request here:


#4

Great that you have done some work on the implementation. Are you able to validate the if it works or do you need some help there? I’m actually running Domoticz since some items where not supported by Home Assistant. If this implementation will work it will be more likely :wink:


#5

I am running it at the moment. So far it seems to work nicely. But you are welcome to test it also. I am also always changing to comfort mode when I change the temperature (this is something domoticz did not do), so it automatically switches back to away mode after the pre set time.


#6

I followed the instruction as mentioned and it works! Do you know when the pull request will be implemented?

Can you also implement the the quick setting as mentioned by the app/site?

  • Comfort mode (Comfortstand)
  • Away mode (Bespaarstand)
  • Saving mode (Lang-weg-stand)
  • Fixed temperature mode (Vaste temperatuur)


#7

Hi,

I have closed the pull request at the moment, since I do not have time to implement it nicely (and they are pretty strict in quality control for Home Assistant, which is a good thing, but makes it a bit harder to get new stuff merged). My plan is to implement the other operation modes and then reopen the pull request. I am not sure when I will find time for it. And apparently we are the only two users at the moment :wink:
Away/Comfort mode is already working. If it is in comfort mode, and you switch the button underneath the target temperature slider it will set the thermostat to away mode and set the away temperature (default 14 degrees).
You can set the temperatures that it will set via the yaml configuration file:

climate:
  - platform: e_thermostaat
    username: USERNAME
    password: PASSWORD
    away_temperature: 14
    comfort_temperature: 19

#8

Thanks for adding support for the E-thermostaat. I installed it today and it works.

This will make three users ;). Love to see the last extra bits and pushed in the main software.


#9

I had some time this weekend and did some updates:

  1. I implemented the detection and setting of the other modes
  2. I made the whole update process a bit faster, since previously it would always get a new session token, but now the token is only updated if the old token is not accepted anymore (needs some testing for reliability though :wink: ).

One additional optional setting to above is now:
saving_temperature: 10

It behaves now like this:

  • Setting away mode to true sets the thermostaat to bespaar-stand and to the set away_temperature (default 14)
  • Similarly setting the other modes also set the temperature to the corresponding temp (ie. comfort to 19, lang-weg to 10)
  • If you change the temperature while being either bespaar or lang-weg, it will automatically jump to comfort stand (see comment below)
  • If you are in fixed-temp mode, it will stay in that mode
  • Away Mode is detected as “True” if the mode is either bespaar-stand or lang-weg-stand

I was considering to set the temperature always to the comfort_temp if you come from lang-weg or bespaar-stand (similar to the behaviour in the app). Do you think that would make sense?

The updated version can be found here:

https://raw.githubusercontent.com/daenny/home-assistant/icy/homeassistant/components/climate/e_thermostaat.py

The easiest to test is to use it as a “custom_component” as above. Just overwrite the old file.
If you find any bugs and weird things let me know.


#10

Thanks for implementing the missing parts. I updated my HA and will test the updates.

Really great news. :wink:


#11

I have been able to test is for a while now and it works really great.

It does in deed make sense o set the temperature always to the comfort_temp if you come from lang-weg or bespaar-stand (similar to the behaviour in the app).


#12

Thanks for letting me know. I am not sure how the interaction might feel, since the state is not updated immediately by the web-interface.
So it might be like
You are in bespaar-stand -> want to set the temperature to 20 degrees, but comfort_temp is on 19
you select 20 degrees with the slider and it jumps back to 19 degrees.
That’s why I also did not implement it yet.
But I am just thinking, if the set value its above the comfort_temp, i should just take that. From the top of my head it should be this (beware untested):

def _set_temperature(self, temperature, mode_int=None):
    """Set new target temperature, via URL commands."""
    payload_new = [("temperature1", temperature)]
    if self._old_conf is not None:
        # overwrite first int to set mode
        if mode_int is not None:
            payload_new.append(("configuration[]", mode_int))
        elif self._current_operation_mode \
                and self._current_operation_mode == STATE_FIXED_TEMP:
            payload_new.append(("configuration[]", FIXED_TEMP))
        else:
            ### NEW: Set temp to max of comfort_temp or requested temp
            if self._current_operation_mode and self.is_away_mode_on:
                 payload_new[0][1] = max(temperature, self._comfort_temp)
            ### END
            payload_new.append(("configuration[]", COMFORT))

        mode_int = payload_new[1][1]
        # append old config not to overwrite old settings
        for i in self._old_conf[1:]:
            payload_new.append(('configuration[]', i))

    if self._request_with_retry(URL_DATA, payload_new, request_type='post'):
        # update state values since we assume update was successful
        self._current_operation_mode = self.map_int_to_operation_mode(mode_int)
        self._target_temperature = temperature
        # self.schedule_update_ha_state(force_refresh=True)

Just change the _set_temperature method in your file to match this and be aware of the spacing.


#13

Nice. I will give it a go when I get something else fixed.

The slider you mentioned I don’t have that. I don’t really mind because i think the up and down arrows in the interface is more useful. I use a Mac and I don’t see a slider with safari or firefox and I don’t have the ugly scroll slider on the right ;). HA version is 0.40.1.


#14

hi i am new to home assistant and i have a question where do i copy the code .

thanx


#15

See the second post:


#16

thank you
had to create the folder :grin: works great thank you for the great work.


#17

Thanks for this custom component! It works great :slight_smile:

@daenny do you still have plans to add this as a pull request to Home Assistant?


#18

If someone helps with the documentation, I would be willing to add it again. However, at the moment I am pretty busy with other projects and moving to a new house where I cannot use the ICY anymore, so if someone else wants to take the lead with the pull request, just go ahead and use/change my code as needed.


#19

I can help with the documentation (maybe even some help with the pull request of the component, but still fairly new to Home Assistant). But I understand this is not on your priority list and this custom component is working fine as it is :slight_smile:.


#20

Great work on the component! This saves me a lot of time trying to implement it myself :sweat_smile:

After an update of hass.io to 0.59.1 this afternoon the component doesn’t show up anymore.
I can find the following error mentioning climate. In the standard log it only mentions ‘setting up climate.e_thermostaat’ (with debugging enabled).

Tue Dec 05 2017 19:14:38 GMT+0100 (West-Europa (standaardtijd))

Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/lib/python3.6/asyncio/tasks.py", line 179, in _step
    result = coro.send(None)
  File "/usr/lib/python3.6/site-packages/homeassistant/helpers/entity_component.py", line 399, in async_process_entity
    new_entity, self, update_before_add=update_before_add
  File "/usr/lib/python3.6/site-packages/homeassistant/helpers/entity_component.py", line 247, in async_add_entity
    yield from entity.async_update_ha_state()
  File "/usr/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 231, in async_update_ha_state
    attr)
  File "/usr/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 333, in _attr_setter
    value = getattr(self, name)
  File "/usr/lib/python3.6/site-packages/homeassistant/components/climate/__init__.py", line 736, in supported_features
    raise NotImplementedError()
NotImplementedError

Any idea what causes this?
Thanks!