diff --git a/.coveragerc b/.coveragerc index dede973976ead0dac6d87294bb3df4abe9321331..0876aa0d7b75541c692f81530d14f31532421c06 100644 --- a/.coveragerc +++ b/.coveragerc @@ -296,6 +296,7 @@ omit = homeassistant/components/camera/rpi_camera.py homeassistant/components/camera/synology.py homeassistant/components/camera/yi.py + homeassistant/components/climate/econet.py homeassistant/components/climate/ephember.py homeassistant/components/climate/eq3btsmart.py homeassistant/components/climate/flexit.py diff --git a/homeassistant/components/climate/econet.py b/homeassistant/components/climate/econet.py new file mode 100644 index 0000000000000000000000000000000000000000..7cafcd816cb2ad938f986685cddc9fbef1fcf340 --- /dev/null +++ b/homeassistant/components/climate/econet.py @@ -0,0 +1,235 @@ +""" +Support for Rheem EcoNet water heaters. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/climate.econet/ +""" +import datetime +import logging +from os import path + +import voluptuous as vol + +from homeassistant.components.climate import ( + DOMAIN, + PLATFORM_SCHEMA, + STATE_ECO, STATE_GAS, STATE_ELECTRIC, + STATE_HEAT_PUMP, STATE_HIGH_DEMAND, + STATE_OFF, SUPPORT_TARGET_TEMPERATURE, + SUPPORT_OPERATION_MODE, + ClimateDevice) +from homeassistant.config import load_yaml_config_file +from homeassistant.const import (ATTR_ENTITY_ID, + CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, + ATTR_TEMPERATURE) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['pyeconet==0.0.4'] + +_LOGGER = logging.getLogger(__name__) + +ATTR_VACATION_START = 'next_vacation_start_date' +ATTR_VACATION_END = 'next_vacation_end_date' +ATTR_ON_VACATION = 'on_vacation' +ATTR_TODAYS_ENERGY_USAGE = 'todays_energy_usage' +ATTR_IN_USE = 'in_use' + +ATTR_START_DATE = 'start_date' +ATTR_END_DATE = 'end_date' + +SUPPORT_FLAGS_HEATER = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE) + +SERVICE_ADD_VACATION = 'econet_add_vacation' +SERVICE_DELETE_VACATION = 'econet_delete_vacation' + +ADD_VACATION_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_START_DATE): cv.positive_int, + vol.Required(ATTR_END_DATE): cv.positive_int, +}) + +DELETE_VACATION_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, +}) + +ECONET_DATA = 'econet' + +HA_STATE_TO_ECONET = { + STATE_ECO: 'Energy Saver', + STATE_ELECTRIC: 'Electric', + STATE_HEAT_PUMP: 'Heat Pump', + STATE_GAS: 'gas', + STATE_HIGH_DEMAND: 'High Demand', + STATE_OFF: 'Off', +} + +ECONET_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_ECONET.items()} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the EcoNet water heaters.""" + from pyeconet.api import PyEcoNet + + hass.data[ECONET_DATA] = {} + hass.data[ECONET_DATA]['water_heaters'] = [] + + username = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + + econet = PyEcoNet(username, password) + water_heaters = econet.get_water_heaters() + hass_water_heaters = [ + EcoNetWaterHeater(water_heater) for water_heater in water_heaters] + add_devices(hass_water_heaters) + hass.data[ECONET_DATA]['water_heaters'].extend(hass_water_heaters) + + def service_handle(service): + """Handler for services.""" + entity_ids = service.data.get('entity_id') + all_heaters = hass.data[ECONET_DATA]['water_heaters'] + _heaters = [ + x for x in all_heaters + if not entity_ids or x.entity_id in entity_ids] + + for _water_heater in _heaters: + if service.service == SERVICE_ADD_VACATION: + start = service.data.get(ATTR_START_DATE) + end = service.data.get(ATTR_END_DATE) + _water_heater.add_vacation(start, end) + if service.service == SERVICE_DELETE_VACATION: + for vacation in _water_heater.water_heater.vacations: + vacation.delete() + + _water_heater.schedule_update_ha_state(True) + + descriptions = load_yaml_config_file( + path.join(path.dirname(__file__), 'services.yaml')) + + hass.services.register(DOMAIN, SERVICE_ADD_VACATION, + service_handle, + descriptions.get(SERVICE_ADD_VACATION), + schema=ADD_VACATION_SCHEMA) + + hass.services.register(DOMAIN, SERVICE_DELETE_VACATION, + service_handle, + descriptions.get(SERVICE_DELETE_VACATION), + schema=DELETE_VACATION_SCHEMA) + + +class EcoNetWaterHeater(ClimateDevice): + """Representation of an EcoNet water heater.""" + + def __init__(self, water_heater): + """Initialize the water heater.""" + self.water_heater = water_heater + + @property + def name(self): + """Return the device name.""" + return self.water_heater.name + + @property + def available(self): + """Return if the the device is online or not.""" + return self.water_heater.is_connected + + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return TEMP_FAHRENHEIT + + @property + def device_state_attributes(self): + """Return the optional state attributes.""" + data = {} + vacations = self.water_heater.get_vacations() + if vacations: + data[ATTR_VACATION_START] = vacations[0].start_date + data[ATTR_VACATION_END] = vacations[0].end_date + data[ATTR_ON_VACATION] = self.water_heater.is_on_vacation + todays_usage = self.water_heater.total_usage_for_today + if todays_usage: + data[ATTR_TODAYS_ENERGY_USAGE] = todays_usage + data[ATTR_IN_USE] = self.water_heater.in_use + + return data + + @property + def current_operation(self): + """ + Return current operation as one of the following. + + ["eco", "heat_pump", + "high_demand", "electric_only"] + """ + current_op = ECONET_STATE_TO_HA.get(self.water_heater.mode) + return current_op + + @property + def operation_list(self): + """List of available operation modes.""" + op_list = [] + modes = self.water_heater.supported_modes + for mode in modes: + ha_mode = ECONET_STATE_TO_HA.get(mode) + if ha_mode is not None: + op_list.append(ha_mode) + else: + error = "Invalid operation mode mapping. " + mode + \ + " doesn't map. Please report this." + _LOGGER.error(error) + return op_list + + @property + def supported_features(self): + """Return the list of supported features.""" + return SUPPORT_FLAGS_HEATER + + def set_temperature(self, **kwargs): + """Set new target temperature.""" + target_temp = kwargs.get(ATTR_TEMPERATURE) + if target_temp is not None: + self.water_heater.set_target_set_point(target_temp) + else: + _LOGGER.error("A target temperature must be provided.") + + def set_operation_mode(self, operation_mode): + """Set operation mode.""" + op_mode_to_set = HA_STATE_TO_ECONET.get(operation_mode) + if op_mode_to_set is not None: + self.water_heater.set_mode(op_mode_to_set) + else: + _LOGGER.error("An operation mode must be provided.") + + def add_vacation(self, start, end): + """Add a vacation to this water heater.""" + if not start: + start = datetime.datetime.now() + else: + start = datetime.datetime.fromtimestamp(start) + end = datetime.datetime.fromtimestamp(end) + self.water_heater.set_vacation_mode(start, end) + + def update(self): + """Get the latest date.""" + self.water_heater.update_state() + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + return self.water_heater.set_point + + @property + def min_temp(self): + """Return the minimum temperature.""" + return self.water_heater.min_set_point + + @property + def max_temp(self): + """Return the maximum temperature.""" + return self.water_heater.max_set_point diff --git a/homeassistant/components/climate/services.yaml b/homeassistant/components/climate/services.yaml index 5d7f30d252d5221c2b0f4e278a2e1e43a33a4aa9..5edbf438328e13db90bb854732acb4c677d0b59e 100644 --- a/homeassistant/components/climate/services.yaml +++ b/homeassistant/components/climate/services.yaml @@ -107,3 +107,23 @@ nuheat_resume_program: entity_id: description: Name(s) of entities to change. example: 'climate.kitchen' + +econet_add_vacation: + description: Add a vacation to your water heater. + fields: + entity_id: + description: Name(s) of entities to change. + example: 'climate.water_heater' + start_date: + description: The timestamp of when the vacation should start. (Optional, defaults to now) + example: 1513186320 + end_date: + description: The timestamp of when the vacation should end. + example: 1513445520 + +econet_delete_vacation: + description: Delete your existing vacation from your water heater. + fields: + entity_id: + description: Name(s) of entities to change. + example: 'climate.water_heater' diff --git a/requirements_all.txt b/requirements_all.txt index c1e7bccdd3f54c061e3ed3333bb45a0bab22efb9..8fd24eeeb1c63b7d4fef9d90ade2b2de6e4e7ae6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -673,6 +673,9 @@ pydroid-ipcam==0.8 # homeassistant.components.sensor.ebox pyebox==0.1.0 +# homeassistant.components.climate.econet +pyeconet==0.0.4 + # homeassistant.components.eight_sleep pyeight==0.0.7