Merge pull request #30056 from home-assistant/rc

0.103.1
This commit is contained in:
Paulus Schoutsen 2019-12-18 22:15:02 +01:00 committed by GitHub
commit 82a5c23c9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 466 additions and 161 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -37,11 +37,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
password = config[CONF_PASSWORD] password = config[CONF_PASSWORD]
client_id = config[CONF_CLIENT_ID] client_id = config[CONF_CLIENT_ID]
client_secret = config[CONF_CLIENT_SECRET] client_secret = config[CONF_CLIENT_SECRET]
flume_token_file = hass.config.path("FLUME_TOKEN_FILE")
time_zone = str(hass.config.time_zone) time_zone = str(hass.config.time_zone)
name = config[CONF_NAME] name = config[CONF_NAME]
flume_entity_list = [] 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: for device in flume_devices.device_list:
if device["type"] == FLUME_TYPE_SENSOR: if device["type"] == FLUME_TYPE_SENSOR:
@ -53,6 +56,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device["id"], device["id"],
time_zone, time_zone,
SCAN_INTERVAL, SCAN_INTERVAL,
flume_token_file,
) )
flume_entity_list.append(FlumeSensor(flume, f"{name} {device['id']}")) 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", "documentation": "https://www.home-assistant.io/integrations/google_assistant",
"requirements": [], "requirements": [],
"dependencies": ["http"], "dependencies": ["http"],
"after_dependencies": ["camera"],
"codeowners": ["@home-assistant/cloud"] "codeowners": ["@home-assistant/cloud"]
} }

View File

@ -35,7 +35,12 @@ from homeassistant.components.light import (
Light, Light,
preprocess_turn_on_alternatives, 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 from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.device_registry as dr import homeassistant.helpers.device_registry as dr
@ -369,6 +374,9 @@ class LIFXManager:
async def async_service_to_entities(self, service): async def async_service_to_entities(self, service):
"""Return the known entities that a service call mentions.""" """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) entity_ids = await async_extract_entity_ids(self.hass, service)
return [ return [
entity entity

View File

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

View File

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

View File

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

View File

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

View File

@ -115,7 +115,7 @@ async def websocket_delete_registration(
except HomeAssistantError: except HomeAssistantError:
return error_message(msg["id"], "internal_error", "Error deleting registration") 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) await hass.components.cloud.async_delete_cloudhook(webhook_id)
connection.send_message(result_message(msg["id"], "ok")) connection.send_message(result_message(msg["id"], "ok"))

View File

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

View File

@ -118,7 +118,7 @@ async def async_unload_entry(hass, entry):
async def async_remove_entry(hass, entry): async def async_remove_entry(hass, entry):
"""Remove an OwnTracks config 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 return
await hass.components.cloud.async_delete_cloudhook(entry.data[CONF_WEBHOOK_ID]) 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): async def _get_webhook_id(self):
"""Generate webhook ID.""" """Generate webhook ID."""
webhook_id = self.hass.components.webhook.async_generate_id() webhook_id = self.hass.components.webhook.async_generate_id()
if ( if self.hass.components.cloud.async_active_subscription():
"cloud" in self.hass.config.components
and self.hass.components.cloud.async_active_subscription()
):
webhook_url = await self.hass.components.cloud.async_create_cloudhook( webhook_url = await self.hass.components.cloud.async_create_cloudhook(
webhook_id webhook_id
) )

View File

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

View File

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

View File

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

View File

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

View File

