Closed bouwew closed 4 years ago
This was tested, first in my IDLE-simulation-environment, next in my "production" system, working as intended.
PLEASE HOLD I will check the haanna-coding in my HA Development environment.
@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.
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)
PyPi pushed
Great, thanks! I'll update the README.md as well, later.
GJ @bouwew
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.