laetificat / haanna

Plugwise Anna API to use in conjunction with Home Assistant.
MIT License
5 stars 5 forks source link

Implement get_last_active_schema as a separate function #31

Closed bouwew closed 4 years ago

bouwew commented 4 years ago

Reason for change: at present the plugwise climate entity shows always a selected_schema name, also when no schema is active. By implementing this change, this issue can be fixed. And the result of get_last_active_schema can be used as input to the set_schema_state function.

bouwew commented 4 years ago

This was tested, first in my IDLE-simulation-environment, next in my "production" system, working as intended.

bouwew commented 4 years ago

PLEASE HOLD I will check the haanna-coding in my HA Development environment.

bouwew commented 4 years ago

@CoMPaTech @laetificat Next to fixing all the flake8, pylint and pydoc errors and applying black-formatting, I also wanted to improve the formatting of values shown inside HA, show the trailing zero. But apparently this is not allowed for some float values used by HA itself. Anyway, it's working now for the values like outdoor temp, boiler_temp, etc.

So this pull-request is ready now, please test and publish.

bouwew commented 4 years ago

This is my latest Plugwise-code that uses the last_active_schema changes:

"""Plugwise Climate component for Home Assistant."""

import logging

import voluptuous as vol
import haanna

import homeassistant.helpers.config_validation as cv
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate.const import (
    CURRENT_HVAC_HEAT,
    CURRENT_HVAC_COOL,
    CURRENT_HVAC_IDLE,
    HVAC_MODE_HEAT,
    HVAC_MODE_HEAT_COOL,
    HVAC_MODE_AUTO,
    HVAC_MODE_OFF,
    SUPPORT_PRESET_MODE,
    SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import (
    ATTR_TEMPERATURE,
    CONF_HOST,
    CONF_NAME,
    CONF_PASSWORD,
    CONF_PORT,
    CONF_USERNAME,
    TEMP_CELSIUS,
)
from homeassistant.exceptions import PlatformNotReady

SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE

_LOGGER = logging.getLogger(__name__)

# Configuration directives
CONF_MIN_TEMP = "min_temp"
CONF_MAX_TEMP = "max_temp"
CONF_LEGACY = "legacy_anna"

# Default directives
DEFAULT_NAME = "Plugwise Dev Thermostat"
DEFAULT_USERNAME = "smile"
DEFAULT_TIMEOUT = 10
DEFAULT_PORT = 80
DEFAULT_ICON = "mdi:thermometer"
DEFAULT_MIN_TEMP = 4
DEFAULT_MAX_TEMP = 30

# HVAC modes
HVAC_MODES_1 = [HVAC_MODE_HEAT, HVAC_MODE_AUTO]
HVAC_MODES_2 = [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO]

# Read platform configuration
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
        vol.Required(CONF_PASSWORD): cv.string,
        vol.Required(CONF_HOST): cv.string,
        vol.Optional(CONF_LEGACY, default=False): cv.boolean,
        vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
        vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
        vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): cv.positive_int,
        vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): cv.positive_int,
    }
)

def setup_platform(hass, config, add_entities, discovery_info=None):
    """Add the Plugwise (Anna) Thermostate."""
    api = haanna.Haanna(
        config[CONF_USERNAME],
        config[CONF_PASSWORD],
        config[CONF_HOST],
        config[CONF_PORT],
        config[CONF_LEGACY],
    )
    try:
        api.ping_anna_thermostat()
    except OSError:
        _LOGGER.debug("Ping failed, retrying later", exc_info=True)
        raise PlatformNotReady
    devices = [
        ThermostatDevice(
            api, config[CONF_NAME], config[CONF_MIN_TEMP], config[CONF_MAX_TEMP]
        )
    ]
    add_entities(devices, True)

