From c194f4a8137a2ca06cfc5545510c5fe552c4b546 Mon Sep 17 00:00:00 2001 From: Mark Coombes Date: Thu, 26 Sep 2019 15:23:44 -0400 Subject: [PATCH] Add ecobee services to create and delete vacations (#26923) * Bump pyecobee to 0.1.3 * Add functions, attrs, and services to climate Fixes * Update requirements * Update service strings * Fix typo * Fix log msg string formatting for lint * Change some parameters to Inclusive start_date, start_time, end_date, and end_time must be specified together. * Use built-in convert util for temps * Match other service variable names --- homeassistant/components/ecobee/climate.py | 139 +++++++++++++++++- homeassistant/components/ecobee/manifest.json | 2 +- homeassistant/components/ecobee/services.yaml | 52 +++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 190 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index 9eb8e8f26bc..460bd2bb4a4 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -33,12 +33,21 @@ from homeassistant.const import ( ATTR_TEMPERATURE, TEMP_FAHRENHEIT, ) +from homeassistant.util.temperature import convert import homeassistant.helpers.config_validation as cv from .const import DOMAIN, _LOGGER +ATTR_COOL_TEMP = "cool_temp" +ATTR_END_DATE = "end_date" +ATTR_END_TIME = "end_time" ATTR_FAN_MIN_ON_TIME = "fan_min_on_time" +ATTR_FAN_MODE = "fan_mode" +ATTR_HEAT_TEMP = "heat_temp" ATTR_RESUME_ALL = "resume_all" +ATTR_START_DATE = "start_date" +ATTR_START_TIME = "start_time" +ATTR_VACATION_NAME = "vacation_name" DEFAULT_RESUME_ALL = False PRESET_TEMPERATURE = "temp" @@ -84,13 +93,37 @@ PRESET_TO_ECOBEE_HOLD = { PRESET_HOLD_INDEFINITE: "indefinite", } -SERVICE_SET_FAN_MIN_ON_TIME = "set_fan_min_on_time" +SERVICE_CREATE_VACATION = "create_vacation" +SERVICE_DELETE_VACATION = "delete_vacation" SERVICE_RESUME_PROGRAM = "resume_program" +SERVICE_SET_FAN_MIN_ON_TIME = "set_fan_min_on_time" -SET_FAN_MIN_ON_TIME_SCHEMA = vol.Schema( +DTGROUP_INCLUSIVE_MSG = ( + f"{ATTR_START_DATE}, {ATTR_START_TIME}, {ATTR_END_DATE}, " + f"and {ATTR_END_TIME} must be specified together" +) + +CREATE_VACATION_SCHEMA = vol.Schema( { - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_FAN_MIN_ON_TIME): vol.Coerce(int), + vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_VACATION_NAME): cv.string, + vol.Required(ATTR_COOL_TEMP): vol.Coerce(float), + vol.Required(ATTR_HEAT_TEMP): vol.Coerce(float), + vol.Inclusive(ATTR_START_DATE, "dtgroup", msg=DTGROUP_INCLUSIVE_MSG): cv.string, + vol.Inclusive(ATTR_START_TIME, "dtgroup", msg=DTGROUP_INCLUSIVE_MSG): cv.string, + vol.Inclusive(ATTR_END_DATE, "dtgroup", msg=DTGROUP_INCLUSIVE_MSG): cv.string, + vol.Inclusive(ATTR_END_TIME, "dtgroup", msg=DTGROUP_INCLUSIVE_MSG): cv.string, + vol.Optional(ATTR_FAN_MODE, default="auto"): vol.Any("auto", "on"), + vol.Optional(ATTR_FAN_MIN_ON_TIME, default=0): vol.All( + int, vol.Range(min=0, max=60) + ), + } +) + +DELETE_VACATION_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_VACATION_NAME): cv.string, } ) @@ -101,6 +134,14 @@ RESUME_PROGRAM_SCHEMA = vol.Schema( } ) +SET_FAN_MIN_ON_TIME_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_FAN_MIN_ON_TIME): vol.Coerce(int), + } +) + + SUPPORT_FLAGS = ( SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE @@ -124,6 +165,27 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(devices, True) + def create_vacation_service(service): + """Create a vacation on the target thermostat.""" + entity_id = service.data[ATTR_ENTITY_ID] + + for thermostat in devices: + if thermostat.entity_id == entity_id: + thermostat.create_vacation(service.data) + thermostat.schedule_update_ha_state(True) + break + + def delete_vacation_service(service): + """Delete a vacation on the target thermostat.""" + entity_id = service.data[ATTR_ENTITY_ID] + vacation_name = service.data[ATTR_VACATION_NAME] + + for thermostat in devices: + if thermostat.entity_id == entity_id: + thermostat.delete_vacation(vacation_name) + thermostat.schedule_update_ha_state(True) + break + def fan_min_on_time_set_service(service): """Set the minimum fan on time on the target thermostats.""" entity_id = service.data.get(ATTR_ENTITY_ID) @@ -158,6 +220,20 @@ async def async_setup_entry(hass, config_entry, async_add_entities): thermostat.schedule_update_ha_state(True) + hass.services.async_register( + DOMAIN, + SERVICE_CREATE_VACATION, + create_vacation_service, + schema=CREATE_VACATION_SCHEMA, + ) + + hass.services.async_register( + DOMAIN, + SERVICE_DELETE_VACATION, + delete_vacation_service, + schema=DELETE_VACATION_SCHEMA, + ) + hass.services.async_register( DOMAIN, SERVICE_SET_FAN_MIN_ON_TIME, @@ -536,3 +612,58 @@ class Thermostat(ClimateDevice): # supported; note that this should not include 'indefinite' # as an indefinite away hold is interpreted as away_mode return "nextTransition" + + def create_vacation(self, service_data): + """Create a vacation with user-specified parameters.""" + vacation_name = service_data[ATTR_VACATION_NAME] + cool_temp = convert( + service_data[ATTR_COOL_TEMP], + self.hass.config.units.temperature_unit, + TEMP_FAHRENHEIT, + ) + heat_temp = convert( + service_data[ATTR_HEAT_TEMP], + self.hass.config.units.temperature_unit, + TEMP_FAHRENHEIT, + ) + start_date = service_data.get(ATTR_START_DATE) + start_time = service_data.get(ATTR_START_TIME) + end_date = service_data.get(ATTR_END_DATE) + end_time = service_data.get(ATTR_END_TIME) + fan_mode = service_data[ATTR_FAN_MODE] + fan_min_on_time = service_data[ATTR_FAN_MIN_ON_TIME] + + kwargs = { + key: value + for key, value in { + "start_date": start_date, + "start_time": start_time, + "end_date": end_date, + "end_time": end_time, + "fan_mode": fan_mode, + "fan_min_on_time": fan_min_on_time, + }.items() + if value is not None + } + + _LOGGER.debug( + "Creating a vacation on thermostat %s with name %s, cool temp %s, heat temp %s, " + "and the following other parameters: %s", + self.name, + vacation_name, + cool_temp, + heat_temp, + kwargs, + ) + self.data.ecobee.create_vacation( + self.thermostat_index, vacation_name, cool_temp, heat_temp, **kwargs + ) + + def delete_vacation(self, vacation_name): + """Delete a vacation with the specified name.""" + _LOGGER.debug( + "Deleting a vacation on thermostat %s with name %s", + self.name, + vacation_name, + ) + self.data.ecobee.delete_vacation(self.thermostat_index, vacation_name) diff --git a/homeassistant/components/ecobee/manifest.json b/homeassistant/components/ecobee/manifest.json index 092594c41fc..131c35d7f89 100644 --- a/homeassistant/components/ecobee/manifest.json +++ b/homeassistant/components/ecobee/manifest.json @@ -4,6 +4,6 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/components/ecobee", "dependencies": [], - "requirements": ["python-ecobee-api==0.1.2"], + "requirements": ["python-ecobee-api==0.1.3"], "codeowners": ["@marthoc"] } diff --git a/homeassistant/components/ecobee/services.yaml b/homeassistant/components/ecobee/services.yaml index 87eefed9eaa..2155d3cf7d2 100644 --- a/homeassistant/components/ecobee/services.yaml +++ b/homeassistant/components/ecobee/services.yaml @@ -1,3 +1,55 @@ +create_vacation: + description: >- + Create a vacation on the selected thermostat. Note: start/end date and time must all be specified + together for these parameters to have an effect. If start/end date and time are not specified, the + vacation will start immediately and last 14 days (unless deleted earlier). + fields: + entity_id: + description: ecobee thermostat on which to create the vacation (required). + example: "climate.kitchen" + vacation_name: + description: Name of the vacation to create; must be unique on the thermostat (required). + example: "Skiing" + cool_temp: + description: Cooling temperature during the vacation (required). + example: 23 + heat_temp: + description: Heating temperature during the vacation (required). + example: 25 + start_date: + description: >- + Date the vacation starts in the YYYY-MM-DD format (optional, immediately if not provided along with + start_time, end_date, and end_time). + example: "2019-03-15" + start_time: + description: Time the vacation starts, in the local time of the thermostat, in the 24-hour format "HH:MM:SS" + example: "20:00:00" + end_date: + description: >- + Date the vacation ends in the YYYY-MM-DD format (optional, 14 days from now if not provided along with + start_date, start_time, and end_time). + example: "2019-03-20" + end_time: + description: Time the vacation ends, in the local time of the thermostat, in the 24-hour format "HH:MM:SS" + example: "20:00:00" + fan_mode: + description: Fan mode of the thermostat during the vacation (auto or on) (optional, auto if not provided). + example: "on" + fan_min_on_time: + description: Minimum number of minutes to run the fan each hour (0 to 60) during the vacation (optional, 0 if not provided). + example: 30 + +delete_vacation: + description: >- + Delete a vacation on the selected thermostat. + fields: + entity_id: + description: ecobee thermostat on which to delete the vacation (required). + example: "climate.kitchen" + vacation_name: + description: Name of the vacation to delete (required). + example: "Skiing" + resume_program: description: Resume the programmed schedule. fields: diff --git a/requirements_all.txt b/requirements_all.txt index 3b9eb718737..19bdc4efd83 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1483,7 +1483,7 @@ python-clementine-remote==1.0.1 python-digitalocean==1.13.2 # homeassistant.components.ecobee -python-ecobee-api==0.1.2 +python-ecobee-api==0.1.3 # homeassistant.components.eq3btsmart # python-eq3bt==0.1.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d790a423de3..3a4fa60f15e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -356,7 +356,7 @@ pysonos==0.0.23 pyspcwebgw==0.4.0 # homeassistant.components.ecobee -python-ecobee-api==0.1.2 +python-ecobee-api==0.1.3 # homeassistant.components.darksky python-forecastio==1.4.0