@ -3,14 +3,8 @@
"name": "Smartthings", "name": "Smartthings",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/smartthings", "documentation": "https://www.home-assistant.io/integrations/smartthings",
"requirements": [ "requirements": ["pysmartapp==0.3.2", "pysmartthings==0.6.9"],
"pysmartapp==0.3.2", "dependencies": ["webhook"],
"pysmartthings==0.6.9" "after_dependencies": ["cloud"],
], "codeowners": ["@andrewsayre"]
"dependencies": [
"webhook"
],
"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: def validate_webhook_requirements(hass: HomeAssistantType) -> bool:
"""Ensure HASS is setup properly to receive webhooks.""" """Ensure HASS is setup properly to receive webhooks."""
if ( if hass.components.cloud.async_active_subscription():
"cloud" in hass.config.components
and hass.components.cloud.async_active_subscription()
):
return True return True
if hass.data[DOMAIN][CONF_CLOUDHOOK_URL] is not None: if hass.data[DOMAIN][CONF_CLOUDHOOK_URL] is not None:
return True return True
@ -105,11 +102,7 @@ def get_webhook_url(hass: HomeAssistantType) -> str:
Return the cloudhook if available, otherwise local webhook. Return the cloudhook if available, otherwise local webhook.
""" """
cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL] cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL]
if ( if hass.components.cloud.async_active_subscription() and cloudhook_url is not None:
"cloud" in hass.config.components
and hass.components.cloud.async_active_subscription()
and cloudhook_url is not None
):
return cloudhook_url return cloudhook_url
return webhook.async_generate_url(hass, hass.data[DOMAIN][CONF_WEBHOOK_ID]) 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) cloudhook_url = config.get(CONF_CLOUDHOOK_URL)
if ( if (
cloudhook_url is None cloudhook_url is None
and "cloud" in hass.config.components
and hass.components.cloud.async_active_subscription() and hass.components.cloud.async_active_subscription()
and not hass.config_entries.async_entries(DOMAIN) and not hass.config_entries.async_entries(DOMAIN)
): ):
@ -281,11 +273,7 @@ async def unload_smartapp_endpoint(hass: HomeAssistantType):
return return
# Remove the cloudhook if it was created # Remove the cloudhook if it was created
cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL] cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL]
if ( if cloudhook_url and hass.components.cloud.async_is_logged_in():
cloudhook_url
and "cloud" in hass.config.components
and hass.components.cloud.async_is_logged_in()
):
await hass.components.cloud.async_delete_cloudhook( await hass.components.cloud.async_delete_cloudhook(
hass.data[DOMAIN][CONF_WEBHOOK_ID] hass.data[DOMAIN][CONF_WEBHOOK_ID]
) )

View File

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

View File

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

View File

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

View File

