From 9f3e388b04e7a740a2ce83c9bbaeeaf1edca73cc Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 1 Aug 2019 22:22:57 +0200 Subject: [PATCH] Add Plugwise component (#25533) * Added Plugwise component * pylint fixes * Additional pylints (local tox not noticing these) * Changes according to review * CI flake8 correction * Applying Black * Review changes, without exception and schema * Review changes, exception improvement * Review changes, exception correctio and schema cleanup * Further cleaning as per review * Removed blank line * Add debugging to API init * Add debugging to API init without dot --- .coveragerc | 1 + CODEOWNERS | 1 + homeassistant/components/plugwise/__init__.py | 1 + homeassistant/components/plugwise/climate.py | 213 ++++++++++++++++++ .../components/plugwise/manifest.json | 8 + requirements_all.txt | 3 + 6 files changed, 227 insertions(+) create mode 100644 homeassistant/components/plugwise/__init__.py create mode 100644 homeassistant/components/plugwise/climate.py create mode 100644 homeassistant/components/plugwise/manifest.json diff --git a/.coveragerc b/.coveragerc index 7f3aa2762bd..abd29a8614b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -467,6 +467,7 @@ omit = homeassistant/components/plaato/* homeassistant/components/plex/media_player.py homeassistant/components/plex/sensor.py + homeassistant/components/plugwise/* homeassistant/components/plum_lightpad/* homeassistant/components/pocketcasts/sensor.py homeassistant/components/point/* diff --git a/CODEOWNERS b/CODEOWNERS index c5ff9a21b4b..232bbc4b07b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -203,6 +203,7 @@ homeassistant/components/philips_js/* @elupus homeassistant/components/pi_hole/* @fabaff homeassistant/components/plaato/* @JohNan homeassistant/components/plant/* @ChristianKuehnel +homeassistant/components/plugwise/* @laetificat @CoMPaTech homeassistant/components/point/* @fredrike homeassistant/components/ps4/* @ktnrg45 homeassistant/components/ptvsd/* @swamp-ig diff --git a/homeassistant/components/plugwise/__init__.py b/homeassistant/components/plugwise/__init__.py new file mode 100644 index 00000000000..afde7eae5d6 --- /dev/null +++ b/homeassistant/components/plugwise/__init__.py @@ -0,0 +1 @@ +"""Plugwise Climate (current only Anna) component for HomeAssistant.""" diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py new file mode 100644 index 00000000000..b3b4cf3c1d4 --- /dev/null +++ b/homeassistant/components/plugwise/climate.py @@ -0,0 +1,213 @@ +"""Plugwise Climate component for HomeAssistant.""" + +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_IDLE, + 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" + +# Default directives +DEFAULT_NAME = "Plugwise Thermostat" +DEFAULT_USERNAME = "smile" +DEFAULT_TIMEOUT = 10 +DEFAULT_PORT = 80 +DEFAULT_ICON = "mdi:thermometer" +DEFAULT_MIN_TEMP = 4 +DEFAULT_MAX_TEMP = 30 + +# HVAC modes +ATTR_HVAC_MODES = [HVAC_MODE_AUTO, HVAC_MODE_OFF] + +# 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_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], + ) + 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._domain_objects = None + self._outdoor_temperature = None + self._active_schema = None + self._preset_mode = None + self._hvac_modes = ATTR_HVAC_MODES + + @property + def hvac_action(self): + """Return the current action.""" + if self._api.get_heating_status(self._domain_objects): + return CURRENT_HVAC_HEAT + 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 = {} + attributes["outdoor_temperature"] = self._outdoor_temperature + attributes["available_schemas"] = self._api.get_schema_names( + self._domain_objects + ) + attributes["active_schema"] = self._active_schema + return attributes + + def update(self): + """Update the data from the thermostat.""" + _LOGGER.debug("Update called") + self._domain_objects = self._api.get_domain_objects() + self._outdoor_temperature = self._api.get_outdoor_temperature( + self._domain_objects + ) + self._active_schema = self._api.get_active_schema_name(self._domain_objects) + + @property + def hvac_mode(self): + """Return current active hvac state.""" + if self._api.get_schema_state(self._domain_objects): + return HVAC_MODE_AUTO + return HVAC_MODE_OFF + + @property + def preset_mode(self): + """Return the active preset mode.""" + return self._api.get_current_preset(self._domain_objects) + + @property + def preset_modes(self): + """Return the available preset modes list without values.""" + presets = list(self._api.get_presets(self._domain_objects)) + return presets + + @property + def hvac_modes(self): + """Return the available hvac modes list.""" + return self._hvac_modes + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._api.get_temperature(self._domain_objects) + + @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 target_temperature(self): + """Return the target temperature.""" + return self._api.get_target_temperature(self._domain_objects) + + @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._active_schema, schema_mode + ) + + def set_preset_mode(self, preset_mode): + """Set the preset mode.""" + _LOGGER.debug("Changing preset mode") + self._preset_mode = preset_mode + self._api.set_preset(self._domain_objects, preset_mode) diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json new file mode 100644 index 00000000000..3a5f4ae0be4 --- /dev/null +++ b/homeassistant/components/plugwise/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "plugwise", + "name": "Plugwise", + "documentation": "https://www.home-assistant.io/components/plugwise", + "dependencies": [], + "codeowners": ["@laetificat","@CoMPaTech"], + "requirements": ["haanna==0.10.0"] +} diff --git a/requirements_all.txt b/requirements_all.txt index 82630107fcf..4b40bfef566 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -586,6 +586,9 @@ ha-ffmpeg==2.0 # homeassistant.components.philips_js ha-philipsjs==0.0.8 +# homeassistant.components.plugwise +haanna==0.10.0 + # homeassistant.components.habitica habitipy==0.2.0