class ThermostatDevice(ClimateDevice):
    """Representation of an Plugwise thermostat."""

    def __init__(self, api, name, min_temp, max_temp):
        """Set up the Plugwise API."""
        self._api = api
        self._min_temp = min_temp
        self._max_temp = max_temp
        self._name = name
        self._direct_objects = None
        self._domain_objects = None
        self._outdoor_temperature = None
        self._selected_schema = None
        self._last_active_schema = None
        self._preset_mode = None
        self._presets = None
        self._presets_list = None
        self._boiler_status = None
        self._heating_status = None
        self._cooling_status = None
        self._dhw_status = None
        self._schema_names = None
        self._schema_status = None
        self._current_temperature = None
        self._thermostat_temperature = None
        self._boiler_temperature = None
        self._water_pressure = None
        self._schedule_temperature = None
        self._hvac_mode = None

    @property
    def hvac_action(self):
        """Return the current action."""
        if self._heating_status or self._boiler_status or self._dhw_status:
            return CURRENT_HVAC_HEAT
        if self._cooling_status:
            return CURRENT_HVAC_COOL
        return CURRENT_HVAC_IDLE

    @property
    def name(self):
        """Return the name of the thermostat, if any."""
        return self._name

    @property
    def icon(self):
        """Return the icon to use in the frontend."""
        return DEFAULT_ICON

    @property
    def supported_features(self):
        """Return the list of supported features."""
        return SUPPORT_FLAGS

    @property
    def device_state_attributes(self):
        """Return the device specific state attributes."""
        attributes = {}
        if self._outdoor_temperature:
            attributes["outdoor_temperature"] = self._outdoor_temperature
        if self._schema_names:
            attributes["available_schemas"] = self._schema_names
        else:
            attributes["available_schemas"] = "None"
        if self._selected_schema:
            attributes["selected_schema"] = self._selected_schema
        if self._boiler_temperature:
            attributes["boiler_temperature"] = self._boiler_temperature
        if self._water_pressure:
            attributes["water_pressure"] = self._water_pressure
        return attributes

    @property
    def preset_modes(self):
        """Return the available preset modes list.

        And make the presets with their temperatures available.
        """
        return self._presets_list

    @property
    def hvac_modes(self):
        """Return the available hvac modes list."""
        if self._heating_status is not None or self._boiler_status is not None:
            if self._cooling_status is not None:
                return HVAC_MODES_2
            return HVAC_MODES_1
        return None

    @property
    def hvac_mode(self):
        """Return current active hvac state."""
        if self._schema_status:
            return HVAC_MODE_AUTO
        if self._heating_status or self._boiler_status or self._dhw_status:
            if self._cooling_status:
                return HVAC_MODE_HEAT_COOL
            return HVAC_MODE_HEAT
        return HVAC_MODE_OFF

    @property
    def target_temperature(self):
        """Return the target_temperature.

        From the XML the thermostat-value is used because it updates 'immediately'
        compared to the target_temperature-value. This way the information on the card
        is "immediately" updated after changing the preset, temperature, etc.
        """
        return self._thermostat_temperature

    @property
    def preset_mode(self):
        """Return the active selected schedule-name.

        Or return the active preset, or return Temporary in case of a manual change
        in the set-temperature with a weekschedule active,
        or return Manual in case of a manual change and no weekschedule active.
        """
        if self._presets:
            presets = self._presets
            preset_temperature = presets.get(self._preset_mode, "none")
            if self.hvac_mode == HVAC_MODE_AUTO:
                if self._thermostat_temperature == self._schedule_temperature:
                    return "{}".format(self._selected_schema)
                if self._thermostat_temperature == preset_temperature:
                    return self._preset_mode
                return "Temporary"
            if self._thermostat_temperature != preset_temperature:
                return "Manual"
            return self._preset_mode
        return None

    @property
    def current_temperature(self):
        """Return the current room temperature."""
        return self._current_temperature

    @property
    def min_temp(self):
        """Return the minimal temperature possible to set."""
        return self._min_temp

    @property
    def max_temp(self):
        """Return the maximum temperature possible to set."""
        return self._max_temp

    @property
    def temperature_unit(self):
        """Return the unit of measured temperature."""
        return TEMP_CELSIUS

    def set_temperature(self, **kwargs):
        """Set new target temperature."""
        _LOGGER.debug("Adjusting temperature")
        temperature = kwargs.get(ATTR_TEMPERATURE)
        if temperature is not None and self._min_temp < temperature < self._max_temp:
            _LOGGER.debug("Changing temporary temperature")
            self._api.set_temperature(self._domain_objects, temperature)
        else:
            _LOGGER.error("Invalid temperature requested")

    def set_hvac_mode(self, hvac_mode):
        """Set the hvac mode."""
        _LOGGER.debug("Adjusting hvac_mode (i.e. schedule/schema)")
        schema_mode = "false"
        if hvac_mode == HVAC_MODE_AUTO:
            schema_mode = "true"
        self._api.set_schema_state(
            self._domain_objects, self._last_active_schema, schema_mode
        )

    def set_preset_mode(self, preset_mode):
        """Set the preset mode."""
        _LOGGER.debug("Changing preset mode")
        self._api.set_preset(self._domain_objects, preset_mode)

    def update(self):
        """Update the data from the thermostat."""
        _LOGGER.debug("Update called")
        self._direct_objects = self._api.get_direct_objects()
        self._domain_objects = self._api.get_domain_objects()
        self._outdoor_temperature = self._api.get_outdoor_temperature(
            self._domain_objects
        )
        self._selected_schema = self._api.get_active_schema_name(
            self._domain_objects
            )
        self._last_active_schema = self._api.get_last_active_schema_name(
            self._domain_objects
            )
        self._preset_mode = self._api.get_current_preset(self._domain_objects)
        self._presets = self._api.get_presets(self._domain_objects)
        self._presets_list = list(self._api.get_presets(self._domain_objects))
        self._boiler_status = self._api.get_boiler_status(self._direct_objects)
        self._heating_status = self._api.get_heating_status(self._direct_objects)
        self._cooling_status = self._api.get_cooling_status(self._direct_objects)
        self._dhw_status = self._api.get_domestic_hot_water_status(
            self._direct_objects
            )
        self._schema_names = self._api.get_schema_names(self._domain_objects)
        self._schema_status = self._api.get_schema_state(self._domain_objects)
        self._current_temperature = self._api.get_current_temperature(
            self._domain_objects
        )
        self._thermostat_temperature = self._api.get_thermostat_temperature(
            self._domain_objects
        )
        self._schedule_temperature = self._api.get_schedule_temperature(
            self._domain_objects
        )
        self._boiler_temperature = self._api.get_boiler_temperature(
            self._domain_objects
        )
        self._water_pressure = self._api.get_water_pressure(self._domain_objects)
CoMPaTech commented 4 years ago

PyPi pushed

bouwew commented 4 years ago

Great, thanks! I'll update the README.md as well, later.

CoMPaTech commented 4 years ago

GJ @bouwew