@ -288,8 +288,8 @@ async def async_process_deps_reqs(
raise HomeAssistantError("Could not set up all dependencies.") raise HomeAssistantError("Could not set up all dependencies.")
if not hass.config.skip_pip and integration.requirements: if not hass.config.skip_pip and integration.requirements:
await requirements.async_process_requirements( await requirements.async_get_integration_with_requirements(
hass, integration.domain, integration.requirements hass, integration.domain
) )
processed.add(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 aio_geojson_nsw_rfs_incidents==0.1
# homeassistant.components.ambient_station # homeassistant.components.ambient_station
aioambient==0.3.2 aioambient==1.0.2
# homeassistant.components.asuswrt # homeassistant.components.asuswrt
aioasuswrt==1.1.22 aioasuswrt==1.1.22
@ -1231,7 +1231,7 @@ pyflexit==0.3
pyflic-homeassistant==0.4.dev0 pyflic-homeassistant==0.4.dev0
# homeassistant.components.flume # homeassistant.components.flume
pyflume==0.2.1 pyflume==0.2.4
# homeassistant.components.flunearyou # homeassistant.components.flunearyou
pyflunearyou==1.0.3 pyflunearyou==1.0.3
@ -1793,7 +1793,7 @@ shodan==1.20.0
simplepush==1.1.4 simplepush==1.1.4
# homeassistant.components.simplisafe # homeassistant.components.simplisafe
simplisafe-python==5.3.5 simplisafe-python==5.3.6
# homeassistant.components.sisyphus # homeassistant.components.sisyphus
sisyphus-control==2.2.1 sisyphus-control==2.2.1

View File

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

View File

@ -43,15 +43,12 @@ def validate_dependencies(integration: Integration):
if referenced: if referenced:
for domain in sorted(referenced): for domain in sorted(referenced):
print( integration.add_error(
"Warning: {} references integration {} but it's not a " "dependencies",
"dependency".format(integration.domain, domain) "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): 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 import data_entry_flow
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.components import cloud
from homeassistant.components.smartthings import smartapp from homeassistant.components.smartthings import smartapp
from homeassistant.components.smartthings.config_flow import SmartThingsFlowHandler from homeassistant.components.smartthings.config_flow import SmartThingsFlowHandler
from homeassistant.components.smartthings.const import ( from homeassistant.components.smartthings.const import (
@ -18,7 +17,7 @@ from homeassistant.components.smartthings.const import (
DOMAIN, DOMAIN,
) )
from tests.common import MockConfigEntry from tests.common import MockConfigEntry, mock_coro
async def test_step_user(hass): 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) await smartapp.unload_smartapp_endpoint(hass)
with patch.object( with patch.object(
cloud, "async_active_subscription", return_value=True hass.components.cloud, "async_active_subscription", return_value=True
), patch.object( ), 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: ) as mock_create_cloudhook:
await smartapp.setup_smartapp_endpoint(hass) await smartapp.setup_smartapp_endpoint(hass)

View File

@ -13,6 +13,7 @@ from homeassistant.components.climate.const import (
PRESET_BOOST, PRESET_BOOST,
PRESET_ECO, PRESET_ECO,
PRESET_NONE, PRESET_NONE,
SUPPORT_AUX_HEAT,
SUPPORT_FAN_MODE, SUPPORT_FAN_MODE,
SUPPORT_PRESET_MODE, SUPPORT_PRESET_MODE,
SUPPORT_SWING_MODE, SUPPORT_SWING_MODE,
@ -21,8 +22,11 @@ from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_LOW,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_HIGH,
) )
from homeassistant.components.zwave import climate from homeassistant.components.zwave import climate, const
from homeassistant.components.zwave.climate import DEFAULT_HVAC_MODES from homeassistant.components.zwave.climate import (
AUX_HEAT_ZWAVE_MODE,
DEFAULT_HVAC_MODES,
)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed
@ -34,6 +38,7 @@ def device(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT, data=HVAC_MODE_HEAT,
data_items=[ data_items=[
HVAC_MODE_OFF, HVAC_MODE_OFF,
@ -62,6 +67,7 @@ def device_zxt_120(hass, mock_openzwave):
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT, data=HVAC_MODE_HEAT,
data_items=[ data_items=[
HVAC_MODE_OFF, HVAC_MODE_OFF,
@ -90,6 +96,7 @@ def device_mapping(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data="Heat", data="Heat",
data_items=["Off", "Cool", "Heat", "Full Power", "Auto"], data_items=["Off", "Cool", "Heat", "Full Power", "Auto"],
node=node, node=node,
@ -112,6 +119,7 @@ def device_unknown(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data="Heat", data="Heat",
data_items=["Off", "Cool", "Heat", "heat_cool", "Abcdefg"], data_items=["Off", "Cool", "Heat", "heat_cool", "Abcdefg"],
node=node, node=node,
@ -134,6 +142,7 @@ def device_heat_cool(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT, data=HVAC_MODE_HEAT,
data_items=[ data_items=[
HVAC_MODE_OFF, HVAC_MODE_OFF,
@ -162,6 +171,7 @@ def device_heat_cool_range(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT_COOL, data=HVAC_MODE_HEAT_COOL,
data_items=[ data_items=[
HVAC_MODE_OFF, HVAC_MODE_OFF,
@ -189,6 +199,7 @@ def device_heat_cool_away(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT_COOL, data=HVAC_MODE_HEAT_COOL,
data_items=[ data_items=[
HVAC_MODE_OFF, HVAC_MODE_OFF,
@ -219,6 +230,7 @@ def device_heat_eco(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT, data=HVAC_MODE_HEAT,
data_items=[HVAC_MODE_OFF, HVAC_MODE_HEAT, "heat econ"], data_items=[HVAC_MODE_OFF, HVAC_MODE_HEAT, "heat econ"],
node=node, node=node,
@ -235,6 +247,100 @@ def device_heat_eco(hass, mock_openzwave):
yield device 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(): def test_default_hvac_modes():
"""Test wether all hvac modes are included in default_hvac_modes.""" """Test wether all hvac modes are included in default_hvac_modes."""
for hvac_mode in 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): def test_supported_features_swing_mode(device_zxt_120):
"""Test supported features flags with swing mode.""" """Test supported features flags with swing mode."""
device = device_zxt_120 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): def test_zxt_120_swing_mode(device_zxt_120):
"""Test operation of the zxt 120 swing mode.""" """Test operation of the zxt 120 swing mode."""
device = device_zxt_120 device = device_zxt_120
@ -331,6 +470,22 @@ def test_data_lists(device):
assert device.preset_modes == [] 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): def test_data_lists_mapping(device_mapping):
"""Test data lists from zwave value items.""" """Test data lists from zwave value items."""
device = device_mapping device = device_mapping
@ -404,6 +559,14 @@ def test_target_value_set_eco(device_heat_eco):
assert device.values.setpoint_eco_heating.data == 0 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): def test_operation_value_set(device):
"""Test values changed for climate device.""" """Test values changed for climate device."""
assert device.values.primary.data == HVAC_MODE_HEAT 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 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): def test_temperature_value_changed(device):
"""Test values changed for climate device.""" """Test values changed for climate device."""
assert device.current_temperature == 5 assert device.current_temperature == 5
@ -677,3 +849,44 @@ def test_fan_action_value_changed(device):
device.values.fan_action.data = 9 device.values.fan_action.data = 9
value_changed(device.values.fan_action) value_changed(device.values.fan_action)
assert device.device_state_attributes[climate.ATTR_FAN_ACTION] == 9 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 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( thermostat_mode = MockValue(
data="Heat", data="Heat",
data_items=["Off", "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): async def test_power_schemes(hass, mock_openzwave):
"""Test power attribute.""" """Test power attribute."""
mock_receivers = [] mock_receivers = []