Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@ omit =
homeassistant/components/vesync/const.py
homeassistant/components/vesync/switch.py
homeassistant/components/viaggiatreno/sensor.py
homeassistant/components/vicare/*
homeassistant/components/vizio/media_player.py
homeassistant/components/vlc/media_player.py
homeassistant/components/vlc_telnet/media_player.py
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ homeassistant/components/velbus/* @cereal2nd
homeassistant/components/velux/* @Julius2342
homeassistant/components/version/* @fabaff
homeassistant/components/vesync/* @markperdue @webdjoe
homeassistant/components/vicare/* @oischinger
homeassistant/components/vizio/* @raman325
homeassistant/components/vlc_telnet/* @rodripf
homeassistant/components/waqi/* @andrey-git
Expand Down
58 changes: 58 additions & 0 deletions homeassistant/components/vicare/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""The ViCare integration."""
import logging

import voluptuous as vol
Comment thread
oischinger marked this conversation as resolved.
from PyViCare.PyViCareDevice import Device

import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_NAME
from homeassistant.helpers import discovery

_LOGGER = logging.getLogger(__name__)

VICARE_PLATFORMS = ["climate", "water_heater"]

DOMAIN = "vicare"
VICARE_API = "api"
VICARE_NAME = "name"

CONF_CIRCUIT = "circuit"

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_CIRCUIT): int,
vol.Optional(CONF_NAME, default="ViCare"): cv.string,
}
)
},
extra=vol.ALLOW_EXTRA,
)


def setup(hass, config):
"""Create the ViCare component."""
conf = config[DOMAIN]
params = {"token_file": "/tmp/vicare_token.save"}
if conf.get(CONF_CIRCUIT) is not None:
params["circuit"] = conf[CONF_CIRCUIT]

try:
vicare_api = Device(conf[CONF_USERNAME], conf[CONF_PASSWORD], **params)
except AttributeError:
_LOGGER.error(
"Failed to create PyViCare API client. Please check your credentials."
)
return False

hass.data[DOMAIN] = {}
hass.data[DOMAIN][VICARE_API] = vicare_api
hass.data[DOMAIN][VICARE_NAME] = conf[CONF_NAME]

for platform in VICARE_PLATFORMS:
discovery.load_platform(hass, platform, DOMAIN, {}, config)

return True
207 changes: 207 additions & 0 deletions homeassistant/components/vicare/climate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
"""Viessmann ViCare climate device."""
import logging

from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE,
PRESET_ECO,
PRESET_COMFORT,
HVAC_MODE_OFF,
HVAC_MODE_HEAT,
HVAC_MODE_AUTO,
)
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_WHOLE

from . import DOMAIN as VICARE_DOMAIN
from . import VICARE_API
from . import VICARE_NAME

_LOGGER = logging.getLogger(__name__)

VICARE_MODE_DHW = "dhw"
VICARE_MODE_DHWANDHEATING = "dhwAndHeating"
VICARE_MODE_FORCEDREDUCED = "forcedReduced"
VICARE_MODE_FORCEDNORMAL = "forcedNormal"
VICARE_MODE_OFF = "standby"

VICARE_PROGRAM_ACTIVE = "active"
VICARE_PROGRAM_COMFORT = "comfort"
VICARE_PROGRAM_ECO = "eco"
VICARE_PROGRAM_EXTERNAL = "external"
VICARE_PROGRAM_HOLIDAY = "holiday"
VICARE_PROGRAM_NORMAL = "normal"
VICARE_PROGRAM_REDUCED = "reduced"
VICARE_PROGRAM_STANDBY = "standby"

VICARE_HOLD_MODE_AWAY = "away"
VICARE_HOLD_MODE_HOME = "home"
VICARE_HOLD_MODE_OFF = "off"

VICARE_TEMP_HEATING_MIN = 3
VICARE_TEMP_HEATING_MAX = 37

SUPPORT_FLAGS_HEATING = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE

VICARE_TO_HA_HVAC_HEATING = {
VICARE_MODE_DHW: HVAC_MODE_OFF,
VICARE_MODE_DHWANDHEATING: HVAC_MODE_AUTO,
VICARE_MODE_FORCEDREDUCED: HVAC_MODE_OFF,
VICARE_MODE_FORCEDNORMAL: HVAC_MODE_HEAT,
VICARE_MODE_OFF: HVAC_MODE_OFF,
}

HA_TO_VICARE_HVAC_HEATING = {
HVAC_MODE_HEAT: VICARE_MODE_FORCEDNORMAL,
HVAC_MODE_OFF: VICARE_MODE_FORCEDREDUCED,
HVAC_MODE_AUTO: VICARE_MODE_DHWANDHEATING,
}

VICARE_TO_HA_PRESET_HEATING = {
VICARE_PROGRAM_COMFORT: PRESET_COMFORT,
VICARE_PROGRAM_ECO: PRESET_ECO,
}

HA_TO_VICARE_PRESET_HEATING = {
PRESET_COMFORT: VICARE_PROGRAM_COMFORT,
PRESET_ECO: VICARE_PROGRAM_ECO,
}

PYVICARE_ERROR = "error"


def setup_platform(hass, config, add_entities, discovery_info=None):
"""Create the ViCare climate devices."""
if discovery_info is None:
return
vicare_api = hass.data[VICARE_DOMAIN][VICARE_API]
add_entities(
[ViCareClimate(f"{hass.data[VICARE_DOMAIN][VICARE_NAME]} Heating", vicare_api)]
)


class ViCareClimate(ClimateDevice):
"""Representation of the ViCare heating climate device."""

def __init__(self, name, api):
"""Initialize the climate device."""
self._name = name
self._state = None
self._api = api
self._target_temperature = None
self._current_mode = None
self._current_temperature = None
self._current_program = None

def update(self):
"""Let HA know there has been an update from the ViCare API."""
_room_temperature = self._api.getRoomTemperature()
if _room_temperature is not None and _room_temperature != "error":
self._current_temperature = _room_temperature
else:
self._current_temperature = self._api.getSupplyTemperature()
self._current_program = self._api.getActiveProgram()

# The getCurrentDesiredTemperature call can yield 'error' (str) when the system is in standby
desired_temperature = self._api.getCurrentDesiredTemperature()
if desired_temperature == PYVICARE_ERROR:
desired_temperature = None

self._target_temperature = desired_temperature

self._current_mode = self._api.getActiveMode()

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

@property
def name(self):
"""Return the name of the climate device."""
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

@property
def hvac_mode(self):
"""Return current hvac mode."""
return VICARE_TO_HA_HVAC_HEATING.get(self._current_mode)

def set_hvac_mode(self, hvac_mode):
"""Set a new hvac mode on the ViCare API."""
vicare_mode = HA_TO_VICARE_HVAC_HEATING.get(hvac_mode)
if vicare_mode is None:
_LOGGER.error(
"Cannot set invalid vicare mode: %s / %s", hvac_mode, vicare_mode
)
return

_LOGGER.debug("Setting hvac mode to %s / %s", hvac_mode, vicare_mode)
self._api.setMode(vicare_mode)

@property
def hvac_modes(self):
"""Return the list of available hvac modes."""
return list(HA_TO_VICARE_HVAC_HEATING)

@property
def min_temp(self):
"""Return the minimum temperature."""
return VICARE_TEMP_HEATING_MIN

@property
def max_temp(self):
"""Return the maximum temperature."""
return VICARE_TEMP_HEATING_MAX

@property
def precision(self):
"""Return the precision of the system."""
return PRECISION_WHOLE

def set_temperature(self, **kwargs):
"""Set new target temperatures."""
temp = kwargs.get(ATTR_TEMPERATURE)
if temp is not None:
self._api.setProgramTemperature(
self._current_program, self._target_temperature
)

@property
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp."""
return VICARE_TO_HA_PRESET_HEATING.get(self._current_program)

@property
def preset_modes(self):
"""Return the available preset mode."""
return list(VICARE_TO_HA_PRESET_HEATING)

def set_preset_mode(self, preset_mode):
"""Set new preset mode and deactivate any existing programs."""
vicare_program = HA_TO_VICARE_PRESET_HEATING.get(preset_mode)
if vicare_program is None:
_LOGGER.error(
"Cannot set invalid vicare program: %s / %s",
preset_mode,
vicare_program,
)
return

_LOGGER.debug("Setting preset to %s / %s", preset_mode, vicare_program)
self._api.deactivateProgram(self._current_program)
self._api.activateProgram(vicare_program)
9 changes: 9 additions & 0 deletions homeassistant/components/vicare/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"domain": "vicare",
"name": "Viessmann ViCare",
"documentation": "https://www.home-assistant.io/components/vicare",
"dependencies": [],
"codeowners": ["@oischinger"],
"requirements": ["PyViCare==0.1.1"]
}

Loading