Compare commits

...

14 Commits

Author SHA1 Message Date
Paulus Schoutsen
50714fbedf Merge pull request #30064 from home-assistant/rc
0.103.2
2019-12-19 08:03:47 +01:00
Paulus Schoutsen
c9de5b9fef Bumped version to 0.103.2 2019-12-19 07:53:38 +01:00
Paulus Schoutsen
aae80dca1c Fix recursion 2019-12-19 07:53:32 +01:00
Paulus Schoutsen
82a5c23c9c Merge pull request #30056 from home-assistant/rc
0.103.1
2019-12-18 22:15:02 +01:00
Paulus Schoutsen
952b21facc Bumped version to 0.103.1 2019-12-18 21:24:39 +01:00
Aaron Bach
9f64656603 Bump simplisafe-python to 5.3.6 (#30055) 2019-12-18 21:23:57 +01:00
Andrew Onyshchuk
6d06cec0e0 Fix support for legacy Z-Wave thermostats (#29955)
This brings back support for Z-Wave thermostats
of SETPOINT_THERMOSTAT specific device class.
Such devices don't have COMMAND_CLASS_THERMOSTAT_MODE
and are now handled separately.
2019-12-18 21:19:59 +01:00
Chris Mandich
9836e78120 Fix loading flume integration (#29926)
* Fix https://github.com/home-assistant/home-assistant/issues/29853

* Run script.gen_requirements

* Update to store Token File in config directory

* Update to store Token File in config directory

* Update to store Token File in config directory
2019-12-18 21:18:49 +01:00
Justin Bassett
f1d22db009 Fix mobile app device identifiers (#29920)
Fix identifiers when updating device registration.
2019-12-18 21:18:49 +01:00
Anders Melchiorsen
24c87638e6 Support entity_id: all in lifx.set_state (#29919) 2019-12-18 21:18:48 +01:00
springstan
9b65d83e28 Fix setup error for logbook (#29908)
* Fix setup error by moving an import back into the setup function

* Revert c741664d4d

* Add homekit as after_dependency to logbook manifest.json
2019-12-18 21:18:10 +01:00
springstan
29e412a6c4 Fix setup for tank_utility component (#29902) 2019-12-18 21:17:26 +01:00
Aaron Bach
a466ae0279 Bump aioambient to 1.0.2 (#29850) 2019-12-18 21:17:25 +01:00
Paulus Schoutsen
628a148944 Install requirements of after_dependencies when loading integrations (#29491)
* Install requirements of after_dependencies when loading integrations

* Fix smartthings test
2019-12-18 21:17:24 +01:00
34 changed files with 483 additions and 168 deletions

View File

@@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ambient_station",
"requirements": [
"aioambient==0.3.2"
"aioambient==1.0.2"
],
"dependencies": [],
"codeowners": [

View File

@@ -3,10 +3,7 @@
"name": "Auth",
"documentation": "https://www.home-assistant.io/integrations/auth",
"requirements": [],
"dependencies": [
"http"
],
"codeowners": [
"@home-assistant/core"
]
"dependencies": ["http"],
"after_dependencies": ["onboarding"],
"codeowners": ["@home-assistant/core"]
}

View File

@@ -3,11 +3,6 @@
"name": "Camera",
"documentation": "https://www.home-assistant.io/integrations/camera",
"requirements": [],
"dependencies": [
"http"
],
"after_dependencies": [
"stream"
],
"dependencies": ["http"],
"codeowners": []
}

View File

@@ -5,6 +5,7 @@
"documentation": "https://www.home-assistant.io/integrations/cast",
"requirements": ["pychromecast==4.0.1"],
"dependencies": [],
"after_dependencies": ["cloud"],
"zeroconf": ["_googlecast._tcp.local."],
"codeowners": []
}

View File

@@ -3,9 +3,9 @@
"name": "Flume",
"documentation": "https://www.home-assistant.io/integrations/flume/",
"requirements": [
"pyflume==0.2.1"
"pyflume==0.2.4"
],
"dependencies": [],
"codeowners": ["@ChrisMandich"]
}

View File

@@ -37,11 +37,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
password = config[CONF_PASSWORD]
client_id = config[CONF_CLIENT_ID]
client_secret = config[CONF_CLIENT_SECRET]
flume_token_file = hass.config.path("FLUME_TOKEN_FILE")
time_zone = str(hass.config.time_zone)
name = config[CONF_NAME]
flume_entity_list = []
flume_devices = FlumeDeviceList(username, password, client_id, client_secret)
flume_devices = FlumeDeviceList(
username, password, client_id, client_secret, flume_token_file
)
for device in flume_devices.device_list:
if device["type"] == FLUME_TYPE_SENSOR:
@@ -53,6 +56,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device["id"],
time_zone,
SCAN_INTERVAL,
flume_token_file,
)
flume_entity_list.append(FlumeSensor(flume, f"{name} {device['id']}"))

View File

@@ -4,5 +4,6 @@
"documentation": "https://www.home-assistant.io/integrations/google_assistant",
"requirements": [],
"dependencies": ["http"],
"after_dependencies": ["camera"],
"codeowners": ["@home-assistant/cloud"]
}

View File

@@ -35,7 +35,12 @@ from homeassistant.components.light import (
Light,
preprocess_turn_on_alternatives,
)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE, EVENT_HOMEASSISTANT_STOP
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_MODE,
ENTITY_MATCH_ALL,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.device_registry as dr
@@ -369,6 +374,9 @@ class LIFXManager:
async def async_service_to_entities(self, service):
"""Return the known entities that a service call mentions."""
if service.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_ALL:
return self.entities.values()
entity_ids = await async_extract_entity_ids(self.hass, service)
return [
entity

View File

@@ -3,9 +3,7 @@
"name": "Logbook",
"documentation": "https://www.home-assistant.io/integrations/logbook",
"requirements": [],
"dependencies": [
"frontend",
"recorder"
],
"dependencies": ["frontend", "http", "recorder"],
"after_dependencies": ["homekit"],
"codeowners": []
}

View File

@@ -37,9 +37,7 @@ class RegistrationsView(HomeAssistantView):
webhook_id = generate_secret()
cloud_loaded = "cloud" in hass.config.components
if cloud_loaded and hass.components.cloud.async_active_subscription():
if hass.components.cloud.async_active_subscription():
data[
CONF_CLOUDHOOK_URL
] = await hass.components.cloud.async_create_cloudhook(webhook_id)
@@ -59,11 +57,10 @@ class RegistrationsView(HomeAssistantView):
)
remote_ui_url = None
if cloud_loaded:
try:
remote_ui_url = hass.components.cloud.async_remote_ui_url()
except hass.components.cloud.CloudNotAvailable:
pass
try:
remote_ui_url = hass.components.cloud.async_remote_ui_url()
except hass.components.cloud.CloudNotAvailable:
pass
return self.json(
{

View File

@@ -5,5 +5,6 @@
"documentation": "https://www.home-assistant.io/integrations/mobile_app",
"requirements": ["PyNaCl==1.3.0"],
"dependencies": ["http", "webhook"],
"after_dependencies": ["cloud"],
"codeowners": ["@robbiet480"]
}

View File

@@ -190,10 +190,7 @@ async def handle_webhook(
device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
identifiers={
(ATTR_DEVICE_ID, registration[ATTR_DEVICE_ID]),
(CONF_WEBHOOK_ID, registration[CONF_WEBHOOK_ID]),
},
identifiers={(DOMAIN, registration[ATTR_DEVICE_ID])},
manufacturer=new_registration[ATTR_MANUFACTURER],
model=new_registration[ATTR_MODEL],
name=new_registration[ATTR_DEVICE_NAME],
@@ -309,10 +306,9 @@ async def handle_webhook(
if CONF_CLOUDHOOK_URL in registration:
resp[CONF_CLOUDHOOK_URL] = registration[CONF_CLOUDHOOK_URL]
if "cloud" in hass.config.components:
try:
resp[CONF_REMOTE_UI_URL] = hass.components.cloud.async_remote_ui_url()
except hass.components.cloud.CloudNotAvailable:
pass
try:
resp[CONF_REMOTE_UI_URL] = hass.components.cloud.async_remote_ui_url()
except hass.components.cloud.CloudNotAvailable:
pass
return webhook_response(resp, registration=registration, headers=headers)

View File

@@ -115,7 +115,7 @@ async def websocket_delete_registration(
except HomeAssistantError:
return error_message(msg["id"], "internal_error", "Error deleting registration")
if CONF_CLOUDHOOK_URL in registration and "cloud" in hass.config.components:
if CONF_CLOUDHOOK_URL in registration:
await hass.components.cloud.async_delete_cloudhook(webhook_id)
connection.send_message(result_message(msg["id"], "ok"))

View File

@@ -3,11 +3,6 @@
"name": "Onboarding",
"documentation": "https://www.home-assistant.io/integrations/onboarding",
"requirements": [],
"dependencies": [
"auth",
"http"
],
"codeowners": [
"@home-assistant/core"
]
"dependencies": ["auth", "http", "person"],
"codeowners": ["@home-assistant/core"]
}

View File

@@ -118,7 +118,7 @@ async def async_unload_entry(hass, entry):
async def async_remove_entry(hass, entry):
"""Remove an OwnTracks config entry."""
if not entry.data.get("cloudhook") or "cloud" not in hass.config.components:
if not entry.data.get("cloudhook"):
return
await hass.components.cloud.async_delete_cloudhook(entry.data[CONF_WEBHOOK_ID])

View File

@@ -66,10 +66,7 @@ class OwnTracksFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def _get_webhook_id(self):
"""Generate webhook ID."""
webhook_id = self.hass.components.webhook.async_generate_id()
if (
"cloud" in self.hass.config.components
and self.hass.components.cloud.async_active_subscription()
):
if self.hass.components.cloud.async_active_subscription():
webhook_url = await self.hass.components.cloud.async_create_cloudhook(
webhook_id
)

View File

@@ -3,14 +3,8 @@
"name": "Owntracks",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/owntracks",
"requirements": [
"PyNaCl==1.3.0"
],
"dependencies": [
"webhook"
],
"after_dependencies": [
"mqtt"
],
"requirements": ["PyNaCl==1.3.0"],
"dependencies": ["webhook"],
"after_dependencies": ["mqtt", "cloud"],
"codeowners": []
}

View File

@@ -7,7 +7,13 @@ import logging
from PIL import Image
import voluptuous as vol
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
from homeassistant.components.camera import (
PLATFORM_SCHEMA,
Camera,
async_get_image,
async_get_mjpeg_stream,
async_get_still_stream,
)
from homeassistant.const import CONF_ENTITY_ID, CONF_MODE, CONF_NAME
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
@@ -227,7 +233,7 @@ class ProxyCamera(Camera):
return self._last_image
self._last_image_time = now
image = await self.hass.components.camera.async_get_image(self._proxied_camera)
image = await async_get_image(self.hass, self._proxied_camera)
if not image:
_LOGGER.error("Error getting original camera image")
return self._last_image
@@ -247,12 +253,12 @@ class ProxyCamera(Camera):
async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from camera images."""
if not self._stream_opts:
return await self.hass.components.camera.async_get_mjpeg_stream(
request, self._proxied_camera
return await async_get_mjpeg_stream(
self.hass, request, self._proxied_camera
)
return await self.hass.components.camera.async_get_still_stream(
request, self._async_stream_image, self.content_type, self.frame_interval
return await async_get_still_stream(
request, self._async_stream_image, self.content_type, self.frame_interval,
)
@property
@@ -263,9 +269,7 @@ class ProxyCamera(Camera):
async def _async_stream_image(self):
"""Return a still image response from the camera."""
try:
image = await self.hass.components.camera.async_get_image(
self._proxied_camera
)
image = await async_get_image(self.hass, self._proxied_camera)
if not image:
return None
except HomeAssistantError:

View File

@@ -2,9 +2,7 @@
"domain": "proxy",
"name": "Proxy",
"documentation": "https://www.home-assistant.io/integrations/proxy",
"requirements": [
"pillow==6.2.1"
],
"requirements": ["pillow==6.2.1"],
"dependencies": [],
"codeowners": []
}

View File

@@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/simplisafe",
"requirements": [
"simplisafe-python==5.3.5"
"simplisafe-python==5.3.6"
],
"dependencies": [],
"codeowners": [

View File

@@ -3,14 +3,8 @@
"name": "Smartthings",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/smartthings",
"requirements": [
"pysmartapp==0.3.2",
"pysmartthings==0.6.9"
],
"dependencies": [
"webhook"
],
"codeowners": [
"@andrewsayre"
]
"requirements": ["pysmartapp==0.3.2", "pysmartthings==0.6.9"],
"dependencies": ["webhook"],
"after_dependencies": ["cloud"],
"codeowners": ["@andrewsayre"]
}

View File

@@ -88,10 +88,7 @@ async def validate_installed_app(api, installed_app_id: str):
def validate_webhook_requirements(hass: HomeAssistantType) -> bool:
"""Ensure HASS is setup properly to receive webhooks."""
if (
"cloud" in hass.config.components
and hass.components.cloud.async_active_subscription()
):
if hass.components.cloud.async_active_subscription():
return True
if hass.data[DOMAIN][CONF_CLOUDHOOK_URL] is not None:
return True
@@ -105,11 +102,7 @@ def get_webhook_url(hass: HomeAssistantType) -> str:
Return the cloudhook if available, otherwise local webhook.
"""
cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL]
if (
"cloud" in hass.config.components
and hass.components.cloud.async_active_subscription()
and cloudhook_url is not None
):
if hass.components.cloud.async_active_subscription() and cloudhook_url is not None:
return cloudhook_url
return webhook.async_generate_url(hass, hass.data[DOMAIN][CONF_WEBHOOK_ID])
@@ -229,7 +222,6 @@ async def setup_smartapp_endpoint(hass: HomeAssistantType):
cloudhook_url = config.get(CONF_CLOUDHOOK_URL)
if (
cloudhook_url is None
and "cloud" in hass.config.components
and hass.components.cloud.async_active_subscription()
and not hass.config_entries.async_entries(DOMAIN)
):
@@ -281,11 +273,7 @@ async def unload_smartapp_endpoint(hass: HomeAssistantType):
return
# Remove the cloudhook if it was created
cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL]
if (
cloudhook_url
and "cloud" in hass.config.components
and hass.components.cloud.async_is_logged_in()
):
if cloudhook_url and hass.components.cloud.async_is_logged_in():
await hass.components.cloud.async_delete_cloudhook(
hass.data[DOMAIN][CONF_WEBHOOK_ID]
)

View File

@@ -4,7 +4,7 @@ import datetime
import logging
import requests
import tank_utility
from tank_utility import auth, device as tank_monitor
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
@@ -47,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
devices = config.get(CONF_DEVICES)
try:
token = tank_utility.auth.get_token(email, password)
token = auth.get_token(email, password)
except requests.exceptions.HTTPError as http_error:
if (
http_error.response.status_code
@@ -111,17 +111,15 @@ class TankUtilitySensor(Entity):
data = {}
try:
data = tank_utility.device.get_device_data(self._token, self.device)
data = tank_monitor.get_device_data(self._token, self.device)
except requests.exceptions.HTTPError as http_error:
if (
http_error.response.status_code
== requests.codes.unauthorized # pylint: disable=no-member
):
_LOGGER.info("Getting new token")
self._token = tank_utility.auth.get_token(
self._email, self._password, force=True
)
data = tank_utility.device.get_device_data(self._token, self.device)
self._token = auth.get_token(self._email, self._password, force=True)
data = tank_monitor.get_device_data(self._token, self.device)
else:
raise http_error
data.update(data.pop("device", {}))

View File

@@ -1,11 +1,12 @@
"""Support for Z-Wave climate devices."""
# Because we do not compile openzwave on CI
import logging
from typing import Optional
from typing import Optional, Tuple
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
CURRENT_HVAC_COOL,
CURRENT_HVAC_FAN,
CURRENT_HVAC_HEAT,
@@ -14,29 +15,26 @@ from homeassistant.components.climate.const import (
DOMAIN,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF,
PRESET_AWAY,
PRESET_BOOST,
PRESET_NONE,
SUPPORT_AUX_HEAT,
SUPPORT_FAN_MODE,
SUPPORT_PRESET_MODE,
SUPPORT_SWING_MODE,
SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE,
SUPPORT_PRESET_MODE,
ATTR_TARGET_TEMP_LOW,
ATTR_TARGET_TEMP_HIGH,
)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import ZWaveDeviceEntity
from . import ZWaveDeviceEntity, const
_LOGGER = logging.getLogger(__name__)
@@ -149,10 +147,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
def get_device(hass, values, **kwargs):
"""Create Z-Wave entity device."""
temp_unit = hass.config.units.temperature_unit
return ZWaveClimate(values, temp_unit)
if values.primary.command_class == const.COMMAND_CLASS_THERMOSTAT_SETPOINT:
return ZWaveClimateSingleSetpoint(values, temp_unit)
if values.primary.command_class == const.COMMAND_CLASS_THERMOSTAT_MODE:
return ZWaveClimateMultipleSetpoint(values, temp_unit)
return None
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
class ZWaveClimateBase(ZWaveDeviceEntity, ClimateDevice):
"""Representation of a Z-Wave Climate device."""
def __init__(self, values, temp_unit):
@@ -190,18 +192,21 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self._zxt_120 = 1
self.update_properties()
def _current_mode_setpoints(self):
current_mode = str(self.values.primary.data).lower()
setpoints_names = MODE_SETPOINT_MAPPINGS.get(current_mode, ())
return tuple(getattr(self.values, name, None) for name in setpoints_names)
def _mode(self) -> None:
"""Return thermostat mode Z-Wave value."""
raise NotImplementedError()
def _current_mode_setpoints(self) -> Tuple:
"""Return a tuple of current setpoint Z-Wave value(s)."""
raise NotImplementedError()
@property
def supported_features(self):
"""Return the list of supported features."""
support = SUPPORT_TARGET_TEMPERATURE
if HVAC_MODE_HEAT_COOL in self._hvac_list:
if self._hvac_list and HVAC_MODE_HEAT_COOL in self._hvac_list:
support |= SUPPORT_TARGET_TEMPERATURE_RANGE
if PRESET_AWAY in self._preset_list:
if self._preset_list and PRESET_AWAY in self._preset_list:
support |= SUPPORT_TARGET_TEMPERATURE_RANGE
if self.values.fan_mode:
@@ -239,13 +244,13 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def _update_operation_mode(self):
"""Update hvac and preset modes."""
if self.values.primary:
if self._mode():
self._hvac_list = []
self._hvac_mapping = {}
self._preset_list = []
self._preset_mapping = {}
mode_list = self.values.primary.data_items
mode_list = self._mode().data_items
if mode_list:
for mode in mode_list:
ha_mode = HVAC_STATE_MAPPINGS.get(str(mode).lower())
@@ -273,7 +278,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
# Presets are supported
self._preset_list.append(PRESET_NONE)
current_mode = self.values.primary.data
current_mode = self._mode().data
_LOGGER.debug("current_mode=%s", current_mode)
_hvac_temp = next(
(
@@ -426,7 +431,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
Need to be one of HVAC_MODE_*.
"""
if self.values.primary:
if self._mode():
return self._hvac_mode
return self._default_hvac_mode
@@ -436,7 +441,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
Need to be a subset of HVAC_MODES.
"""
if self.values.primary:
if self._mode():
return self._hvac_list
return []
@@ -453,7 +458,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Return true if aux heater."""
if not self._aux_heat:
return None
if self.values.primary.data == AUX_HEAT_ZWAVE_MODE:
if self._mode().data == AUX_HEAT_ZWAVE_MODE:
return True
return False
@@ -463,7 +468,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
Need to be one of PRESET_*.
"""
if self.values.primary:
if self._mode():
return self._preset_mode
return PRESET_NONE
@@ -473,7 +478,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
Need to be a subset of PRESET_MODES.
"""
if self.values.primary:
if self._mode():
return self._preset_list
return []
@@ -522,11 +527,11 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
_LOGGER.debug("Set hvac_mode to %s", hvac_mode)
if not self.values.primary:
if not self._mode():
return
operation_mode = self._hvac_mapping.get(hvac_mode)
_LOGGER.debug("Set operation_mode to %s", operation_mode)
self.values.primary.data = operation_mode
self._mode().data = operation_mode
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
@@ -534,7 +539,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
return
operation_mode = AUX_HEAT_ZWAVE_MODE
_LOGGER.debug("Aux heat on. Set operation mode to %s", operation_mode)
self.values.primary.data = operation_mode
self._mode().data = operation_mode
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
@@ -545,23 +550,23 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
else:
operation_mode = self._hvac_mapping.get(HVAC_MODE_OFF)
_LOGGER.debug("Aux heat off. Set operation mode to %s", operation_mode)
self.values.primary.data = operation_mode
self._mode().data = operation_mode
def set_preset_mode(self, preset_mode):
"""Set new target preset mode."""
_LOGGER.debug("Set preset_mode to %s", preset_mode)
if not self.values.primary:
if not self._mode():
return
if preset_mode == PRESET_NONE:
# Activate the current hvac mode
self._update_operation_mode()
operation_mode = self._hvac_mapping.get(self.hvac_mode)
_LOGGER.debug("Set operation_mode to %s", operation_mode)
self.values.primary.data = operation_mode
self._mode().data = operation_mode
else:
operation_mode = self._preset_mapping.get(preset_mode, preset_mode)
_LOGGER.debug("Set operation_mode to %s", operation_mode)
self.values.primary.data = operation_mode
self._mode().data = operation_mode
def set_swing_mode(self, swing_mode):
"""Set new target swing mode."""
@@ -577,3 +582,37 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
if self._fan_action:
data[ATTR_FAN_ACTION] = self._fan_action
return data
class ZWaveClimateSingleSetpoint(ZWaveClimateBase):
"""Representation of a single setpoint Z-Wave thermostat device."""
def __init__(self, values, temp_unit):
"""Initialize the Z-Wave climate device."""
ZWaveClimateBase.__init__(self, values, temp_unit)
def _mode(self) -> None:
"""Return thermostat mode Z-Wave value."""
return self.values.mode
def _current_mode_setpoints(self) -> Tuple:
"""Return a tuple of current setpoint Z-Wave value(s)."""
return (self.values.primary,)
class ZWaveClimateMultipleSetpoint(ZWaveClimateBase):
"""Representation of a multiple setpoint Z-Wave thermostat device."""
def __init__(self, values, temp_unit):
"""Initialize the Z-Wave climate device."""
ZWaveClimateBase.__init__(self, values, temp_unit)
def _mode(self) -> None:
"""Return thermostat mode Z-Wave value."""
return self.values.primary
def _current_mode_setpoints(self) -> Tuple:
"""Return a tuple of current setpoint Z-Wave value(s)."""
current_mode = str(self.values.primary.data).lower()
setpoints_names = MODE_SETPOINT_MAPPINGS.get(current_mode, ())
return tuple(getattr(self.values, name, None) for name in setpoints_names)

View File

@@ -48,11 +48,60 @@ DISCOVERY_SCHEMAS = [
),
},
{
const.DISC_COMPONENT: "climate",
const.DISC_COMPONENT: "climate", # thermostat without COMMAND_CLASS_THERMOSTAT_MODE
const.DISC_GENERIC_DEVICE_CLASS: [
const.GENERIC_TYPE_THERMOSTAT,
const.GENERIC_TYPE_SENSOR_MULTILEVEL,
],
const.DISC_SPECIFIC_DEVICE_CLASS: [
const.SPECIFIC_TYPE_THERMOSTAT_HEATING,
const.SPECIFIC_TYPE_SETPOINT_THERMOSTAT,
],
const.DISC_VALUES: dict(
DEFAULT_VALUES_SCHEMA,
**{
const.DISC_PRIMARY: {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_SETPOINT]
},
"temperature": {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SENSOR_MULTILEVEL],
const.DISC_INDEX: [const.INDEX_SENSOR_MULTILEVEL_TEMPERATURE],
const.DISC_OPTIONAL: True,
},
"fan_mode": {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_FAN_MODE],
const.DISC_OPTIONAL: True,
},
"operating_state": {
const.DISC_COMMAND_CLASS: [
const.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE
],
const.DISC_OPTIONAL: True,
},
"fan_action": {
const.DISC_COMMAND_CLASS: [
const.COMMAND_CLASS_THERMOSTAT_FAN_ACTION
],
const.DISC_OPTIONAL: True,
},
"mode": {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_MODE],
const.DISC_OPTIONAL: True,
},
},
),
},
{
const.DISC_COMPONENT: "climate", # thermostat with COMMAND_CLASS_THERMOSTAT_MODE
const.DISC_GENERIC_DEVICE_CLASS: [
const.GENERIC_TYPE_THERMOSTAT,
const.GENERIC_TYPE_SENSOR_MULTILEVEL,
],
const.DISC_SPECIFIC_DEVICE_CLASS: [
const.SPECIFIC_TYPE_THERMOSTAT_GENERAL,
const.SPECIFIC_TYPE_THERMOSTAT_GENERAL_V2,
const.SPECIFIC_TYPE_SETBACK_THERMOSTAT,
],
const.DISC_VALUES: dict(
DEFAULT_VALUES_SCHEMA,
**{

View File

@@ -1,7 +1,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 103
PATCH_VERSION = "0"
PATCH_VERSION = "2"
__short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION)
__version__ = "{}.{}".format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 6, 1)

View File

@@ -3,7 +3,7 @@ import asyncio
from pathlib import Path
import logging
import os
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Set
from homeassistant.exceptions import HomeAssistantError
import homeassistant.util.package as pkg_util
@@ -28,16 +28,19 @@ class RequirementsNotFound(HomeAssistantError):
async def async_get_integration_with_requirements(
hass: HomeAssistant, domain: str
hass: HomeAssistant, domain: str, done: Set[str] = None
) -> Integration:
"""Get an integration with installed requirements.
This can raise IntegrationNotFound if manifest or integration
is invalid, RequirementNotFound if there was some type of
failure to install requirements.
Does not handle circular dependencies.
"""
if done is None:
done = {domain}
else:
done.add(domain)
integration = await async_get_integration(hass, domain)
if hass.config.skip_pip:
@@ -48,11 +51,18 @@ async def async_get_integration_with_requirements(
hass, integration.domain, integration.requirements
)
deps = integration.dependencies + (integration.after_dependencies or [])
deps_to_check = [
dep
for dep in integration.dependencies + (integration.after_dependencies or [])
if dep not in done
]
if deps:
if deps_to_check:
await asyncio.gather(
*[async_get_integration_with_requirements(hass, dep) for dep in deps]
*[
async_get_integration_with_requirements(hass, dep, done)
for dep in deps_to_check
]
)
return integration

View File

@@ -288,8 +288,8 @@ async def async_process_deps_reqs(
raise HomeAssistantError("Could not set up all dependencies.")
if not hass.config.skip_pip and integration.requirements:
await requirements.async_process_requirements(
hass, integration.domain, integration.requirements
await requirements.async_get_integration_with_requirements(
hass, integration.domain
)
processed.add(integration.domain)

View File

@@ -133,7 +133,7 @@ aio_geojson_geonetnz_volcano==0.5
aio_geojson_nsw_rfs_incidents==0.1
# homeassistant.components.ambient_station
aioambient==0.3.2
aioambient==1.0.2
# homeassistant.components.asuswrt
aioasuswrt==1.1.22
@@ -1231,7 +1231,7 @@ pyflexit==0.3
pyflic-homeassistant==0.4.dev0
# homeassistant.components.flume
pyflume==0.2.1
pyflume==0.2.4
# homeassistant.components.flunearyou
pyflunearyou==1.0.3
@@ -1793,7 +1793,7 @@ shodan==1.20.0
simplepush==1.1.4
# homeassistant.components.simplisafe
simplisafe-python==5.3.5
simplisafe-python==5.3.6
# homeassistant.components.sisyphus
sisyphus-control==2.2.1

View File

@@ -44,7 +44,7 @@ aio_geojson_geonetnz_volcano==0.5
aio_geojson_nsw_rfs_incidents==0.1
# homeassistant.components.ambient_station
aioambient==0.3.2
aioambient==1.0.2
# homeassistant.components.asuswrt
aioasuswrt==1.1.22
@@ -558,7 +558,7 @@ rxv==0.6.0
samsungctl[websocket]==0.7.1
# homeassistant.components.simplisafe
simplisafe-python==5.3.5
simplisafe-python==5.3.6
# homeassistant.components.sleepiq
sleepyq==0.7

View File

@@ -43,15 +43,12 @@ def validate_dependencies(integration: Integration):
if referenced:
for domain in sorted(referenced):
print(
"Warning: {} references integration {} but it's not a "
"dependency".format(integration.domain, domain)
integration.add_error(
"dependencies",
"Using component {} but it's not in 'dependencies' or 'after_dependencies'".format(
domain
),
)
# Not enforced yet.
# integration.add_error(
# 'dependencies',
# "Using component {} but it's not a dependency".format(domain)
# )
def validate(integrations: Dict[str, Integration], config):

View File

@@ -7,7 +7,6 @@ from pysmartthings import APIResponseError
from homeassistant import data_entry_flow
from homeassistant.setup import async_setup_component
from homeassistant.components import cloud
from homeassistant.components.smartthings import smartapp
from homeassistant.components.smartthings.config_flow import SmartThingsFlowHandler
from homeassistant.components.smartthings.const import (
@@ -18,7 +17,7 @@ from homeassistant.components.smartthings.const import (
DOMAIN,
)
from tests.common import MockConfigEntry
from tests.common import MockConfigEntry, mock_coro
async def test_step_user(hass):
@@ -211,9 +210,11 @@ async def test_cloudhook_app_created_then_show_wait_form(
await smartapp.unload_smartapp_endpoint(hass)
with patch.object(
cloud, "async_active_subscription", return_value=True
hass.components.cloud, "async_active_subscription", return_value=True
), patch.object(
cloud, "async_create_cloudhook", return_value="http://cloud.test"
hass.components.cloud,
"async_create_cloudhook",
return_value=mock_coro("http://cloud.test"),
) as mock_create_cloudhook:
await smartapp.setup_smartapp_endpoint(hass)

View File

@@ -13,6 +13,7 @@ from homeassistant.components.climate.const import (
PRESET_BOOST,
PRESET_ECO,
PRESET_NONE,
SUPPORT_AUX_HEAT,
SUPPORT_FAN_MODE,
SUPPORT_PRESET_MODE,
SUPPORT_SWING_MODE,
@@ -21,8 +22,11 @@ from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_LOW,
ATTR_TARGET_TEMP_HIGH,
)
from homeassistant.components.zwave import climate
from homeassistant.components.zwave.climate import DEFAULT_HVAC_MODES
from homeassistant.components.zwave import climate, const
from homeassistant.components.zwave.climate import (
AUX_HEAT_ZWAVE_MODE,
DEFAULT_HVAC_MODES,
)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed
@@ -34,6 +38,7 @@ def device(hass, mock_openzwave):
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT,
data_items=[
HVAC_MODE_OFF,
@@ -62,6 +67,7 @@ def device_zxt_120(hass, mock_openzwave):
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT,
data_items=[
HVAC_MODE_OFF,
@@ -90,6 +96,7 @@ def device_mapping(hass, mock_openzwave):
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data="Heat",
data_items=["Off", "Cool", "Heat", "Full Power", "Auto"],
node=node,
@@ -112,6 +119,7 @@ def device_unknown(hass, mock_openzwave):
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data="Heat",
data_items=["Off", "Cool", "Heat", "heat_cool", "Abcdefg"],
node=node,
@@ -134,6 +142,7 @@ def device_heat_cool(hass, mock_openzwave):
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT,
data_items=[
HVAC_MODE_OFF,
@@ -162,6 +171,7 @@ def device_heat_cool_range(hass, mock_openzwave):
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT_COOL,
data_items=[
HVAC_MODE_OFF,
@@ -189,6 +199,7 @@ def device_heat_cool_away(hass, mock_openzwave):
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT_COOL,
data_items=[
HVAC_MODE_OFF,
@@ -219,6 +230,7 @@ def device_heat_eco(hass, mock_openzwave):
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT,
data_items=[HVAC_MODE_OFF, HVAC_MODE_HEAT, "heat econ"],
node=node,
@@ -235,6 +247,100 @@ def device_heat_eco(hass, mock_openzwave):
yield device
@pytest.fixture
def device_aux_heat(hass, mock_openzwave):
"""Fixture to provide a precreated climate device. aux heat."""
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT,
data_items=[HVAC_MODE_OFF, HVAC_MODE_HEAT, "Aux Heat"],
node=node,
),
setpoint_heating=MockValue(data=2, node=node),
setpoint_eco_heating=MockValue(data=1, node=node),
temperature=MockValue(data=5, node=node, units=None),
fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node),
operating_state=MockValue(data="test4", node=node),
fan_action=MockValue(data=7, node=node),
)
device = climate.get_device(hass, node=node, values=values, node_config={})
yield device
@pytest.fixture
def device_single_setpoint(hass, mock_openzwave):
"""Fixture to provide a precreated climate device.
SETPOINT_THERMOSTAT device class.
"""
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_SETPOINT, data=1, node=node
),
mode=None,
temperature=MockValue(data=5, node=node, units=None),
fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node),
operating_state=MockValue(data=CURRENT_HVAC_HEAT, node=node),
fan_action=MockValue(data=7, node=node),
)
device = climate.get_device(hass, node=node, values=values, node_config={})
yield device
@pytest.fixture
def device_single_setpoint_with_mode(hass, mock_openzwave):
"""Fixture to provide a precreated climate device.
SETPOINT_THERMOSTAT device class with COMMAND_CLASS_THERMOSTAT_MODE command class
"""
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_SETPOINT, data=1, node=node
),
mode=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT,
data_items=[HVAC_MODE_OFF, HVAC_MODE_HEAT],
node=node,
),
temperature=MockValue(data=5, node=node, units=None),
fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node),
operating_state=MockValue(data=CURRENT_HVAC_HEAT, node=node),
fan_action=MockValue(data=7, node=node),
)
device = climate.get_device(hass, node=node, values=values, node_config={})
yield device
def test_get_device_detects_none(hass, mock_openzwave):
"""Test get_device returns None."""
node = MockNode()
value = MockValue(data=0, node=node)
values = MockEntityValues(primary=value)
device = climate.get_device(hass, node=node, values=values, node_config={})
assert device is None
def test_get_device_detects_multiple_setpoint_device(device):
"""Test get_device returns a Z-Wave multiple setpoint device."""
assert isinstance(device, climate.ZWaveClimateMultipleSetpoint)
def test_get_device_detects_single_setpoint_device(device_single_setpoint):
"""Test get_device returns a Z-Wave single setpoint device."""
assert isinstance(device_single_setpoint, climate.ZWaveClimateSingleSetpoint)
def test_default_hvac_modes():
"""Test wether all hvac modes are included in default_hvac_modes."""
for hvac_mode in HVAC_MODES:
@@ -274,6 +380,18 @@ def test_supported_features_preset_mode(device_mapping):
)
def test_supported_features_preset_mode_away(device_heat_cool_away):
"""Test supported features flags with swing mode."""
device = device_heat_cool_away
assert (
device.supported_features
== SUPPORT_FAN_MODE
+ SUPPORT_TARGET_TEMPERATURE
+ SUPPORT_TARGET_TEMPERATURE_RANGE
+ SUPPORT_PRESET_MODE
)
def test_supported_features_swing_mode(device_zxt_120):
"""Test supported features flags with swing mode."""
device = device_zxt_120
@@ -286,6 +404,27 @@ def test_supported_features_swing_mode(device_zxt_120):
)
def test_supported_features_aux_heat(device_aux_heat):
"""Test supported features flags with aux heat."""
device = device_aux_heat
assert (
device.supported_features
== SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE + SUPPORT_AUX_HEAT
)
def test_supported_features_single_setpoint(device_single_setpoint):
"""Test supported features flags for SETPOINT_THERMOSTAT."""
device = device_single_setpoint
assert device.supported_features == SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE
def test_supported_features_single_setpoint_with_mode(device_single_setpoint_with_mode):
"""Test supported features flags for SETPOINT_THERMOSTAT."""
device = device_single_setpoint_with_mode
assert device.supported_features == SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE
def test_zxt_120_swing_mode(device_zxt_120):
"""Test operation of the zxt 120 swing mode."""
device = device_zxt_120
@@ -331,6 +470,22 @@ def test_data_lists(device):
assert device.preset_modes == []
def test_data_lists_single_setpoint(device_single_setpoint):
"""Test data lists from zwave value items."""
device = device_single_setpoint
assert device.fan_modes == [3, 4, 5]
assert device.hvac_modes == []
assert device.preset_modes == []
def test_data_lists_single_setpoint_with_mode(device_single_setpoint_with_mode):
"""Test data lists from zwave value items."""
device = device_single_setpoint_with_mode
assert device.fan_modes == [3, 4, 5]
assert device.hvac_modes == [HVAC_MODE_OFF, HVAC_MODE_HEAT]
assert device.preset_modes == []
def test_data_lists_mapping(device_mapping):
"""Test data lists from zwave value items."""
device = device_mapping
@@ -404,6 +559,14 @@ def test_target_value_set_eco(device_heat_eco):
assert device.values.setpoint_eco_heating.data == 0
def test_target_value_set_single_setpoint(device_single_setpoint):
"""Test values changed for climate device."""
device = device_single_setpoint
assert device.values.primary.data == 1
device.set_temperature(**{ATTR_TEMPERATURE: 2})
assert device.values.primary.data == 2
def test_operation_value_set(device):
"""Test values changed for climate device."""
assert device.values.primary.data == HVAC_MODE_HEAT
@@ -546,6 +709,15 @@ def test_target_changed_with_mode(device):
assert device.target_temperature_high == 10
def test_target_value_changed_single_setpoint(device_single_setpoint):
"""Test values changed for climate device."""
device = device_single_setpoint
assert device.target_temperature == 1
device.values.primary.data = 2
value_changed(device.values.primary)
assert device.target_temperature == 2
def test_temperature_value_changed(device):
"""Test values changed for climate device."""
assert device.current_temperature == 5
@@ -677,3 +849,44 @@ def test_fan_action_value_changed(device):
device.values.fan_action.data = 9
value_changed(device.values.fan_action)
assert device.device_state_attributes[climate.ATTR_FAN_ACTION] == 9
def test_aux_heat_unsupported_set(device):
"""Test aux heat for climate device."""
device = device
assert device.values.primary.data == HVAC_MODE_HEAT
device.turn_aux_heat_on()
assert device.values.primary.data == HVAC_MODE_HEAT
device.turn_aux_heat_off()
assert device.values.primary.data == HVAC_MODE_HEAT
def test_aux_heat_unsupported_value_changed(device):
"""Test aux heat for climate device."""
device = device
assert device.is_aux_heat is None
device.values.primary.data = HVAC_MODE_HEAT
value_changed(device.values.primary)
assert device.is_aux_heat is None
def test_aux_heat_set(device_aux_heat):
"""Test aux heat for climate device."""
device = device_aux_heat
assert device.values.primary.data == HVAC_MODE_HEAT
device.turn_aux_heat_on()
assert device.values.primary.data == AUX_HEAT_ZWAVE_MODE
device.turn_aux_heat_off()
assert device.values.primary.data == HVAC_MODE_HEAT
def test_aux_heat_value_changed(device_aux_heat):
"""Test aux heat for climate device."""
device = device_aux_heat
assert device.is_aux_heat is False
device.values.primary.data = AUX_HEAT_ZWAVE_MODE
value_changed(device.values.primary)
assert device.is_aux_heat is True
device.values.primary.data = HVAC_MODE_HEAT
value_changed(device.values.primary)
assert device.is_aux_heat is False

View File

@@ -573,7 +573,11 @@ async def test_value_discovery_existing_entity(hass, mock_openzwave):
assert len(mock_receivers) == 1
node = MockNode(node_id=11, generic=const.GENERIC_TYPE_THERMOSTAT)
node = MockNode(
node_id=11,
generic=const.GENERIC_TYPE_THERMOSTAT,
specific=const.SPECIFIC_TYPE_THERMOSTAT_GENERAL_V2,
)
thermostat_mode = MockValue(
data="Heat",
data_items=["Off", "Heat"],
@@ -638,6 +642,42 @@ async def test_value_discovery_existing_entity(hass, mock_openzwave):
)
async def test_value_discovery_legacy_thermostat(hass, mock_openzwave):
"""Test discovery of a node. Special case for legacy thermostats."""
mock_receivers = []
def mock_connect(receiver, signal, *args, **kwargs):
if signal == MockNetwork.SIGNAL_VALUE_ADDED:
mock_receivers.append(receiver)
with patch("pydispatch.dispatcher.connect", new=mock_connect):
await async_setup_component(hass, "zwave", {"zwave": {}})
await hass.async_block_till_done()
assert len(mock_receivers) == 1
node = MockNode(
node_id=11,
generic=const.GENERIC_TYPE_THERMOSTAT,
specific=const.SPECIFIC_TYPE_SETPOINT_THERMOSTAT,
)
setpoint_heating = MockValue(
data=22.0,
node=node,
command_class=const.COMMAND_CLASS_THERMOSTAT_SETPOINT,
index=1,
genre=const.GENRE_USER,
)
hass.async_add_job(mock_receivers[0], node, setpoint_heating)
await hass.async_block_till_done()
assert (
hass.states.get("climate.mock_node_mock_value").attributes["temperature"]
== 22.0
)
async def test_power_schemes(hass, mock_openzwave):
"""Test power attribute."""
mock_receivers = []