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
139 changes: 135 additions & 4 deletions homeassistant/components/ecobee/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,21 @@
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"
Expand Down Expand Up @@ -84,13 +93,37 @@
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,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should have validated the date and time as date and time? Future improvement possible. Sorry for missing this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had thought of that - but it's really a string that gets passed to the ecobee API so we wouldn't necessarily want to convert it to a datetime object - and in any event if it's not in a valid format, then the vacation won't get created by the API.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would just be for validation. We like to validate as early as possible, ie in the service schema.

Copy link
Copy Markdown
Contributor Author

@marthoc marthoc Sep 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this validate or transform?

vol.Inclusive(ATTR_START_DATE, "dtgroup", msg=DTGROUP_INCLUSIVE_MSG): cv.date,
vol.Inclusive(ATTR_START_TIME, "dtgroup", msg=DTGROUP_INCLUSIVE_MSG): cv.time,
vol.Inclusive(ATTR_END_DATE, "dtgroup", msg=DTGROUP_INCLUSIVE_MSG): cv.date,
vol.Inclusive(ATTR_END_TIME, "dtgroup", msg=DTGROUP_INCLUSIVE_MSG): cv.time,

I'm happy to open a PR now to patch this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would transform, so we'd need to transform back to string with another validator after the first one. Combine with vol.All.

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,
}
)

Expand All @@ -101,6 +134,14 @@
}
)

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
Expand All @@ -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)
Expand Down Expand Up @@ -158,6 +220,20 @@ def resume_program_set_service(service):

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,
Expand Down Expand Up @@ -536,3 +612,58 @@ def hold_preference(self):
# 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)
2 changes: 1 addition & 1 deletion homeassistant/components/ecobee/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
52 changes: 52 additions & 0 deletions homeassistant/components/ecobee/services.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down