diff --git a/.coveragerc b/.coveragerc index 237cbf8f8b7244..e9627277905e73 100644 --- a/.coveragerc +++ b/.coveragerc @@ -91,6 +91,9 @@ omit = homeassistant/components/eight_sleep.py homeassistant/components/*/eight_sleep.py + homeassistant/components/ecoal_boiler.py + homeassistant/components/*/ecoal_boiler.py + homeassistant/components/ecobee.py homeassistant/components/*/ecobee.py diff --git a/homeassistant/components/ecoal_boiler.py b/homeassistant/components/ecoal_boiler.py new file mode 100644 index 00000000000000..bd08024e64a88d --- /dev/null +++ b/homeassistant/components/ecoal_boiler.py @@ -0,0 +1,98 @@ +""" +Component to control ecoal/esterownik.pl coal/wood boiler controller. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/ecoal_boiler/ +""" +import logging + +import voluptuous as vol + +from homeassistant.const import (CONF_HOST, CONF_PASSWORD, CONF_USERNAME, + CONF_MONITORED_CONDITIONS, CONF_SENSORS, + CONF_SWITCHES) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import load_platform + +REQUIREMENTS = ['ecoaliface==0.4.0'] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "ecoal_boiler" +DATA_ECOAL_BOILER = 'data_' + DOMAIN + +DEFAULT_USERNAME = "admin" +DEFAULT_PASSWORD = "admin" + + +# Available pump ids with assigned HA names +# Available as switches +AVAILABLE_PUMPS = { + "central_heating_pump": "Central heating pump", + "central_heating_pump2": "Central heating pump2", + "domestic_hot_water_pump": "Domestic hot water pump", +} + +# Available temp sensor ids with assigned HA names +# Available as sensors +AVAILABLE_SENSORS = { + "outdoor_temp": 'Outdoor temperature', + "indoor_temp": 'Indoor temperature', + "indoor2_temp": 'Indoor temperature 2', + "domestic_hot_water_temp": 'Domestic hot water temperature', + "target_domestic_hot_water_temp": 'Target hot water temperature', + "feedwater_in_temp": 'Feedwater input temperature', + "feedwater_out_temp": 'Feedwater output temperature', + "target_feedwater_temp": 'Target feedwater temperature', + "fuel_feeder_temp": 'Fuel feeder temperature', + "exhaust_temp": 'Exhaust temperature', +} + +SWITCH_SCHEMA = vol.Schema({ + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(AVAILABLE_PUMPS)): + vol.All(cv.ensure_list, [vol.In(AVAILABLE_PUMPS)]) +}) + +SENSOR_SCHEMA = vol.Schema({ + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(AVAILABLE_SENSORS)): + vol.All(cv.ensure_list, [vol.In(AVAILABLE_SENSORS)]) +}) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_USERNAME, + default=DEFAULT_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD, + default=DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_SWITCHES, default={}): SWITCH_SCHEMA, + vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA, + }) +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, hass_config): + """Set up global ECoalController instance same for sensors and switches.""" + from ecoaliface.simple import ECoalController + + conf = hass_config[DOMAIN] + host = conf[CONF_HOST] + username = conf[CONF_USERNAME] + passwd = conf[CONF_PASSWORD] + # Creating ECoalController instance makes HTTP request to controller. + ecoal_contr = ECoalController(host, username, passwd) + if ecoal_contr.version is None: + # Wrong credentials nor network config + _LOGGER.error("Unable to read controller status from %s@%s" + " (wrong host/credentials)", username, host, ) + return False + _LOGGER.debug("Detected controller version: %r @%s", + ecoal_contr.version, host, ) + hass.data[DATA_ECOAL_BOILER] = ecoal_contr + # Setup switches + switches = conf[CONF_SWITCHES][CONF_MONITORED_CONDITIONS] + load_platform(hass, 'switch', DOMAIN, switches, hass_config) + # Setup temp sensors + sensors = conf[CONF_SENSORS][CONF_MONITORED_CONDITIONS] + load_platform(hass, 'sensor', DOMAIN, sensors, hass_config) + return True diff --git a/homeassistant/components/sensor/ecoal_boiler.py b/homeassistant/components/sensor/ecoal_boiler.py new file mode 100644 index 00000000000000..de81d16470c4c7 --- /dev/null +++ b/homeassistant/components/sensor/ecoal_boiler.py @@ -0,0 +1,63 @@ +""" +Allows reading temperatures from ecoal/esterownik.pl controller. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.ecoal_boiler/ +""" +import logging + +from homeassistant.components.ecoal_boiler import ( + DATA_ECOAL_BOILER, AVAILABLE_SENSORS, ) +from homeassistant.const import TEMP_CELSIUS +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['ecoal_boiler'] + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the ecoal sensors.""" + if discovery_info is None: + return + devices = [] + ecoal_contr = hass.data[DATA_ECOAL_BOILER] + for sensor_id in discovery_info: + name = AVAILABLE_SENSORS[sensor_id] + devices.append(EcoalTempSensor(ecoal_contr, name, sensor_id)) + add_entities(devices, True) + + +class EcoalTempSensor(Entity): + """Representation of a temperature sensor using ecoal status data.""" + + def __init__(self, ecoal_contr, name, status_attr): + """Initialize the sensor.""" + self._ecoal_contr = ecoal_contr + self._name = name + self._status_attr = status_attr + self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return TEMP_CELSIUS + + def update(self): + """Fetch new state data for the sensor. + + This is the only method that should fetch new data for Home Assistant. + """ + # Old values read 0.5 back can still be used + status = self._ecoal_contr.get_cached_status() + self._state = getattr(status, self._status_attr) diff --git a/homeassistant/components/switch/ecoal_boiler.py b/homeassistant/components/switch/ecoal_boiler.py new file mode 100644 index 00000000000000..d8d6c98bb8b8be --- /dev/null +++ b/homeassistant/components/switch/ecoal_boiler.py @@ -0,0 +1,85 @@ +""" +Allows to configuration ecoal (esterownik.pl) pumps as switches. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.ecoal_boiler/ +""" +import logging +from typing import Optional + +from homeassistant.components.switch import SwitchDevice +from homeassistant.components.ecoal_boiler import ( + DATA_ECOAL_BOILER, AVAILABLE_PUMPS, ) + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['ecoal_boiler'] + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up switches based on ecoal interface.""" + if discovery_info is None: + return + ecoal_contr = hass.data[DATA_ECOAL_BOILER] + switches = [] + for pump_id in discovery_info: + name = AVAILABLE_PUMPS[pump_id] + switches.append(EcoalSwitch(ecoal_contr, name, pump_id)) + add_entities(switches, True) + + +class EcoalSwitch(SwitchDevice): + """Representation of Ecoal switch.""" + + def __init__(self, ecoal_contr, name, state_attr): + """ + Initialize switch. + + Sets HA switch to state as read from controller. + """ + self._ecoal_contr = ecoal_contr + self._name = name + self._state_attr = state_attr + # Ecoalcotroller holds convention that same postfix is used + # to set attribute + # set_() + # as attribute name in status instance: + # status. + self._contr_set_fun = getattr(self._ecoal_contr, "set_" + state_attr) + # No value set, will be read from controller instead + self._state = None + + @property + def name(self) -> Optional[str]: + """Return the name of the switch.""" + return self._name + + def update(self): + """Fetch new state data for the sensor. + + This is the only method that should fetch new data for Home Assistant. + """ + status = self._ecoal_contr.get_cached_status() + self._state = getattr(status, self._state_attr) + + def invalidate_ecoal_cache(self): + """Invalidate ecoal interface cache. + + Forces that next read from ecaol interface to not use cache. + """ + self._ecoal_contr.status = None + + @property + def is_on(self) -> bool: + """Return true if device is on.""" + return self._state + + def turn_on(self, **kwargs) -> None: + """Turn the device on.""" + self._contr_set_fun(1) + self.invalidate_ecoal_cache() + + def turn_off(self, **kwargs) -> None: + """Turn the device off.""" + self._contr_set_fun(0) + self.invalidate_ecoal_cache() diff --git a/requirements_all.txt b/requirements_all.txt index 22ff8887370f6c..c44c442334ffa3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -339,6 +339,9 @@ dsmr_parser==0.12 # homeassistant.components.sensor.dweet dweepy==0.3.0 +# homeassistant.components.ecoal_boiler +ecoaliface==0.4.0 + # homeassistant.components.edp_redy edp_redy==0.0.3