mirror of
https://github.com/home-assistant/core.git
synced 2026-01-08 00:28:31 +01:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cbb818dc3 | ||
|
|
77d984e980 | ||
|
|
e57ecc9d7d | ||
|
|
e1fee1bd45 | ||
|
|
20e279a7ac | ||
|
|
34b5083c27 | ||
|
|
ebf8d5fc66 | ||
|
|
e355012229 | ||
|
|
dffdbda8e2 | ||
|
|
a20c631410 | ||
|
|
0f8f4f4b54 | ||
|
|
609118d3ac | ||
|
|
52de2f4ffb | ||
|
|
a1302a9dfb | ||
|
|
3a78250cad | ||
|
|
d702b17a4f | ||
|
|
27cfda11f7 | ||
|
|
fee1568a85 | ||
|
|
eceef82ffa | ||
|
|
b011dd0b02 | ||
|
|
e1c23b1686 | ||
|
|
387323a8c1 | ||
|
|
6e61b21919 | ||
|
|
868c6f4f71 | ||
|
|
4e2094c893 | ||
|
|
2925f9e57a | ||
|
|
f8753a0c92 | ||
|
|
7a71669027 | ||
|
|
476607787a | ||
|
|
d95c86e964 | ||
|
|
b40d324e0e | ||
|
|
e001b12430 | ||
|
|
8d1deef9cc | ||
|
|
949875ae50 | ||
|
|
ad341e2152 | ||
|
|
d64730a3cf | ||
|
|
a8c4fc33f6 | ||
|
|
476a727df3 | ||
|
|
1d5709f49f | ||
|
|
725d5c636e | ||
|
|
414b85c253 | ||
|
|
56ca0edaa7 | ||
|
|
7168dd6cec | ||
|
|
d3f6c43bbd | ||
|
|
59b42b4236 |
@@ -13,6 +13,7 @@ LABEL maintainer="Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
|
||||
#ENV INSTALL_SSOCR no
|
||||
#ENV INSTALL_DLIB no
|
||||
#ENV INSTALL_IPERF3 no
|
||||
#ENV INSTALL_LOCALES no
|
||||
|
||||
VOLUME /config
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Cisco mobility express",
|
||||
"documentation": "https://www.home-assistant.io/components/cisco_mobility_express",
|
||||
"requirements": [
|
||||
"ciscomobilityexpress==0.3.1"
|
||||
"ciscomobilityexpress==0.3.3"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@fbradyirl"]
|
||||
|
||||
@@ -2,14 +2,7 @@
|
||||
"domain": "cloud",
|
||||
"name": "Cloud",
|
||||
"documentation": "https://www.home-assistant.io/components/cloud",
|
||||
"requirements": [
|
||||
"hass-nabucasa==0.15"
|
||||
],
|
||||
"dependencies": [
|
||||
"http",
|
||||
"webhook"
|
||||
],
|
||||
"codeowners": [
|
||||
"@home-assistant/cloud"
|
||||
]
|
||||
"requirements": ["hass-nabucasa==0.16"],
|
||||
"dependencies": ["http", "webhook"],
|
||||
"codeowners": ["@home-assistant/cloud"]
|
||||
}
|
||||
|
||||
@@ -63,12 +63,12 @@ class DeconzGateway:
|
||||
@property
|
||||
def allow_clip_sensor(self) -> bool:
|
||||
"""Allow loading clip sensor from gateway."""
|
||||
return self.config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
|
||||
return self.config_entry.options.get(CONF_ALLOW_CLIP_SENSOR, True)
|
||||
|
||||
@property
|
||||
def allow_deconz_groups(self) -> bool:
|
||||
"""Allow loading deCONZ groups from gateway."""
|
||||
return self.config_entry.data.get(CONF_ALLOW_DECONZ_GROUPS, True)
|
||||
return self.config_entry.options.get(CONF_ALLOW_DECONZ_GROUPS, True)
|
||||
|
||||
async def async_update_device_registry(self):
|
||||
"""Update device registry."""
|
||||
|
||||
@@ -97,7 +97,7 @@ class EcobeeData:
|
||||
def update(self):
|
||||
"""Get the latest data from pyecobee."""
|
||||
self.ecobee.update()
|
||||
_LOGGER.info("Ecobee data updated successfully")
|
||||
_LOGGER.debug("Ecobee data updated successfully")
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
|
||||
@@ -88,16 +88,6 @@ PRESET_TO_ECOBEE_HOLD = {
|
||||
PRESET_HOLD_INDEFINITE: "indefinite",
|
||||
}
|
||||
|
||||
PRESET_MODES = [
|
||||
PRESET_NONE,
|
||||
PRESET_AWAY,
|
||||
PRESET_TEMPERATURE,
|
||||
PRESET_HOME,
|
||||
PRESET_SLEEP,
|
||||
PRESET_HOLD_NEXT_TRANSITION,
|
||||
PRESET_HOLD_INDEFINITE,
|
||||
]
|
||||
|
||||
SERVICE_SET_FAN_MIN_ON_TIME = "ecobee_set_fan_min_on_time"
|
||||
SERVICE_RESUME_PROGRAM = "ecobee_resume_program"
|
||||
|
||||
@@ -199,7 +189,6 @@ class Thermostat(ClimateDevice):
|
||||
self._name = self.thermostat["name"]
|
||||
self.hold_temp = hold_temp
|
||||
self.vacation = None
|
||||
self._climate_list = self.climate_list
|
||||
|
||||
self._operation_list = []
|
||||
if self.thermostat["settings"]["heatStages"]:
|
||||
@@ -210,6 +199,10 @@ class Thermostat(ClimateDevice):
|
||||
self._operation_list.insert(0, HVAC_MODE_AUTO)
|
||||
self._operation_list.append(HVAC_MODE_OFF)
|
||||
|
||||
self._preset_modes = {
|
||||
comfort["climateRef"]: comfort["name"]
|
||||
for comfort in self.thermostat["program"]["climates"]
|
||||
}
|
||||
self._fan_modes = [FAN_AUTO, FAN_ON]
|
||||
self.update_without_throttle = False
|
||||
|
||||
@@ -223,6 +216,11 @@ class Thermostat(ClimateDevice):
|
||||
|
||||
self.thermostat = self.data.ecobee.get_thermostat(self.thermostat_index)
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return if device is available."""
|
||||
return self.thermostat["runtime"]["connected"]
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
@@ -294,15 +292,9 @@ class Thermostat(ClimateDevice):
|
||||
continue
|
||||
|
||||
if event["type"] == "hold":
|
||||
if event["holdClimateRef"] == "away":
|
||||
if int(event["endDate"][0:4]) - int(event["startDate"][0:4]) <= 1:
|
||||
# A temporary hold from away climate is a hold
|
||||
return PRESET_AWAY
|
||||
# A permanent hold from away climate
|
||||
return PRESET_AWAY
|
||||
if event["holdClimateRef"] != "":
|
||||
# Any other hold based on climate
|
||||
return event["holdClimateRef"]
|
||||
if event["holdClimateRef"] in self._preset_modes:
|
||||
return self._preset_modes[event["holdClimateRef"]]
|
||||
|
||||
# Any hold not based on a climate is a temp hold
|
||||
return PRESET_TEMPERATURE
|
||||
if event["type"].startswith("auto"):
|
||||
@@ -324,14 +316,6 @@ class Thermostat(ClimateDevice):
|
||||
"""Return the operation modes list."""
|
||||
return self._operation_list
|
||||
|
||||
@property
|
||||
def climate_mode(self):
|
||||
"""Return current mode, as the user-visible name."""
|
||||
cur = self.thermostat["program"]["currentClimateRef"]
|
||||
climates = self.thermostat["program"]["climates"]
|
||||
current = list(filter(lambda x: x["climateRef"] == cur, climates))
|
||||
return current[0]["name"]
|
||||
|
||||
@property
|
||||
def current_humidity(self) -> Optional[int]:
|
||||
"""Return the current humidity."""
|
||||
@@ -373,9 +357,7 @@ class Thermostat(ClimateDevice):
|
||||
status = self.thermostat["equipmentStatus"]
|
||||
return {
|
||||
"fan": self.fan,
|
||||
"climate_mode": self.climate_mode,
|
||||
"equipment_running": status,
|
||||
"climate_list": self.climate_list,
|
||||
"fan_min_on_time": self.thermostat["settings"]["fanMinOnTime"],
|
||||
}
|
||||
|
||||
@@ -413,6 +395,21 @@ class Thermostat(ClimateDevice):
|
||||
elif preset_mode == PRESET_NONE:
|
||||
self.data.ecobee.resume_program(self.thermostat_index)
|
||||
|
||||
elif preset_mode in self.preset_modes:
|
||||
climate_ref = None
|
||||
|
||||
for comfort in self.thermostat["program"]["climates"]:
|
||||
if comfort["name"] == preset_mode:
|
||||
climate_ref = comfort["climateRef"]
|
||||
break
|
||||
|
||||
if climate_ref is not None:
|
||||
self.data.ecobee.set_climate_hold(
|
||||
self.thermostat_index, climate_ref, self.hold_preference()
|
||||
)
|
||||
else:
|
||||
_LOGGER.warning("Received unknown preset mode: %s", preset_mode)
|
||||
|
||||
else:
|
||||
self.data.ecobee.set_climate_hold(
|
||||
self.thermostat_index, preset_mode, self.hold_preference()
|
||||
@@ -421,7 +418,7 @@ class Thermostat(ClimateDevice):
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Return available preset modes."""
|
||||
return PRESET_MODES
|
||||
return list(self._preset_modes.values())
|
||||
|
||||
def set_auto_temp_hold(self, heat_temp, cool_temp):
|
||||
"""Set temperature hold in auto mode."""
|
||||
@@ -543,9 +540,3 @@ class Thermostat(ClimateDevice):
|
||||
# supported; note that this should not include 'indefinite'
|
||||
# as an indefinite away hold is interpreted as away_mode
|
||||
return "nextTransition"
|
||||
|
||||
@property
|
||||
def climate_list(self):
|
||||
"""Return the list of climates currently available."""
|
||||
climates = self.thermostat["program"]["climates"]
|
||||
return list(map((lambda x: x["name"]), climates))
|
||||
|
||||
@@ -562,25 +562,15 @@ def get_entity_state(config, entity):
|
||||
|
||||
def entity_to_json(config, entity, state):
|
||||
"""Convert an entity to its Hue bridge JSON representation."""
|
||||
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if entity_features & SUPPORT_BRIGHTNESS:
|
||||
return {
|
||||
"state": {
|
||||
HUE_API_STATE_ON: state[STATE_ON],
|
||||
HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
|
||||
HUE_API_STATE_HUE: state[STATE_HUE],
|
||||
HUE_API_STATE_SAT: state[STATE_SATURATION],
|
||||
"reachable": True,
|
||||
},
|
||||
"type": "Dimmable light",
|
||||
"name": config.get_entity_name(entity),
|
||||
"modelid": "HASS123",
|
||||
"uniqueid": entity.entity_id,
|
||||
"swversion": "123",
|
||||
}
|
||||
return {
|
||||
"state": {HUE_API_STATE_ON: state[STATE_ON], "reachable": True},
|
||||
"type": "On/off light",
|
||||
"state": {
|
||||
HUE_API_STATE_ON: state[STATE_ON],
|
||||
HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
|
||||
HUE_API_STATE_HUE: state[STATE_HUE],
|
||||
HUE_API_STATE_SAT: state[STATE_SATURATION],
|
||||
"reachable": True,
|
||||
},
|
||||
"type": "Dimmable light",
|
||||
"name": config.get_entity_name(entity),
|
||||
"modelid": "HASS123",
|
||||
"uniqueid": entity.entity_id,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Enphase envoy",
|
||||
"documentation": "https://www.home-assistant.io/components/enphase_envoy",
|
||||
"requirements": [
|
||||
"envoy_reader==0.8"
|
||||
"envoy_reader==0.8.6"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
import logging
|
||||
import socket
|
||||
import random
|
||||
from asyncio import sleep
|
||||
from functools import partial
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -13,14 +11,12 @@ from homeassistant.components.light import (
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_EFFECT,
|
||||
ATTR_WHITE_VALUE,
|
||||
ATTR_COLOR_TEMP,
|
||||
EFFECT_COLORLOOP,
|
||||
EFFECT_RANDOM,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_EFFECT,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_WHITE_VALUE,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
Light,
|
||||
PLATFORM_SCHEMA,
|
||||
)
|
||||
@@ -38,9 +34,7 @@ ATTR_MODE = "mode"
|
||||
|
||||
DOMAIN = "flux_led"
|
||||
|
||||
SUPPORT_FLUX_LED = (
|
||||
SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | SUPPORT_COLOR | SUPPORT_COLOR_TEMP
|
||||
)
|
||||
SUPPORT_FLUX_LED = SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | SUPPORT_COLOR
|
||||
|
||||
MODE_RGB = "rgb"
|
||||
MODE_RGBW = "rgbw"
|
||||
@@ -49,11 +43,6 @@ MODE_RGBW = "rgbw"
|
||||
# RGB value is ignored when this mode is specified.
|
||||
MODE_WHITE = "w"
|
||||
|
||||
# Constant color temp values for 2 flux_led special modes
|
||||
# Warm-white and Cool-white. Details on #23704
|
||||
COLOR_TEMP_WARM_WHITE = 333
|
||||
COLOR_TEMP_COOL_WHITE = 250
|
||||
|
||||
# List of supported effects which aren't already declared in LIGHT
|
||||
EFFECT_RED_FADE = "red_fade"
|
||||
EFFECT_GREEN_FADE = "green_fade"
|
||||
@@ -196,8 +185,6 @@ class FluxLight(Light):
|
||||
self._custom_effect = device[CONF_CUSTOM_EFFECT]
|
||||
self._bulb = None
|
||||
self._error_reported = False
|
||||
self._color = (0, 0, 100)
|
||||
self._white_value = 0
|
||||
|
||||
def _connect(self):
|
||||
"""Connect to Flux light."""
|
||||
@@ -238,14 +225,14 @@ class FluxLight(Light):
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
if self._mode == MODE_WHITE:
|
||||
return self._white_value
|
||||
return self.white_value
|
||||
|
||||
return int(self._color[2] / 100 * 255)
|
||||
return self._bulb.brightness
|
||||
|
||||
@property
|
||||
def hs_color(self):
|
||||
"""Return the color property."""
|
||||
return self._color[0:2]
|
||||
return color_util.color_RGB_to_hs(*self._bulb.getRgb())
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
@@ -261,7 +248,7 @@ class FluxLight(Light):
|
||||
@property
|
||||
def white_value(self):
|
||||
"""Return the white value of this light between 0..255."""
|
||||
return self._white_value
|
||||
return self._bulb.getRgbw()[3]
|
||||
|
||||
@property
|
||||
def effect_list(self):
|
||||
@@ -282,85 +269,75 @@ class FluxLight(Light):
|
||||
for effect, code in EFFECT_MAP.items():
|
||||
if current_mode == code:
|
||||
return effect
|
||||
|
||||
return None
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the specified or all lights on and wait for state."""
|
||||
await self.hass.async_add_executor_job(partial(self._turn_on, **kwargs))
|
||||
# The bulb needs a bit to tell its new values,
|
||||
# so we wait 1 second before updating
|
||||
await sleep(1)
|
||||
|
||||
def _turn_on(self, **kwargs):
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the specified or all lights on."""
|
||||
self._bulb.turnOn()
|
||||
if not self.is_on:
|
||||
self._bulb.turnOn()
|
||||
|
||||
hs_color = kwargs.get(ATTR_HS_COLOR)
|
||||
|
||||
if hs_color:
|
||||
rgb = color_util.color_hs_to_RGB(*hs_color)
|
||||
else:
|
||||
rgb = None
|
||||
|
||||
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
||||
effect = kwargs.get(ATTR_EFFECT)
|
||||
white = kwargs.get(ATTR_WHITE_VALUE)
|
||||
color_temp = kwargs.get(ATTR_COLOR_TEMP)
|
||||
|
||||
if all(
|
||||
item is None for item in [hs_color, brightness, effect, white, color_temp]
|
||||
):
|
||||
# Show warning if effect set with rgb, brightness, or white level
|
||||
if effect and (brightness or white or rgb):
|
||||
_LOGGER.warning(
|
||||
"RGB, brightness and white level are ignored when"
|
||||
" an effect is specified for a flux bulb"
|
||||
)
|
||||
|
||||
# Random color effect
|
||||
if effect == EFFECT_RANDOM:
|
||||
self._bulb.setRgb(
|
||||
random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
|
||||
)
|
||||
return
|
||||
|
||||
# handle W only mode (use brightness instead of white value)
|
||||
if self._mode == MODE_WHITE:
|
||||
if brightness is not None:
|
||||
self._bulb.setWarmWhite255(brightness)
|
||||
return
|
||||
|
||||
# handle effects
|
||||
if effect is not None:
|
||||
# Random color effect
|
||||
if effect == EFFECT_RANDOM:
|
||||
self._bulb.setRgb(
|
||||
random.randint(0, 255),
|
||||
random.randint(0, 255),
|
||||
random.randint(0, 255),
|
||||
if effect == EFFECT_CUSTOM:
|
||||
if self._custom_effect:
|
||||
self._bulb.setCustomPattern(
|
||||
self._custom_effect[CONF_COLORS],
|
||||
self._custom_effect[CONF_SPEED_PCT],
|
||||
self._custom_effect[CONF_TRANSITION],
|
||||
)
|
||||
elif effect == EFFECT_CUSTOM:
|
||||
if self._custom_effect:
|
||||
self._bulb.setCustomPattern(
|
||||
self._custom_effect[CONF_COLORS],
|
||||
self._custom_effect[CONF_SPEED_PCT],
|
||||
self._custom_effect[CONF_TRANSITION],
|
||||
)
|
||||
# Effect selection
|
||||
elif effect in EFFECT_MAP:
|
||||
self._bulb.setPresetPattern(EFFECT_MAP[effect], 50)
|
||||
return
|
||||
|
||||
# handle special modes
|
||||
if color_temp is not None:
|
||||
if brightness is None:
|
||||
brightness = self.brightness
|
||||
if color_temp == COLOR_TEMP_WARM_WHITE:
|
||||
self._bulb.setRgbw(w=brightness)
|
||||
elif color_temp == COLOR_TEMP_COOL_WHITE:
|
||||
self._bulb.setRgbw(w2=brightness)
|
||||
else:
|
||||
self._bulb.setRgbw(*color_util.color_temperature_to_rgb(color_temp))
|
||||
# Effect selection
|
||||
if effect in EFFECT_MAP:
|
||||
self._bulb.setPresetPattern(EFFECT_MAP[effect], 50)
|
||||
return
|
||||
|
||||
# Preserve current brightness on color/white level change
|
||||
if hs_color is not None:
|
||||
if brightness is None:
|
||||
brightness = self.brightness
|
||||
color = (hs_color[0], hs_color[1], brightness / 255 * 100)
|
||||
elif brightness is not None:
|
||||
color = (self._color[0], self._color[1], brightness / 255 * 100)
|
||||
if brightness is None:
|
||||
brightness = self.brightness
|
||||
|
||||
# Preserve color on brightness/white level change
|
||||
if rgb is None:
|
||||
rgb = self._bulb.getRgb()
|
||||
|
||||
if white is None and self._mode == MODE_RGBW:
|
||||
white = self.white_value
|
||||
|
||||
# handle W only mode (use brightness instead of white value)
|
||||
if self._mode == MODE_WHITE:
|
||||
self._bulb.setRgbw(0, 0, 0, w=brightness)
|
||||
|
||||
# handle RGBW mode
|
||||
if self._mode == MODE_RGBW:
|
||||
if white is None:
|
||||
self._bulb.setRgbw(*color_util.color_hsv_to_RGB(*color))
|
||||
else:
|
||||
self._bulb.setRgbw(w=white)
|
||||
elif self._mode == MODE_RGBW:
|
||||
self._bulb.setRgbw(*tuple(rgb), w=white, brightness=brightness)
|
||||
|
||||
# handle RGB mode
|
||||
else:
|
||||
self._bulb.setRgb(*color_util.color_hsv_to_RGB(*color))
|
||||
self._bulb.setRgb(*tuple(rgb), brightness=brightness)
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the specified or all lights off."""
|
||||
@@ -380,10 +357,5 @@ class FluxLight(Light):
|
||||
)
|
||||
self._error_reported = True
|
||||
return
|
||||
|
||||
self._bulb.update_state(retry=2)
|
||||
if self._mode != MODE_WHITE and self._bulb.getRgb() != (0, 0, 0):
|
||||
color = self._bulb.getRgbw()
|
||||
self._color = color_util.color_RGB_to_hsv(*color[0:3])
|
||||
self._white_value = color[3]
|
||||
elif self._mode == MODE_WHITE:
|
||||
self._white_value = self._bulb.getRgbw()[3]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Home Assistant Frontend",
|
||||
"documentation": "https://www.home-assistant.io/components/frontend",
|
||||
"requirements": [
|
||||
"home-assistant-frontend==20190731.0"
|
||||
"home-assistant-frontend==20190805.0"
|
||||
],
|
||||
"dependencies": [
|
||||
"api",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"domain": "haveibeenpwned",
|
||||
"name": "Haveibeenpwned",
|
||||
"documentation": "https://www.home-assistant.io/components/haveibeenpwned",
|
||||
"requirements": [],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
"domain": "haveibeenpwned",
|
||||
"name": "Haveibeenpwned",
|
||||
"documentation": "https://www.home-assistant.io/components/haveibeenpwned",
|
||||
"requirements": [],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import requests
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import CONF_EMAIL, ATTR_ATTRIBUTION
|
||||
from homeassistant.const import CONF_EMAIL, CONF_API_KEY, ATTR_ATTRIBUTION
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.event import track_point_in_time
|
||||
@@ -25,17 +25,21 @@ HA_USER_AGENT = "Home Assistant HaveIBeenPwned Sensor Component"
|
||||
MIN_TIME_BETWEEN_FORCED_UPDATES = timedelta(seconds=5)
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
|
||||
|
||||
URL = "https://haveibeenpwned.com/api/v2/breachedaccount/"
|
||||
URL = "https://haveibeenpwned.com/api/v3/breachedaccount/"
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{vol.Required(CONF_EMAIL): vol.All(cv.ensure_list, [cv.string])}
|
||||
{
|
||||
vol.Required(CONF_EMAIL): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Required(CONF_API_KEY): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the HaveIBeenPwned sensor."""
|
||||
emails = config.get(CONF_EMAIL)
|
||||
data = HaveIBeenPwnedData(emails)
|
||||
api_key = config[CONF_API_KEY]
|
||||
data = HaveIBeenPwnedData(emails, api_key)
|
||||
|
||||
devices = []
|
||||
for email in emails:
|
||||
@@ -125,13 +129,14 @@ class HaveIBeenPwnedSensor(Entity):
|
||||
class HaveIBeenPwnedData:
|
||||
"""Class for handling the data retrieval."""
|
||||
|
||||
def __init__(self, emails):
|
||||
def __init__(self, emails, api_key):
|
||||
"""Initialize the data object."""
|
||||
self._email_count = len(emails)
|
||||
self._current_index = 0
|
||||
self.data = {}
|
||||
self._email = emails[0]
|
||||
self._emails = emails
|
||||
self._api_key = api_key
|
||||
|
||||
def set_next_email(self):
|
||||
"""Set the next email to be looked up."""
|
||||
@@ -146,16 +151,10 @@ class HaveIBeenPwnedData:
|
||||
def update(self, **kwargs):
|
||||
"""Get the latest data for current email from REST service."""
|
||||
try:
|
||||
url = "{}{}".format(URL, self._email)
|
||||
|
||||
url = "{}{}?truncateResponse=false".format(URL, self._email)
|
||||
header = {USER_AGENT: HA_USER_AGENT, "hibp-api-key": self._api_key}
|
||||
_LOGGER.debug("Checking for breaches for email: %s", self._email)
|
||||
|
||||
req = requests.get(
|
||||
url,
|
||||
headers={USER_AGENT: HA_USER_AGENT},
|
||||
allow_redirects=True,
|
||||
timeout=5,
|
||||
)
|
||||
req = requests.get(url, headers=header, allow_redirects=True, timeout=5)
|
||||
|
||||
except requests.exceptions.RequestException:
|
||||
_LOGGER.error("Failed fetching data for %s", self._email)
|
||||
|
||||
@@ -234,6 +234,7 @@ def states_to_json(
|
||||
axis correctly.
|
||||
"""
|
||||
result = defaultdict(list)
|
||||
# Set all entity IDs to empty lists in result set to maintain the order
|
||||
if entity_ids is not None:
|
||||
for ent_id in entity_ids:
|
||||
result[ent_id] = []
|
||||
@@ -253,7 +254,9 @@ def states_to_json(
|
||||
# Append all changes to it
|
||||
for ent_id, group in groupby(states, lambda state: state.entity_id):
|
||||
result[ent_id].extend(group)
|
||||
return result
|
||||
|
||||
# Filter out the empty lists if some states had 0 results.
|
||||
return {key: val for key, val in result.items() if val}
|
||||
|
||||
|
||||
def get_state(hass, utc_point_in_time, entity_id, run=None):
|
||||
@@ -348,7 +351,6 @@ class HistoryPeriodView(HomeAssistantView):
|
||||
|
||||
# Optionally reorder the result to respect the ordering given
|
||||
# by any entities explicitly included in the configuration.
|
||||
|
||||
if self.use_include_order:
|
||||
sorted_result = []
|
||||
for order_entity in self.filters.included_entities:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Allow users to set and activate scenes."""
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -11,12 +12,19 @@ from homeassistant.const import (
|
||||
CONF_PLATFORM,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
SERVICE_RELOAD,
|
||||
)
|
||||
from homeassistant.core import State, DOMAIN
|
||||
from homeassistant import config as conf_util
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.loader import async_get_integration
|
||||
from homeassistant.helpers import (
|
||||
config_per_platform,
|
||||
config_validation as cv,
|
||||
entity_platform,
|
||||
)
|
||||
from homeassistant.core import State
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.state import HASS_DOMAIN, async_reproduce_state
|
||||
from homeassistant.components.scene import STATES, Scene
|
||||
|
||||
from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, STATES, Scene
|
||||
|
||||
PLATFORM_SCHEMA = vol.Schema(
|
||||
{
|
||||
@@ -37,19 +45,63 @@ PLATFORM_SCHEMA = vol.Schema(
|
||||
)
|
||||
|
||||
SCENECONFIG = namedtuple("SceneConfig", [CONF_NAME, STATES])
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up home assistant scene entries."""
|
||||
scene_config = config.get(STATES)
|
||||
_process_scenes_config(hass, async_add_entities, config)
|
||||
|
||||
# This platform can be loaded multiple times. Only first time register the service.
|
||||
if hass.services.has_service(SCENE_DOMAIN, SERVICE_RELOAD):
|
||||
return
|
||||
|
||||
# Store platform for later.
|
||||
platform = entity_platform.current_platform.get()
|
||||
|
||||
async def reload_config(call):
|
||||
"""Reload the scene config."""
|
||||
try:
|
||||
conf = await conf_util.async_hass_config_yaml(hass)
|
||||
except HomeAssistantError as err:
|
||||
_LOGGER.error(err)
|
||||
return
|
||||
|
||||
integration = await async_get_integration(hass, SCENE_DOMAIN)
|
||||
|
||||
conf = await conf_util.async_process_component_config(hass, conf, integration)
|
||||
|
||||
if not conf or not platform:
|
||||
return
|
||||
|
||||
await platform.async_reset()
|
||||
|
||||
# Extract only the config for the Home Assistant platform, ignore the rest.
|
||||
for p_type, p_config in config_per_platform(conf, SCENE_DOMAIN):
|
||||
if p_type != DOMAIN:
|
||||
continue
|
||||
|
||||
_process_scenes_config(hass, async_add_entities, p_config)
|
||||
|
||||
hass.helpers.service.async_register_admin_service(
|
||||
SCENE_DOMAIN, SERVICE_RELOAD, reload_config
|
||||
)
|
||||
|
||||
|
||||
def _process_scenes_config(hass, async_add_entities, config):
|
||||
"""Process multiple scenes and add them."""
|
||||
scene_config = config[STATES]
|
||||
|
||||
# Check empty list
|
||||
if not scene_config:
|
||||
return
|
||||
|
||||
async_add_entities(
|
||||
HomeAssistantScene(hass, _process_config(scene)) for scene in scene_config
|
||||
HomeAssistantScene(hass, _process_scene_config(scene)) for scene in scene_config
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def _process_config(scene_config):
|
||||
def _process_scene_config(scene_config):
|
||||
"""Process passed in config into a format to work with.
|
||||
|
||||
Async friendly.
|
||||
|
||||
@@ -120,13 +120,21 @@ class HomeKitEntity(Entity):
|
||||
"""Collect new data from bridge and update the entity state in hass."""
|
||||
accessory_state = self._accessory.current_state.get(self._aid, {})
|
||||
for iid, result in accessory_state.items():
|
||||
# No value so dont process this result
|
||||
if "value" not in result:
|
||||
continue
|
||||
|
||||
# Unknown iid - this is probably for a sibling service that is part
|
||||
# of the same physical accessory. Ignore it.
|
||||
if iid not in self._char_names:
|
||||
continue
|
||||
|
||||
# Callback to update the entity with this characteristic value
|
||||
char_name = escape_characteristic_name(self._char_names[iid])
|
||||
update_fn = getattr(self, "_update_{}".format(char_name), None)
|
||||
if not update_fn:
|
||||
continue
|
||||
|
||||
# pylint: disable=not-callable
|
||||
update_fn(result["value"])
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/components/homekit_controller",
|
||||
"requirements": [
|
||||
"homekit[IP]==0.14.0"
|
||||
"homekit[IP]==0.15.0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"zeroconf": ["_hap._tcp.local."],
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import logging
|
||||
|
||||
from homematicip.aio.device import (
|
||||
AsyncContactInterface,
|
||||
AsyncDevice,
|
||||
AsyncFullFlushContactInterface,
|
||||
AsyncMotionDetectorIndoor,
|
||||
@@ -10,6 +11,7 @@ from homematicip.aio.device import (
|
||||
AsyncPresenceDetectorIndoor,
|
||||
AsyncRotaryHandleSensor,
|
||||
AsyncShutterContact,
|
||||
AsyncShutterContactMagnetic,
|
||||
AsyncSmokeDetector,
|
||||
AsyncWaterSensor,
|
||||
AsyncWeatherSensor,
|
||||
@@ -63,9 +65,12 @@ async def async_setup_entry(
|
||||
home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home
|
||||
devices = []
|
||||
for device in home.devices:
|
||||
if isinstance(device, AsyncFullFlushContactInterface):
|
||||
if isinstance(device, (AsyncContactInterface, AsyncFullFlushContactInterface)):
|
||||
devices.append(HomematicipContactInterface(home, device))
|
||||
if isinstance(device, (AsyncShutterContact, AsyncRotaryHandleSensor)):
|
||||
if isinstance(
|
||||
device,
|
||||
(AsyncShutterContact, AsyncShutterContactMagnetic, AsyncRotaryHandleSensor),
|
||||
):
|
||||
devices.append(HomematicipShutterContact(home, device))
|
||||
if isinstance(
|
||||
device,
|
||||
|
||||
@@ -5,16 +5,19 @@ from typing import Awaitable
|
||||
from homematicip.aio.device import AsyncHeatingThermostat, AsyncHeatingThermostatCompact
|
||||
from homematicip.aio.group import AsyncHeatingGroup
|
||||
from homematicip.aio.home import AsyncHome
|
||||
from homematicip.base.enums import AbsenceType
|
||||
from homematicip.functionalHomes import IndoorClimateHome
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
HVAC_MODE_AUTO,
|
||||
HVAC_MODE_HEAT,
|
||||
PRESET_AWAY,
|
||||
PRESET_BOOST,
|
||||
PRESET_ECO,
|
||||
PRESET_NONE,
|
||||
SUPPORT_PRESET_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
PRESET_NONE,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
@@ -116,9 +119,17 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
|
||||
if self._device.boostMode:
|
||||
return PRESET_BOOST
|
||||
if self._device.controlMode == HMIP_ECO_CM:
|
||||
return PRESET_ECO
|
||||
absence_type = self._home.get_functionalHome(IndoorClimateHome).absenceType
|
||||
if absence_type == AbsenceType.VACATION:
|
||||
return PRESET_AWAY
|
||||
if absence_type in [
|
||||
AbsenceType.PERIOD,
|
||||
AbsenceType.PERMANENT,
|
||||
AbsenceType.PARTY,
|
||||
]:
|
||||
return PRESET_ECO
|
||||
|
||||
return None
|
||||
return PRESET_NONE
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
|
||||
@@ -110,10 +110,13 @@ class HomematicipHAP:
|
||||
|
||||
Triggered when the HMIP HOME_CHANGED event has fired.
|
||||
There are several occasions for this event to happen.
|
||||
We are only interested to check whether the access point
|
||||
1. We are interested to check whether the access point
|
||||
is still connected. If not, device state changes cannot
|
||||
be forwarded to hass. So if access point is disconnected all devices
|
||||
are set to unavailable.
|
||||
2. We need to update home including devices and groups after a reconnect.
|
||||
3. We need to update home without devices and groups in all other cases.
|
||||
|
||||
"""
|
||||
if not self.home.connected:
|
||||
_LOGGER.error("HMIP access point has lost connection with the cloud")
|
||||
@@ -127,6 +130,12 @@ class HomematicipHAP:
|
||||
|
||||
job = self.hass.async_create_task(self.get_state())
|
||||
job.add_done_callback(self.get_state_finished)
|
||||
self._accesspoint_connected = True
|
||||
else:
|
||||
# Update home with the given json from arg[0],
|
||||
# without devices and groups.
|
||||
|
||||
self.home.update_home_only(args[0])
|
||||
|
||||
async def get_state(self):
|
||||
"""Update HMIP state and tell Home Assistant."""
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/components/homematicip_cloud",
|
||||
"requirements": [
|
||||
"homematicip==0.10.9"
|
||||
"homematicip==0.10.10"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
|
||||
@@ -51,6 +51,8 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_SERVER_HOST = "0.0.0.0"
|
||||
DEFAULT_DEVELOPMENT = "0"
|
||||
# To be able to load custom cards.
|
||||
DEFAULT_CORS = "https://cast.home-assistant.io"
|
||||
NO_LOGIN_ATTEMPT_THRESHOLD = -1
|
||||
|
||||
|
||||
@@ -91,7 +93,7 @@ HTTP_SCHEMA = vol.Schema(
|
||||
vol.Optional(CONF_SSL_CERTIFICATE): cv.isfile,
|
||||
vol.Optional(CONF_SSL_PEER_CERTIFICATE): cv.isfile,
|
||||
vol.Optional(CONF_SSL_KEY): cv.isfile,
|
||||
vol.Optional(CONF_CORS_ORIGINS, default=[]): vol.All(
|
||||
vol.Optional(CONF_CORS_ORIGINS, default=[DEFAULT_CORS]): vol.All(
|
||||
cv.ensure_list, [cv.string]
|
||||
),
|
||||
vol.Inclusive(CONF_USE_X_FORWARDED_FOR, "proxy"): cv.boolean,
|
||||
|
||||
@@ -45,6 +45,9 @@ def setup_cors(app, origins):
|
||||
|
||||
path = path.canonical
|
||||
|
||||
if path.startswith("/api/hassio_ingress/"):
|
||||
return
|
||||
|
||||
if path in cors_added:
|
||||
return
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Intergas InComfort/Intouch Lan2RF gateway",
|
||||
"documentation": "https://www.home-assistant.io/components/incomfort",
|
||||
"requirements": [
|
||||
"incomfort-client==0.3.0"
|
||||
"incomfort-client==0.3.1"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Jewish calendar",
|
||||
"documentation": "https://www.home-assistant.io/components/jewish_calendar",
|
||||
"requirements": [
|
||||
"hdate==0.8.8"
|
||||
"hdate==0.9.0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
||||
@@ -113,13 +113,17 @@ def setup(hass, config):
|
||||
# If weather alert monitoring is expected initiate a client to be used by
|
||||
# all weather_alert entities.
|
||||
if need_weather_alert_watcher:
|
||||
_LOGGER.debug("Weather Alert monitoring expected. Loading vigilancemeteo")
|
||||
from vigilancemeteo import VigilanceMeteoFranceProxy, VigilanceMeteoError
|
||||
|
||||
weather_alert_client = VigilanceMeteoFranceProxy()
|
||||
try:
|
||||
weather_alert_client.update_data()
|
||||
except VigilanceMeteoError as exp:
|
||||
_LOGGER.error(exp)
|
||||
_LOGGER.error(
|
||||
"Unexpected error when creating the" "vigilance_meteoFrance proxy: %s ",
|
||||
exp,
|
||||
)
|
||||
else:
|
||||
weather_alert_client = None
|
||||
hass.data[DATA_METEO_FRANCE]["weather_alert_client"] = weather_alert_client
|
||||
@@ -133,7 +137,9 @@ def setup(hass, config):
|
||||
try:
|
||||
client = meteofranceClient(city)
|
||||
except meteofranceError as exp:
|
||||
_LOGGER.error(exp)
|
||||
_LOGGER.error(
|
||||
"Unexpected error when creating the meteofrance proxy: %s", exp
|
||||
)
|
||||
return
|
||||
|
||||
client.need_rain_forecast = bool(
|
||||
@@ -179,4 +185,6 @@ class MeteoFranceUpdater:
|
||||
try:
|
||||
self._client.update()
|
||||
except meteofranceError as exp:
|
||||
_LOGGER.error(exp)
|
||||
_LOGGER.error(
|
||||
"Unexpected error when updating the meteofrance proxy: %s", exp
|
||||
)
|
||||
|
||||
@@ -35,18 +35,22 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
datas["dept"], weather_alert_client
|
||||
)
|
||||
except ValueError as exp:
|
||||
_LOGGER.error(exp)
|
||||
_LOGGER.error(
|
||||
"Unexpected error when creating the weather alert sensor for %s in department %s: %s",
|
||||
city,
|
||||
datas["dept"],
|
||||
exp,
|
||||
)
|
||||
alert_watcher = None
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"weather alert watcher added for %s" "in department %s",
|
||||
"Weather alert watcher added for %s" "in department %s",
|
||||
city,
|
||||
datas["dept"],
|
||||
)
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"No dept key found for '%s'. So weather alert "
|
||||
"information won't be available",
|
||||
"No 'dept' key found for '%s'. So weather alert information won't be available",
|
||||
city,
|
||||
)
|
||||
# Exit and don't create the sensor if no department code available.
|
||||
|
||||
@@ -61,7 +61,9 @@ ACTION_NEST_TO_HASS = {
|
||||
"cooling": CURRENT_HVAC_COOL,
|
||||
}
|
||||
|
||||
PRESET_MODES = [PRESET_NONE, PRESET_AWAY, PRESET_ECO]
|
||||
PRESET_AWAY_AND_ECO = "Away and Eco"
|
||||
|
||||
PRESET_MODES = [PRESET_NONE, PRESET_AWAY, PRESET_ECO, PRESET_AWAY_AND_ECO]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
@@ -259,6 +261,9 @@ class NestThermostat(ClimateDevice):
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return current preset mode."""
|
||||
if self._away and self._mode == NEST_MODE_ECO:
|
||||
return PRESET_AWAY_AND_ECO
|
||||
|
||||
if self._away:
|
||||
return PRESET_AWAY
|
||||
|
||||
@@ -277,15 +282,19 @@ class NestThermostat(ClimateDevice):
|
||||
if preset_mode == self.preset_mode:
|
||||
return
|
||||
|
||||
if self._away:
|
||||
self.structure.away = False
|
||||
elif preset_mode == PRESET_AWAY:
|
||||
self.structure.away = True
|
||||
need_away = preset_mode in (PRESET_AWAY, PRESET_AWAY_AND_ECO)
|
||||
need_eco = preset_mode in (PRESET_ECO, PRESET_AWAY_AND_ECO)
|
||||
is_away = self._away
|
||||
is_eco = self._mode == NEST_MODE_ECO
|
||||
|
||||
if self.preset_mode == PRESET_ECO:
|
||||
self.device.mode = MODE_HASS_TO_NEST[self._operation_list[0]]
|
||||
elif preset_mode == PRESET_ECO:
|
||||
self.device.mode = NEST_MODE_ECO
|
||||
if is_away != need_away:
|
||||
self.structure.away = need_away
|
||||
|
||||
if is_eco != need_eco:
|
||||
if need_eco:
|
||||
self.device.mode = NEST_MODE_ECO
|
||||
else:
|
||||
self.device.mode = MODE_HASS_TO_NEST[self._operation_list[0]]
|
||||
|
||||
@property
|
||||
def fan_mode(self):
|
||||
|
||||
@@ -235,7 +235,7 @@ class NotionEntity(Entity):
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device registry information for this entity."""
|
||||
bridge = self._notion.bridges[self._bridge_id]
|
||||
bridge = self._notion.bridges.get(self._bridge_id, {})
|
||||
sensor = self._notion.sensors[self._sensor_id]
|
||||
|
||||
return {
|
||||
@@ -244,7 +244,7 @@ class NotionEntity(Entity):
|
||||
"model": sensor["hardware_revision"],
|
||||
"name": sensor["name"],
|
||||
"sw_version": sensor["firmware_version"],
|
||||
"via_device": (DOMAIN, bridge["hardware_id"]),
|
||||
"via_device": (DOMAIN, bridge.get("hardware_id")),
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -271,7 +271,14 @@ class NotionEntity(Entity):
|
||||
Sensors can move to other bridges based on signal strength, etc.
|
||||
"""
|
||||
sensor = self._notion.sensors[self._sensor_id]
|
||||
if self._bridge_id == sensor["bridge"]["id"]:
|
||||
|
||||
# If the sensor's bridge ID is the same as what we had before or if it points
|
||||
# to a bridge that doesn't exist (which can happen due to a Notion API bug),
|
||||
# return immediately:
|
||||
if (
|
||||
self._bridge_id == sensor["bridge"]["id"]
|
||||
or sensor["bridge"]["id"] not in self._notion.bridges
|
||||
):
|
||||
return
|
||||
|
||||
self._bridge_id = sensor["bridge"]["id"]
|
||||
|
||||
@@ -111,14 +111,14 @@ class RejseplanenTransportSensor(Entity):
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
if not self._times:
|
||||
return None
|
||||
return {ATTR_STOP_ID: self._stop_id, ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||
|
||||
next_up = []
|
||||
if len(self._times) > 1:
|
||||
next_up = self._times[1:]
|
||||
|
||||
params = {
|
||||
ATTR_DUE_IN: str(self._times[0][ATTR_DUE_IN]),
|
||||
return {
|
||||
ATTR_DUE_IN: self._times[0][ATTR_DUE_IN],
|
||||
ATTR_DUE_AT: self._times[0][ATTR_DUE_AT],
|
||||
ATTR_TYPE: self._times[0][ATTR_TYPE],
|
||||
ATTR_ROUTE: self._times[0][ATTR_ROUTE],
|
||||
@@ -128,7 +128,6 @@ class RejseplanenTransportSensor(Entity):
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
ATTR_NEXT_UP: next_up,
|
||||
}
|
||||
return {k: v for k, v in params.items() if v}
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
@@ -144,10 +143,14 @@ class RejseplanenTransportSensor(Entity):
|
||||
"""Get the latest data from rejseplanen.dk and update the states."""
|
||||
self.data.update()
|
||||
self._times = self.data.info
|
||||
try:
|
||||
self._state = self._times[0][ATTR_DUE_IN]
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if not self._times:
|
||||
self._state = None
|
||||
else:
|
||||
try:
|
||||
self._state = self._times[0][ATTR_DUE_IN]
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
|
||||
class PublicTransportData:
|
||||
@@ -159,20 +162,7 @@ class PublicTransportData:
|
||||
self.route = route
|
||||
self.direction = direction
|
||||
self.departure_type = departure_type
|
||||
self.info = self.empty_result()
|
||||
|
||||
def empty_result(self):
|
||||
"""Object returned when no departures are found."""
|
||||
return [
|
||||
{
|
||||
ATTR_DUE_IN: "n/a",
|
||||
ATTR_DUE_AT: "n/a",
|
||||
ATTR_TYPE: "n/a",
|
||||
ATTR_ROUTE: self.route,
|
||||
ATTR_DIRECTION: "n/a",
|
||||
ATTR_STOP_NAME: "n/a",
|
||||
}
|
||||
]
|
||||
self.info = []
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data from rejseplanen."""
|
||||
@@ -200,11 +190,9 @@ class PublicTransportData:
|
||||
)
|
||||
except rjpl.rjplAPIError as error:
|
||||
_LOGGER.debug("API returned error: %s", error)
|
||||
self.info = self.empty_result()
|
||||
return
|
||||
except (rjpl.rjplConnectionError, rjpl.rjplHTTPError):
|
||||
_LOGGER.debug("Error occured while connecting to the API")
|
||||
self.info = self.empty_result()
|
||||
return
|
||||
|
||||
# Filter result
|
||||
@@ -246,7 +234,6 @@ class PublicTransportData:
|
||||
|
||||
if not self.info:
|
||||
_LOGGER.debug("No departures with given parameters")
|
||||
self.info = self.empty_result()
|
||||
|
||||
# Sort the data by time
|
||||
self.info = sorted(self.info, key=itemgetter(ATTR_DUE_IN))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Roku",
|
||||
"documentation": "https://www.home-assistant.io/components/roku",
|
||||
"requirements": [
|
||||
"roku==3.0.0"
|
||||
"roku==3.1"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
|
||||
@@ -5,6 +5,7 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import DOMAIN as HA_DOMAIN
|
||||
from homeassistant.const import CONF_PLATFORM, SERVICE_TURN_ON
|
||||
from homeassistant.helpers.config_validation import ENTITY_SERVICE_SCHEMA
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@@ -60,6 +61,10 @@ async def async_setup(hass, config):
|
||||
component = hass.data[DOMAIN] = EntityComponent(logger, DOMAIN, hass)
|
||||
|
||||
await component.async_setup(config)
|
||||
# Ensure Home Assistant platform always loaded.
|
||||
await component.async_setup_platform(
|
||||
HA_DOMAIN, {"platform": "homeasistant", STATES: []}
|
||||
)
|
||||
|
||||
async def async_handle_scene_service(service):
|
||||
"""Handle calls to the switch services."""
|
||||
|
||||
@@ -133,7 +133,9 @@ class SmartThingsLight(SmartThingsEntity, Light):
|
||||
"""Update entity attributes when the device status has changed."""
|
||||
# Brightness and transition
|
||||
if self._supported_features & SUPPORT_BRIGHTNESS:
|
||||
self._brightness = convert_scale(self._device.status.level, 100, 255)
|
||||
self._brightness = int(
|
||||
convert_scale(self._device.status.level, 100, 255, 0)
|
||||
)
|
||||
# Color Temperature
|
||||
if self._supported_features & SUPPORT_COLOR_TEMP:
|
||||
self._color_temp = color_util.color_temperature_kelvin_to_mired(
|
||||
|
||||
@@ -11,6 +11,9 @@ from .const import (
|
||||
CONF_BLOCK_CLIENT,
|
||||
CONF_CONTROLLER,
|
||||
CONF_DETECTION_TIME,
|
||||
CONF_DONT_TRACK_CLIENTS,
|
||||
CONF_DONT_TRACK_DEVICES,
|
||||
CONF_DONT_TRACK_WIRED_CLIENTS,
|
||||
CONF_SITE_ID,
|
||||
CONF_SSID_FILTER,
|
||||
CONTROLLER_ID,
|
||||
@@ -28,6 +31,9 @@ CONTROLLER_SCHEMA = vol.Schema(
|
||||
vol.Optional(CONF_BLOCK_CLIENT, default=[]): vol.All(
|
||||
cv.ensure_list, [cv.string]
|
||||
),
|
||||
vol.Optional(CONF_DONT_TRACK_CLIENTS): cv.boolean,
|
||||
vol.Optional(CONF_DONT_TRACK_DEVICES): cv.boolean,
|
||||
vol.Optional(CONF_DONT_TRACK_WIRED_CLIENTS): cv.boolean,
|
||||
vol.Optional(CONF_DETECTION_TIME): vol.All(
|
||||
cv.time_period, cv.positive_timedelta
|
||||
),
|
||||
|
||||
@@ -13,6 +13,9 @@ UNIFI_CONFIG = "unifi_config"
|
||||
|
||||
CONF_BLOCK_CLIENT = "block_client"
|
||||
CONF_DETECTION_TIME = "detection_time"
|
||||
CONF_DONT_TRACK_CLIENTS = "dont_track_clients"
|
||||
CONF_DONT_TRACK_DEVICES = "dont_track_devices"
|
||||
CONF_DONT_TRACK_WIRED_CLIENTS = "dont_track_wired_clients"
|
||||
CONF_SSID_FILTER = "ssid_filter"
|
||||
|
||||
ATTR_MANUFACTURER = "Ubiquiti Networks"
|
||||
|
||||
@@ -28,6 +28,9 @@ from .const import (
|
||||
ATTR_MANUFACTURER,
|
||||
CONF_CONTROLLER,
|
||||
CONF_DETECTION_TIME,
|
||||
CONF_DONT_TRACK_CLIENTS,
|
||||
CONF_DONT_TRACK_DEVICES,
|
||||
CONF_DONT_TRACK_WIRED_CLIENTS,
|
||||
CONF_SITE_ID,
|
||||
CONF_SSID_FILTER,
|
||||
CONTROLLER_ID,
|
||||
@@ -38,11 +41,7 @@ LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEVICE_ATTRIBUTES = [
|
||||
"_is_guest_by_uap",
|
||||
"ap_mac",
|
||||
"authorized",
|
||||
"bssid",
|
||||
"ccq",
|
||||
"channel",
|
||||
"essid",
|
||||
"hostname",
|
||||
"ip",
|
||||
@@ -51,14 +50,11 @@ DEVICE_ATTRIBUTES = [
|
||||
"is_wired",
|
||||
"mac",
|
||||
"name",
|
||||
"noise",
|
||||
"noted",
|
||||
"oui",
|
||||
"qos_policy_applied",
|
||||
"radio",
|
||||
"radio_proto",
|
||||
"rssi",
|
||||
"signal",
|
||||
"site_id",
|
||||
"vlan",
|
||||
]
|
||||
@@ -154,46 +150,64 @@ def update_items(controller, async_add_entities, tracked):
|
||||
"""Update tracked device state from the controller."""
|
||||
new_tracked = []
|
||||
|
||||
for client_id in controller.api.clients:
|
||||
if not controller.unifi_config.get(CONF_DONT_TRACK_CLIENTS, False):
|
||||
|
||||
if client_id in tracked:
|
||||
for client_id in controller.api.clients:
|
||||
|
||||
if client_id in tracked:
|
||||
LOGGER.debug(
|
||||
"Updating UniFi tracked client %s (%s)",
|
||||
tracked[client_id].entity_id,
|
||||
tracked[client_id].client.mac,
|
||||
)
|
||||
tracked[client_id].async_schedule_update_ha_state()
|
||||
continue
|
||||
|
||||
client = controller.api.clients[client_id]
|
||||
|
||||
if (
|
||||
not client.is_wired
|
||||
and CONF_SSID_FILTER in controller.unifi_config
|
||||
and client.essid not in controller.unifi_config[CONF_SSID_FILTER]
|
||||
):
|
||||
continue
|
||||
|
||||
if (
|
||||
controller.unifi_config.get(CONF_DONT_TRACK_WIRED_CLIENTS, False)
|
||||
and client.is_wired
|
||||
):
|
||||
continue
|
||||
|
||||
tracked[client_id] = UniFiClientTracker(client, controller)
|
||||
new_tracked.append(tracked[client_id])
|
||||
LOGGER.debug(
|
||||
"Updating UniFi tracked client %s (%s)",
|
||||
tracked[client_id].entity_id,
|
||||
tracked[client_id].client.mac,
|
||||
"New UniFi client tracker %s (%s)",
|
||||
client.name or client.hostname,
|
||||
client.mac,
|
||||
)
|
||||
tracked[client_id].async_schedule_update_ha_state()
|
||||
continue
|
||||
|
||||
client = controller.api.clients[client_id]
|
||||
if not controller.unifi_config.get(CONF_DONT_TRACK_DEVICES, False):
|
||||
|
||||
if (
|
||||
not client.is_wired
|
||||
and CONF_SSID_FILTER in controller.unifi_config
|
||||
and client.essid not in controller.unifi_config[CONF_SSID_FILTER]
|
||||
):
|
||||
continue
|
||||
for device_id in controller.api.devices:
|
||||
|
||||
tracked[client_id] = UniFiClientTracker(client, controller)
|
||||
new_tracked.append(tracked[client_id])
|
||||
LOGGER.debug("New UniFi client tracker %s (%s)", client.hostname, client.mac)
|
||||
if device_id in tracked:
|
||||
LOGGER.debug(
|
||||
"Updating UniFi tracked device %s (%s)",
|
||||
tracked[device_id].entity_id,
|
||||
tracked[device_id].device.mac,
|
||||
)
|
||||
tracked[device_id].async_schedule_update_ha_state()
|
||||
continue
|
||||
|
||||
for device_id in controller.api.devices:
|
||||
device = controller.api.devices[device_id]
|
||||
|
||||
if device_id in tracked:
|
||||
tracked[device_id] = UniFiDeviceTracker(device, controller)
|
||||
new_tracked.append(tracked[device_id])
|
||||
LOGGER.debug(
|
||||
"Updating UniFi tracked device %s (%s)",
|
||||
tracked[device_id].entity_id,
|
||||
tracked[device_id].device.mac,
|
||||
"New UniFi device tracker %s (%s)",
|
||||
device.name or device.model,
|
||||
device.mac,
|
||||
)
|
||||
tracked[device_id].async_schedule_update_ha_state()
|
||||
continue
|
||||
|
||||
device = controller.api.devices[device_id]
|
||||
|
||||
tracked[device_id] = UniFiDeviceTracker(device, controller)
|
||||
new_tracked.append(tracked[device_id])
|
||||
LOGGER.debug("New UniFi device tracker %s (%s)", device.name, device.mac)
|
||||
|
||||
if new_tracked:
|
||||
async_add_entities(new_tracked)
|
||||
@@ -280,9 +294,10 @@ class UniFiDeviceTracker(ScannerEntity):
|
||||
CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME
|
||||
)
|
||||
|
||||
if (
|
||||
if self.device.last_seen and (
|
||||
dt_util.utcnow() - dt_util.utc_from_timestamp(float(self.device.last_seen))
|
||||
) < detection_time:
|
||||
< detection_time
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -294,7 +309,7 @@ class UniFiDeviceTracker(ScannerEntity):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the device."""
|
||||
return self.device.name
|
||||
return self.device.name or self.device.model
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
@@ -304,22 +319,29 @@ class UniFiDeviceTracker(ScannerEntity):
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if controller is available."""
|
||||
return self.controller.available
|
||||
return not self.device.disabled and self.controller.available
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
return {
|
||||
info = {
|
||||
"connections": {(CONNECTION_NETWORK_MAC, self.device.mac)},
|
||||
"manufacturer": ATTR_MANUFACTURER,
|
||||
"model": self.device.model,
|
||||
"name": self.device.name,
|
||||
"sw_version": self.device.version,
|
||||
}
|
||||
|
||||
if self.device.name:
|
||||
info["name"] = self.device.name
|
||||
|
||||
return info
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device state attributes."""
|
||||
if not self.device.last_seen:
|
||||
return {}
|
||||
|
||||
attributes = {}
|
||||
|
||||
attributes["upgradable"] = self.device.upgradable
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/components/unifi",
|
||||
"requirements": [
|
||||
"aiounifi==8"
|
||||
"aiounifi==10"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
||||
@@ -266,8 +266,8 @@ class UniFiBlockClientSwitch(UniFiClient, SwitchDevice):
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if client is blocked."""
|
||||
return self.client.blocked
|
||||
"""Return true if client is allowed to connect."""
|
||||
return not self.client.blocked
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
@@ -275,9 +275,9 @@ class UniFiBlockClientSwitch(UniFiClient, SwitchDevice):
|
||||
return self.controller.available
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Block client."""
|
||||
await self.controller.api.clients.async_block(self.client.mac)
|
||||
"""Turn on connectivity for client."""
|
||||
await self.controller.api.clients.async_unblock(self.client.mac)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Unblock client."""
|
||||
await self.controller.api.clients.async_unblock(self.client.mac)
|
||||
"""Turn off connectivity for client."""
|
||||
await self.controller.api.clients.async_block(self.client.mac)
|
||||
|
||||
@@ -5,14 +5,11 @@
|
||||
"documentation": "https://www.home-assistant.io/components/zha",
|
||||
"requirements": [
|
||||
"bellows-homeassistant==0.9.0",
|
||||
"zha-quirks==0.0.19",
|
||||
"zha-quirks==0.0.20",
|
||||
"zigpy-deconz==0.2.1",
|
||||
"zigpy-homeassistant==0.7.0",
|
||||
"zigpy-xbee-homeassistant==0.4.0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
"@dmulcahey",
|
||||
"@adminiuga"
|
||||
]
|
||||
"codeowners": ["@dmulcahey", "@adminiuga"]
|
||||
}
|
||||
|
||||
@@ -10,15 +10,19 @@ from homeassistant.components.climate.const import (
|
||||
CURRENT_HVAC_IDLE,
|
||||
CURRENT_HVAC_OFF,
|
||||
DOMAIN,
|
||||
HVAC_MODE_AUTO,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_DRY,
|
||||
HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_OFF,
|
||||
PRESET_BOOST,
|
||||
PRESET_NONE,
|
||||
SUPPORT_FAN_MODE,
|
||||
SUPPORT_SWING_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_PRESET_MODE,
|
||||
)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
from homeassistant.core import callback
|
||||
@@ -37,38 +41,57 @@ REMOTEC_ZXT_120_THERMOSTAT = (REMOTEC, REMOTEC_ZXT_120)
|
||||
ATTR_OPERATING_STATE = "operating_state"
|
||||
ATTR_FAN_STATE = "fan_state"
|
||||
|
||||
|
||||
# Device is in manufacturer specific mode (e.g. setting the valve manually)
|
||||
PRESET_MANUFACTURER_SPECIFIC = "Manufacturer Specific"
|
||||
|
||||
WORKAROUND_ZXT_120 = "zxt_120"
|
||||
|
||||
DEVICE_MAPPINGS = {REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120}
|
||||
|
||||
HVAC_STATE_MAPPINGS = {
|
||||
"Off": HVAC_MODE_OFF,
|
||||
"Heat": HVAC_MODE_HEAT,
|
||||
"Heat Mode": HVAC_MODE_HEAT,
|
||||
"Heat (Default)": HVAC_MODE_HEAT,
|
||||
"Aux Heat": HVAC_MODE_HEAT,
|
||||
"Furnace": HVAC_MODE_HEAT,
|
||||
"Fan Only": HVAC_MODE_FAN_ONLY,
|
||||
"Dry Air": HVAC_MODE_DRY,
|
||||
"Moist Air": HVAC_MODE_DRY,
|
||||
"Cool": HVAC_MODE_COOL,
|
||||
"Auto": HVAC_MODE_HEAT_COOL,
|
||||
"off": HVAC_MODE_OFF,
|
||||
"heat": HVAC_MODE_HEAT,
|
||||
"heat mode": HVAC_MODE_HEAT,
|
||||
"heat (default)": HVAC_MODE_HEAT,
|
||||
"aux heat": HVAC_MODE_HEAT,
|
||||
"furnace": HVAC_MODE_HEAT,
|
||||
"fan only": HVAC_MODE_FAN_ONLY,
|
||||
"dry air": HVAC_MODE_DRY,
|
||||
"moist air": HVAC_MODE_DRY,
|
||||
"cool": HVAC_MODE_COOL,
|
||||
"heat_cool": HVAC_MODE_HEAT_COOL,
|
||||
"auto": HVAC_MODE_HEAT_COOL,
|
||||
}
|
||||
|
||||
|
||||
HVAC_CURRENT_MAPPINGS = {
|
||||
"Idle": CURRENT_HVAC_IDLE,
|
||||
"Heat": CURRENT_HVAC_HEAT,
|
||||
"Pending Heat": CURRENT_HVAC_IDLE,
|
||||
"Heating": CURRENT_HVAC_HEAT,
|
||||
"Cool": CURRENT_HVAC_COOL,
|
||||
"Pending Cool": CURRENT_HVAC_IDLE,
|
||||
"Cooling": CURRENT_HVAC_COOL,
|
||||
"Fan Only": CURRENT_HVAC_FAN,
|
||||
"Vent / Economiser": CURRENT_HVAC_FAN,
|
||||
"Off": CURRENT_HVAC_OFF,
|
||||
"idle": CURRENT_HVAC_IDLE,
|
||||
"heat": CURRENT_HVAC_HEAT,
|
||||
"pending heat": CURRENT_HVAC_IDLE,
|
||||
"heating": CURRENT_HVAC_HEAT,
|
||||
"cool": CURRENT_HVAC_COOL,
|
||||
"pending cool": CURRENT_HVAC_IDLE,
|
||||
"cooling": CURRENT_HVAC_COOL,
|
||||
"fan only": CURRENT_HVAC_FAN,
|
||||
"vent / economiser": CURRENT_HVAC_FAN,
|
||||
"off": CURRENT_HVAC_OFF,
|
||||
}
|
||||
|
||||
PRESET_MAPPINGS = {
|
||||
"full power": PRESET_BOOST,
|
||||
"manufacturer specific": PRESET_MANUFACTURER_SPECIFIC,
|
||||
}
|
||||
|
||||
DEFAULT_HVAC_MODES = [
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_DRY,
|
||||
HVAC_MODE_OFF,
|
||||
HVAC_MODE_AUTO,
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Old method of setting up Z-Wave climate devices."""
|
||||
@@ -101,9 +124,13 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
||||
self._target_temperature = None
|
||||
self._current_temperature = None
|
||||
self._hvac_action = None
|
||||
self._hvac_list = None
|
||||
self._hvac_mapping = None
|
||||
self._hvac_mode = None
|
||||
self._hvac_list = None # [zwave_mode]
|
||||
self._hvac_mapping = None # {ha_mode:zwave_mode}
|
||||
self._hvac_mode = None # ha_mode
|
||||
self._default_hvac_mode = None # ha_mode
|
||||
self._preset_mapping = None # {ha_mode:zwave_mode}
|
||||
self._preset_list = None # [zwave_mode]
|
||||
self._preset_mode = None # ha_mode if exists, else zwave_mode
|
||||
self._current_fan_mode = None
|
||||
self._fan_modes = None
|
||||
self._fan_state = None
|
||||
@@ -132,6 +159,8 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
||||
support |= SUPPORT_FAN_MODE
|
||||
if self._zxt_120 == 1 and self.values.zxt_120_swing_mode:
|
||||
support |= SUPPORT_SWING_MODE
|
||||
if self._preset_list:
|
||||
support |= SUPPORT_PRESET_MODE
|
||||
return support
|
||||
|
||||
def update_properties(self):
|
||||
@@ -140,26 +169,86 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
||||
if self.values.mode:
|
||||
self._hvac_list = []
|
||||
self._hvac_mapping = {}
|
||||
hvac_list = self.values.mode.data_items
|
||||
if hvac_list:
|
||||
for mode in hvac_list:
|
||||
ha_mode = HVAC_STATE_MAPPINGS.get(mode)
|
||||
self._preset_list = []
|
||||
self._preset_mapping = {}
|
||||
|
||||
mode_list = self.values.mode.data_items
|
||||
if mode_list:
|
||||
for mode in mode_list:
|
||||
ha_mode = HVAC_STATE_MAPPINGS.get(str(mode).lower())
|
||||
ha_preset = PRESET_MAPPINGS.get(str(mode).lower())
|
||||
if ha_mode and ha_mode not in self._hvac_mapping:
|
||||
self._hvac_mapping[ha_mode] = mode
|
||||
self._hvac_list.append(ha_mode)
|
||||
continue
|
||||
self._hvac_list.append(mode)
|
||||
elif ha_preset and ha_preset not in self._preset_mapping:
|
||||
self._preset_mapping[ha_preset] = mode
|
||||
self._preset_list.append(ha_preset)
|
||||
else:
|
||||
# If nothing matches
|
||||
self._preset_list.append(mode)
|
||||
|
||||
# Default operation mode
|
||||
for mode in DEFAULT_HVAC_MODES:
|
||||
if mode in self._hvac_mapping.keys():
|
||||
self._default_hvac_mode = mode
|
||||
break
|
||||
|
||||
if self._preset_list:
|
||||
# Presets are supported
|
||||
self._preset_list.append(PRESET_NONE)
|
||||
|
||||
current_mode = self.values.mode.data
|
||||
self._hvac_mode = next(
|
||||
_LOGGER.debug("current_mode=%s", current_mode)
|
||||
_hvac_temp = next(
|
||||
(
|
||||
key
|
||||
for key, value in self._hvac_mapping.items()
|
||||
if value == current_mode
|
||||
),
|
||||
current_mode,
|
||||
None,
|
||||
)
|
||||
|
||||
if _hvac_temp is None:
|
||||
# The current mode is not a hvac mode
|
||||
if (
|
||||
"heat" in current_mode.lower()
|
||||
and HVAC_MODE_HEAT in self._hvac_mapping.keys()
|
||||
):
|
||||
# The current preset modes maps to HVAC_MODE_HEAT
|
||||
_LOGGER.debug("Mapped to HEAT")
|
||||
self._hvac_mode = HVAC_MODE_HEAT
|
||||
elif (
|
||||
"cool" in current_mode.lower()
|
||||
and HVAC_MODE_COOL in self._hvac_mapping.keys()
|
||||
):
|
||||
# The current preset modes maps to HVAC_MODE_COOL
|
||||
_LOGGER.debug("Mapped to COOL")
|
||||
self._hvac_mode = HVAC_MODE_COOL
|
||||
else:
|
||||
# The current preset modes maps to self._default_hvac_mode
|
||||
_LOGGER.debug("Mapped to DEFAULT")
|
||||
self._hvac_mode = self._default_hvac_mode
|
||||
self._preset_mode = next(
|
||||
(
|
||||
key
|
||||
for key, value in self._preset_mapping.items()
|
||||
if value == current_mode
|
||||
),
|
||||
current_mode,
|
||||
)
|
||||
else:
|
||||
# The current mode is a hvac mode
|
||||
self._hvac_mode = _hvac_temp
|
||||
self._preset_mode = PRESET_NONE
|
||||
|
||||
_LOGGER.debug("self._hvac_mapping=%s", self._hvac_mapping)
|
||||
_LOGGER.debug("self._hvac_list=%s", self._hvac_list)
|
||||
_LOGGER.debug("self._hvac_mode=%s", self._hvac_mode)
|
||||
_LOGGER.debug("self._default_hvac_mode=%s", self._default_hvac_mode)
|
||||
_LOGGER.debug("self._hvac_action=%s", self._hvac_action)
|
||||
_LOGGER.debug("self._preset_mapping=%s", self._preset_mapping)
|
||||
_LOGGER.debug("self._preset_list=%s", self._preset_list)
|
||||
_LOGGER.debug("self._preset_mode=%s", self._preset_mode)
|
||||
|
||||
# Current Temp
|
||||
if self.values.temperature:
|
||||
@@ -199,7 +288,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
||||
# Operating state
|
||||
if self.values.operating_state:
|
||||
mode = self.values.operating_state.data
|
||||
self._hvac_action = HVAC_CURRENT_MAPPINGS.get(mode)
|
||||
self._hvac_action = HVAC_CURRENT_MAPPINGS.get(str(mode).lower(), mode)
|
||||
|
||||
# Fan operating state
|
||||
if self.values.fan_state:
|
||||
@@ -247,7 +336,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
||||
"""
|
||||
if self.values.mode:
|
||||
return self._hvac_mode
|
||||
return HVAC_MODE_HEAT
|
||||
return self._default_hvac_mode
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
@@ -267,6 +356,26 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
||||
"""
|
||||
return self._hvac_action
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return preset operation ie. eco, away.
|
||||
|
||||
Need to be one of PRESET_*.
|
||||
"""
|
||||
if self.values.mode:
|
||||
return self._preset_mode
|
||||
return PRESET_NONE
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""Return the list of available preset operation modes.
|
||||
|
||||
Need to be a subset of PRESET_MODES.
|
||||
"""
|
||||
if self.values.mode:
|
||||
return self._preset_list
|
||||
return []
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
@@ -274,24 +383,46 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
_LOGGER.debug("Set temperature to %s", kwargs.get(ATTR_TEMPERATURE))
|
||||
if kwargs.get(ATTR_TEMPERATURE) is None:
|
||||
return
|
||||
self.values.primary.data = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
"""Set new target fan mode."""
|
||||
_LOGGER.debug("Set fan mode to %s", fan_mode)
|
||||
if not self.values.fan_mode:
|
||||
return
|
||||
self.values.fan_mode.data = fan_mode
|
||||
|
||||
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.mode:
|
||||
return
|
||||
self.values.mode.data = self._hvac_mapping.get(hvac_mode, hvac_mode)
|
||||
operation_mode = self._hvac_mapping.get(hvac_mode)
|
||||
_LOGGER.debug("Set operation_mode to %s", operation_mode)
|
||||
self.values.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.mode:
|
||||
return
|
||||
if preset_mode == PRESET_NONE:
|
||||
# Activate the current hvac mode
|
||||
self.update_properties()
|
||||
operation_mode = self._hvac_mapping.get(self.hvac_mode)
|
||||
_LOGGER.debug("Set operation_mode to %s", operation_mode)
|
||||
self.values.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.mode.data = operation_mode
|
||||
|
||||
def set_swing_mode(self, swing_mode):
|
||||
"""Set new target swing mode."""
|
||||
_LOGGER.debug("Set swing_mode to %s", swing_mode)
|
||||
if self._zxt_120 == 1:
|
||||
if self.values.zxt_120_swing_mode:
|
||||
self.values.zxt_120_swing_mode.data = swing_mode
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""Constants used by Home Assistant components."""
|
||||
MAJOR_VERSION = 0
|
||||
MINOR_VERSION = 97
|
||||
PATCH_VERSION = "0b0"
|
||||
PATCH_VERSION = "1"
|
||||
__short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION)
|
||||
__version__ = "{}.{}".format(__short_version__, PATCH_VERSION)
|
||||
REQUIRED_PYTHON_VER = (3, 6, 0)
|
||||
|
||||
@@ -114,7 +114,7 @@ class EntityComponent:
|
||||
# Look in config for Domain, Domain 2, Domain 3 etc and load them
|
||||
tasks = []
|
||||
for p_type, p_config in config_per_platform(config, self.domain):
|
||||
tasks.append(self._async_setup_platform(p_type, p_config))
|
||||
tasks.append(self.async_setup_platform(p_type, p_config))
|
||||
|
||||
if tasks:
|
||||
await asyncio.wait(tasks)
|
||||
@@ -123,7 +123,7 @@ class EntityComponent:
|
||||
# Refer to: homeassistant.components.discovery.load_platform()
|
||||
async def component_platform_discovered(platform, info):
|
||||
"""Handle the loading of a platform."""
|
||||
await self._async_setup_platform(platform, {}, info)
|
||||
await self.async_setup_platform(platform, {}, info)
|
||||
|
||||
discovery.async_listen_platform(
|
||||
self.hass, self.domain, component_platform_discovered
|
||||
@@ -212,10 +212,13 @@ class EntityComponent:
|
||||
|
||||
self.hass.services.async_register(self.domain, name, handle_service, schema)
|
||||
|
||||
async def _async_setup_platform(
|
||||
async def async_setup_platform(
|
||||
self, platform_type, platform_config, discovery_info=None
|
||||
):
|
||||
"""Set up a platform for this component."""
|
||||
if self.config is None:
|
||||
raise RuntimeError("async_setup needs to be called first")
|
||||
|
||||
platform = await async_prepare_setup_platform(
|
||||
self.hass, self.config, self.domain, platform_type
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Class to manage the entities for a single platform."""
|
||||
import asyncio
|
||||
from contextvars import ContextVar
|
||||
from typing import Optional
|
||||
|
||||
from homeassistant.const import DEVICE_DEFAULT_NAME
|
||||
from homeassistant.core import callback, valid_entity_id, split_entity_id
|
||||
@@ -127,6 +129,7 @@ class EntityPlatform:
|
||||
|
||||
async_create_setup_task creates a coroutine that sets up platform.
|
||||
"""
|
||||
current_platform.set(self)
|
||||
logger = self.logger
|
||||
hass = self.hass
|
||||
full_name = "{}.{}".format(self.domain, self.platform_name)
|
||||
@@ -457,3 +460,8 @@ class EntityPlatform:
|
||||
|
||||
if tasks:
|
||||
await asyncio.wait(tasks)
|
||||
|
||||
|
||||
current_platform: ContextVar[Optional[EntityPlatform]] = ContextVar(
|
||||
"current_platform", default=None
|
||||
)
|
||||
|
||||
@@ -7,10 +7,11 @@ async_timeout==3.0.1
|
||||
attrs==19.1.0
|
||||
bcrypt==3.1.7
|
||||
certifi>=2019.6.16
|
||||
contextvars==2.4;python_version<"3.7"
|
||||
cryptography==2.7
|
||||
distro==1.4.0
|
||||
hass-nabucasa==0.15
|
||||
home-assistant-frontend==20190731.0
|
||||
hass-nabucasa==0.16
|
||||
home-assistant-frontend==20190805.0
|
||||
importlib-metadata==0.18
|
||||
jinja2>=2.10.1
|
||||
netdisco==2.6.0
|
||||
|
||||
@@ -5,6 +5,7 @@ async_timeout==3.0.1
|
||||
attrs==19.1.0
|
||||
bcrypt==3.1.7
|
||||
certifi>=2019.6.16
|
||||
contextvars==2.4;python_version<"3.7"
|
||||
importlib-metadata==0.18
|
||||
jinja2>=2.10.1
|
||||
PyJWT==1.7.1
|
||||
@@ -169,7 +170,7 @@ aiopvapi==1.6.14
|
||||
aioswitcher==2019.4.26
|
||||
|
||||
# homeassistant.components.unifi
|
||||
aiounifi==8
|
||||
aiounifi==10
|
||||
|
||||
# homeassistant.components.wwlln
|
||||
aiowwlln==1.0.0
|
||||
@@ -320,7 +321,7 @@ buienradar==1.0.1
|
||||
caldav==0.6.1
|
||||
|
||||
# homeassistant.components.cisco_mobility_express
|
||||
ciscomobilityexpress==0.3.1
|
||||
ciscomobilityexpress==0.3.3
|
||||
|
||||
# homeassistant.components.ciscospark
|
||||
ciscosparkapi==0.4.2
|
||||
@@ -442,7 +443,7 @@ env_canada==0.0.20
|
||||
# envirophat==0.0.6
|
||||
|
||||
# homeassistant.components.enphase_envoy
|
||||
envoy_reader==0.8
|
||||
envoy_reader==0.8.6
|
||||
|
||||
# homeassistant.components.season
|
||||
ephem==3.7.6.0
|
||||
@@ -592,13 +593,13 @@ habitipy==0.2.0
|
||||
hangups==0.4.9
|
||||
|
||||
# homeassistant.components.cloud
|
||||
hass-nabucasa==0.15
|
||||
hass-nabucasa==0.16
|
||||
|
||||
# homeassistant.components.mqtt
|
||||
hbmqtt==0.9.4
|
||||
|
||||
# homeassistant.components.jewish_calendar
|
||||
hdate==0.8.8
|
||||
hdate==0.9.0
|
||||
|
||||
# homeassistant.components.heatmiser
|
||||
heatmiserV3==0.9.1
|
||||
@@ -622,16 +623,16 @@ hole==0.3.0
|
||||
holidays==0.9.11
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20190731.0
|
||||
home-assistant-frontend==20190805.0
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.4
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
homekit[IP]==0.14.0
|
||||
homekit[IP]==0.15.0
|
||||
|
||||
# homeassistant.components.homematicip_cloud
|
||||
homematicip==0.10.9
|
||||
homematicip==0.10.10
|
||||
|
||||
# homeassistant.components.horizon
|
||||
horimote==0.4.1
|
||||
@@ -664,7 +665,7 @@ iglo==1.2.7
|
||||
ihcsdk==2.3.0
|
||||
|
||||
# homeassistant.components.incomfort
|
||||
incomfort-client==0.3.0
|
||||
incomfort-client==0.3.1
|
||||
|
||||
# homeassistant.components.influxdb
|
||||
influxdb==5.2.0
|
||||
@@ -1640,7 +1641,7 @@ rjpl==0.3.5
|
||||
rocketchat-API==0.6.1
|
||||
|
||||
# homeassistant.components.roku
|
||||
roku==3.0.0
|
||||
roku==3.1
|
||||
|
||||
# homeassistant.components.roomba
|
||||
roombapy==1.3.1
|
||||
@@ -1963,7 +1964,7 @@ zengge==0.2
|
||||
zeroconf==0.23.0
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha-quirks==0.0.19
|
||||
zha-quirks==0.0.20
|
||||
|
||||
# homeassistant.components.zhong_hong
|
||||
zhong_hong_hvac==1.0.9
|
||||
|
||||
@@ -68,7 +68,7 @@ aionotion==1.1.0
|
||||
aioswitcher==2019.4.26
|
||||
|
||||
# homeassistant.components.unifi
|
||||
aiounifi==8
|
||||
aiounifi==10
|
||||
|
||||
# homeassistant.components.wwlln
|
||||
aiowwlln==1.0.0
|
||||
@@ -157,25 +157,25 @@ ha-ffmpeg==2.0
|
||||
hangups==0.4.9
|
||||
|
||||
# homeassistant.components.cloud
|
||||
hass-nabucasa==0.15
|
||||
hass-nabucasa==0.16
|
||||
|
||||
# homeassistant.components.mqtt
|
||||
hbmqtt==0.9.4
|
||||
|
||||
# homeassistant.components.jewish_calendar
|
||||
hdate==0.8.8
|
||||
hdate==0.9.0
|
||||
|
||||
# homeassistant.components.workday
|
||||
holidays==0.9.11
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20190731.0
|
||||
home-assistant-frontend==20190805.0
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
homekit[IP]==0.14.0
|
||||
homekit[IP]==0.15.0
|
||||
|
||||
# homeassistant.components.homematicip_cloud
|
||||
homematicip==0.10.9
|
||||
homematicip==0.10.10
|
||||
|
||||
# homeassistant.components.google
|
||||
# homeassistant.components.remember_the_milk
|
||||
|
||||
@@ -215,7 +215,7 @@ def core_requirements():
|
||||
"""Gather core requirements out of setup.py."""
|
||||
with open("setup.py") as inp:
|
||||
reqs_raw = re.search(r"REQUIRES = \[(.*?)\]", inp.read(), re.S).group(1)
|
||||
return re.findall(r"'(.*?)'", reqs_raw)
|
||||
return [x[1] for x in re.findall(r"(['\"])(.*?)\1", reqs_raw)]
|
||||
|
||||
|
||||
def gather_recursive_requirements(domain, seen=None):
|
||||
|
||||
@@ -102,7 +102,7 @@ def write_version(version):
|
||||
"MINOR_VERSION = .*\n", "MINOR_VERSION = {}\n".format(minor), content
|
||||
)
|
||||
content = re.sub(
|
||||
"PATCH_VERSION = .*\n", "PATCH_VERSION = '{}'\n".format(patch), content
|
||||
"PATCH_VERSION = .*\n", 'PATCH_VERSION = "{}"\n'.format(patch), content
|
||||
)
|
||||
|
||||
with open("homeassistant/const.py", "wt") as fil:
|
||||
|
||||
86
setup.py
86
setup.py
@@ -5,55 +5,55 @@ from setuptools import setup, find_packages
|
||||
|
||||
import homeassistant.const as hass_const
|
||||
|
||||
PROJECT_NAME = 'Home Assistant'
|
||||
PROJECT_PACKAGE_NAME = 'homeassistant'
|
||||
PROJECT_LICENSE = 'Apache License 2.0'
|
||||
PROJECT_AUTHOR = 'The Home Assistant Authors'
|
||||
PROJECT_COPYRIGHT = ' 2013-{}, {}'.format(dt.now().year, PROJECT_AUTHOR)
|
||||
PROJECT_URL = 'https://home-assistant.io/'
|
||||
PROJECT_EMAIL = 'hello@home-assistant.io'
|
||||
PROJECT_NAME = "Home Assistant"
|
||||
PROJECT_PACKAGE_NAME = "homeassistant"
|
||||
PROJECT_LICENSE = "Apache License 2.0"
|
||||
PROJECT_AUTHOR = "The Home Assistant Authors"
|
||||
PROJECT_COPYRIGHT = " 2013-{}, {}".format(dt.now().year, PROJECT_AUTHOR)
|
||||
PROJECT_URL = "https://home-assistant.io/"
|
||||
PROJECT_EMAIL = "hello@home-assistant.io"
|
||||
|
||||
PROJECT_GITHUB_USERNAME = 'home-assistant'
|
||||
PROJECT_GITHUB_REPOSITORY = 'home-assistant'
|
||||
PROJECT_GITHUB_USERNAME = "home-assistant"
|
||||
PROJECT_GITHUB_REPOSITORY = "home-assistant"
|
||||
|
||||
PYPI_URL = 'https://pypi.python.org/pypi/{}'.format(PROJECT_PACKAGE_NAME)
|
||||
GITHUB_PATH = '{}/{}'.format(
|
||||
PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY)
|
||||
GITHUB_URL = 'https://github.com/{}'.format(GITHUB_PATH)
|
||||
PYPI_URL = "https://pypi.python.org/pypi/{}".format(PROJECT_PACKAGE_NAME)
|
||||
GITHUB_PATH = "{}/{}".format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY)
|
||||
GITHUB_URL = "https://github.com/{}".format(GITHUB_PATH)
|
||||
|
||||
DOWNLOAD_URL = '{}/archive/{}.zip'.format(GITHUB_URL, hass_const.__version__)
|
||||
DOWNLOAD_URL = "{}/archive/{}.zip".format(GITHUB_URL, hass_const.__version__)
|
||||
PROJECT_URLS = {
|
||||
'Bug Reports': '{}/issues'.format(GITHUB_URL),
|
||||
'Dev Docs': 'https://developers.home-assistant.io/',
|
||||
'Discord': 'https://discordapp.com/invite/c5DvZ4e',
|
||||
'Forum': 'https://community.home-assistant.io/',
|
||||
"Bug Reports": "{}/issues".format(GITHUB_URL),
|
||||
"Dev Docs": "https://developers.home-assistant.io/",
|
||||
"Discord": "https://discordapp.com/invite/c5DvZ4e",
|
||||
"Forum": "https://community.home-assistant.io/",
|
||||
}
|
||||
|
||||
PACKAGES = find_packages(exclude=['tests', 'tests.*'])
|
||||
PACKAGES = find_packages(exclude=["tests", "tests.*"])
|
||||
|
||||
REQUIRES = [
|
||||
'aiohttp==3.5.4',
|
||||
'astral==1.10.1',
|
||||
'async_timeout==3.0.1',
|
||||
'attrs==19.1.0',
|
||||
'bcrypt==3.1.7',
|
||||
'certifi>=2019.6.16',
|
||||
'importlib-metadata==0.18',
|
||||
'jinja2>=2.10.1',
|
||||
'PyJWT==1.7.1',
|
||||
"aiohttp==3.5.4",
|
||||
"astral==1.10.1",
|
||||
"async_timeout==3.0.1",
|
||||
"attrs==19.1.0",
|
||||
"bcrypt==3.1.7",
|
||||
"certifi>=2019.6.16",
|
||||
'contextvars==2.4;python_version<"3.7"',
|
||||
"importlib-metadata==0.18",
|
||||
"jinja2>=2.10.1",
|
||||
"PyJWT==1.7.1",
|
||||
# PyJWT has loose dependency. We want the latest one.
|
||||
'cryptography==2.7',
|
||||
'pip>=8.0.3',
|
||||
'python-slugify==3.0.2',
|
||||
'pytz>=2019.01',
|
||||
'pyyaml==5.1.1',
|
||||
'requests==2.22.0',
|
||||
'ruamel.yaml==0.15.99',
|
||||
'voluptuous==0.11.5',
|
||||
'voluptuous-serialize==2.1.0',
|
||||
"cryptography==2.7",
|
||||
"pip>=8.0.3",
|
||||
"python-slugify==3.0.2",
|
||||
"pytz>=2019.01",
|
||||
"pyyaml==5.1.1",
|
||||
"requests==2.22.0",
|
||||
"ruamel.yaml==0.15.99",
|
||||
"voluptuous==0.11.5",
|
||||
"voluptuous-serialize==2.1.0",
|
||||
]
|
||||
|
||||
MIN_PY_VERSION = '.'.join(map(str, hass_const.REQUIRED_PYTHON_VER))
|
||||
MIN_PY_VERSION = ".".join(map(str, hass_const.REQUIRED_PYTHON_VER))
|
||||
|
||||
setup(
|
||||
name=PROJECT_PACKAGE_NAME,
|
||||
@@ -67,11 +67,7 @@ setup(
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=REQUIRES,
|
||||
python_requires='>={}'.format(MIN_PY_VERSION),
|
||||
test_suite='tests',
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'hass = homeassistant.__main__:main'
|
||||
]
|
||||
},
|
||||
python_requires=">={}".format(MIN_PY_VERSION),
|
||||
test_suite="tests",
|
||||
entry_points={"console_scripts": ["hass = homeassistant.__main__:main"]},
|
||||
)
|
||||
|
||||
@@ -31,14 +31,17 @@ SENSOR = {
|
||||
|
||||
|
||||
ENTRY_CONFIG = {
|
||||
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
|
||||
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
|
||||
deconz.config_flow.CONF_API_KEY: "ABCDEF",
|
||||
deconz.config_flow.CONF_BRIDGEID: "0123456789",
|
||||
deconz.config_flow.CONF_HOST: "1.2.3.4",
|
||||
deconz.config_flow.CONF_PORT: 80,
|
||||
}
|
||||
|
||||
ENTRY_OPTIONS = {
|
||||
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
|
||||
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
|
||||
}
|
||||
|
||||
|
||||
async def setup_gateway(hass, data, allow_clip_sensor=True):
|
||||
"""Load the deCONZ binary sensor platform."""
|
||||
@@ -47,7 +50,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
|
||||
loop = Mock()
|
||||
session = Mock()
|
||||
|
||||
ENTRY_CONFIG[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor
|
||||
ENTRY_OPTIONS[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor
|
||||
|
||||
config_entry = config_entries.ConfigEntry(
|
||||
1,
|
||||
@@ -56,6 +59,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
|
||||
ENTRY_CONFIG,
|
||||
"test",
|
||||
config_entries.CONN_CLASS_LOCAL_PUSH,
|
||||
ENTRY_OPTIONS,
|
||||
)
|
||||
gateway = deconz.DeconzGateway(hass, config_entry)
|
||||
gateway.api = DeconzSession(loop, session, **config_entry.data)
|
||||
|
||||
@@ -39,14 +39,17 @@ SENSOR = {
|
||||
}
|
||||
|
||||
ENTRY_CONFIG = {
|
||||
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
|
||||
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
|
||||
deconz.config_flow.CONF_API_KEY: "ABCDEF",
|
||||
deconz.config_flow.CONF_BRIDGEID: "0123456789",
|
||||
deconz.config_flow.CONF_HOST: "1.2.3.4",
|
||||
deconz.config_flow.CONF_PORT: 80,
|
||||
}
|
||||
|
||||
ENTRY_OPTIONS = {
|
||||
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
|
||||
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
|
||||
}
|
||||
|
||||
|
||||
async def setup_gateway(hass, data, allow_clip_sensor=True):
|
||||
"""Load the deCONZ sensor platform."""
|
||||
@@ -59,7 +62,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
|
||||
|
||||
session = Mock(put=asynctest.CoroutineMock(return_value=response))
|
||||
|
||||
ENTRY_CONFIG[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor
|
||||
ENTRY_OPTIONS[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor
|
||||
|
||||
config_entry = config_entries.ConfigEntry(
|
||||
1,
|
||||
@@ -68,6 +71,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
|
||||
ENTRY_CONFIG,
|
||||
"test",
|
||||
config_entries.CONN_CLASS_LOCAL_PUSH,
|
||||
ENTRY_OPTIONS,
|
||||
)
|
||||
gateway = deconz.DeconzGateway(hass, config_entry)
|
||||
gateway.api = DeconzSession(hass.loop, session, **config_entry.data)
|
||||
|
||||
@@ -62,14 +62,17 @@ SWITCH = {
|
||||
|
||||
|
||||
ENTRY_CONFIG = {
|
||||
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
|
||||
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
|
||||
deconz.config_flow.CONF_API_KEY: "ABCDEF",
|
||||
deconz.config_flow.CONF_BRIDGEID: "0123456789",
|
||||
deconz.config_flow.CONF_HOST: "1.2.3.4",
|
||||
deconz.config_flow.CONF_PORT: 80,
|
||||
}
|
||||
|
||||
ENTRY_OPTIONS = {
|
||||
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
|
||||
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
|
||||
}
|
||||
|
||||
|
||||
async def setup_gateway(hass, data, allow_deconz_groups=True):
|
||||
"""Load the deCONZ light platform."""
|
||||
@@ -78,7 +81,7 @@ async def setup_gateway(hass, data, allow_deconz_groups=True):
|
||||
loop = Mock()
|
||||
session = Mock()
|
||||
|
||||
ENTRY_CONFIG[deconz.const.CONF_ALLOW_DECONZ_GROUPS] = allow_deconz_groups
|
||||
ENTRY_OPTIONS[deconz.const.CONF_ALLOW_DECONZ_GROUPS] = allow_deconz_groups
|
||||
|
||||
config_entry = config_entries.ConfigEntry(
|
||||
1,
|
||||
@@ -87,6 +90,7 @@ async def setup_gateway(hass, data, allow_deconz_groups=True):
|
||||
ENTRY_CONFIG,
|
||||
"test",
|
||||
config_entries.CONN_CLASS_LOCAL_PUSH,
|
||||
ENTRY_OPTIONS,
|
||||
)
|
||||
gateway = deconz.DeconzGateway(hass, config_entry)
|
||||
gateway.api = DeconzSession(loop, session, **config_entry.data)
|
||||
|
||||
@@ -75,14 +75,17 @@ SENSOR = {
|
||||
|
||||
|
||||
ENTRY_CONFIG = {
|
||||
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
|
||||
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
|
||||
deconz.config_flow.CONF_API_KEY: "ABCDEF",
|
||||
deconz.config_flow.CONF_BRIDGEID: "0123456789",
|
||||
deconz.config_flow.CONF_HOST: "1.2.3.4",
|
||||
deconz.config_flow.CONF_PORT: 80,
|
||||
}
|
||||
|
||||
ENTRY_OPTIONS = {
|
||||
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
|
||||
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
|
||||
}
|
||||
|
||||
|
||||
async def setup_gateway(hass, data, allow_clip_sensor=True):
|
||||
"""Load the deCONZ sensor platform."""
|
||||
@@ -91,7 +94,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
|
||||
loop = Mock()
|
||||
session = Mock()
|
||||
|
||||
ENTRY_CONFIG[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor
|
||||
ENTRY_OPTIONS[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor
|
||||
|
||||
config_entry = config_entries.ConfigEntry(
|
||||
1,
|
||||
@@ -100,6 +103,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
|
||||
ENTRY_CONFIG,
|
||||
"test",
|
||||
config_entries.CONN_CLASS_LOCAL_PUSH,
|
||||
ENTRY_OPTIONS,
|
||||
)
|
||||
gateway = deconz.DeconzGateway(hass, config_entry)
|
||||
gateway.api = DeconzSession(loop, session, **config_entry.data)
|
||||
|
||||
@@ -130,44 +130,34 @@ class TestEcobee(unittest.TestCase):
|
||||
"""Test device state attributes property."""
|
||||
self.ecobee["equipmentStatus"] = "heatPump2"
|
||||
assert {
|
||||
"climate_list": ["Climate1", "Climate2"],
|
||||
"fan": "off",
|
||||
"fan_min_on_time": 10,
|
||||
"climate_mode": "Climate1",
|
||||
"equipment_running": "heatPump2",
|
||||
} == self.thermostat.device_state_attributes
|
||||
|
||||
self.ecobee["equipmentStatus"] = "auxHeat2"
|
||||
assert {
|
||||
"climate_list": ["Climate1", "Climate2"],
|
||||
"fan": "off",
|
||||
"fan_min_on_time": 10,
|
||||
"climate_mode": "Climate1",
|
||||
"equipment_running": "auxHeat2",
|
||||
} == self.thermostat.device_state_attributes
|
||||
self.ecobee["equipmentStatus"] = "compCool1"
|
||||
assert {
|
||||
"climate_list": ["Climate1", "Climate2"],
|
||||
"fan": "off",
|
||||
"fan_min_on_time": 10,
|
||||
"climate_mode": "Climate1",
|
||||
"equipment_running": "compCool1",
|
||||
} == self.thermostat.device_state_attributes
|
||||
self.ecobee["equipmentStatus"] = ""
|
||||
assert {
|
||||
"climate_list": ["Climate1", "Climate2"],
|
||||
"fan": "off",
|
||||
"fan_min_on_time": 10,
|
||||
"climate_mode": "Climate1",
|
||||
"equipment_running": "",
|
||||
} == self.thermostat.device_state_attributes
|
||||
|
||||
self.ecobee["equipmentStatus"] = "Unknown"
|
||||
assert {
|
||||
"climate_list": ["Climate1", "Climate2"],
|
||||
"fan": "off",
|
||||
"fan_min_on_time": 10,
|
||||
"climate_mode": "Climate1",
|
||||
"equipment_running": "Unknown",
|
||||
} == self.thermostat.device_state_attributes
|
||||
|
||||
@@ -267,10 +257,6 @@ class TestEcobee(unittest.TestCase):
|
||||
self.ecobee["settings"]["holdAction"] = action
|
||||
assert "nextTransition" == self.thermostat.hold_preference()
|
||||
|
||||
def test_climate_list(self):
|
||||
"""Test climate list property."""
|
||||
assert ["Climate1", "Climate2"] == self.thermostat.climate_list
|
||||
|
||||
def test_set_fan_mode_on(self):
|
||||
"""Test set fan mode to on."""
|
||||
self.data.reset_mock()
|
||||
|
||||
@@ -128,9 +128,6 @@ def hass_hue(loop, hass):
|
||||
kitchen_light_entity.entity_id, kitchen_light_entity.state, attributes=attrs
|
||||
)
|
||||
|
||||
# create a lamp without brightness support
|
||||
hass.states.async_set("light.no_brightness", "on", {})
|
||||
|
||||
# Ceiling Fan is explicitly excluded from being exposed
|
||||
ceiling_fan_entity = hass.states.get("fan.ceiling_fan")
|
||||
attrs = dict(ceiling_fan_entity.attributes)
|
||||
@@ -221,17 +218,6 @@ def test_discover_lights(hue_client):
|
||||
assert "climate.ecobee" not in devices
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_light_without_brightness_supported(hass_hue, hue_client):
|
||||
"""Test that light without brightness is supported."""
|
||||
light_without_brightness_json = yield from perform_get_light_state(
|
||||
hue_client, "light.no_brightness", 200
|
||||
)
|
||||
|
||||
assert light_without_brightness_json["state"][HUE_API_STATE_ON] is True
|
||||
assert light_without_brightness_json["type"] == "On/off light"
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_get_light_state(hass_hue, hue_client):
|
||||
"""Test the getting of light state."""
|
||||
|
||||
@@ -628,3 +628,25 @@ async def test_fetch_period_api(hass, hass_client):
|
||||
"/api/history/period/{}".format(dt_util.utcnow().isoformat())
|
||||
)
|
||||
assert response.status == 200
|
||||
|
||||
|
||||
async def test_fetch_period_api_with_include_order(hass, hass_client):
|
||||
"""Test the fetch period view for history."""
|
||||
await hass.async_add_job(init_recorder_component, hass)
|
||||
await async_setup_component(
|
||||
hass,
|
||||
"history",
|
||||
{
|
||||
"history": {
|
||||
"use_include_order": True,
|
||||
"include": {"entities": ["light.kitchen"]},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_add_job(hass.data[recorder.DATA_INSTANCE].block_till_done)
|
||||
client = await hass_client()
|
||||
response = await client.get(
|
||||
"/api/history/period/{}".format(dt_util.utcnow().isoformat()),
|
||||
params={"filter_entity_id": "non.existing,something.else"},
|
||||
)
|
||||
assert response.status == 200
|
||||
|
||||
30
tests/components/homeassistant/test_scene.py
Normal file
30
tests/components/homeassistant/test_scene.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""Test Home Assistant scenes."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
||||
async def test_reload_config_service(hass):
|
||||
"""Test the reload config service."""
|
||||
assert await async_setup_component(hass, "scene", {})
|
||||
|
||||
with patch(
|
||||
"homeassistant.config.load_yaml_config_file",
|
||||
autospec=True,
|
||||
return_value={"scene": {"name": "Hallo", "entities": {"light.kitchen": "on"}}},
|
||||
), patch("homeassistant.config.find_config_file", return_value=""):
|
||||
await hass.services.async_call("scene", "reload", blocking=True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("scene.hallo") is not None
|
||||
|
||||
with patch(
|
||||
"homeassistant.config.load_yaml_config_file",
|
||||
autospec=True,
|
||||
return_value={"scene": {"name": "Bye", "entities": {"light.kitchen": "on"}}},
|
||||
), patch("homeassistant.config.find_config_file", return_value=""):
|
||||
await hass.services.async_call("scene", "reload", blocking=True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("scene.hallo") is None
|
||||
assert hass.states.get("scene.bye") is not None
|
||||
@@ -232,3 +232,12 @@ async def test_ssl_profile_change_modern(hass):
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_context.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_cors_defaults(hass):
|
||||
"""Test the CORS default settings."""
|
||||
with patch("homeassistant.components.http.setup_cors") as mock_setup:
|
||||
assert await async_setup_component(hass, "http", {})
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert mock_setup.mock_calls[0][1][1] == ["https://cast.home-assistant.io"]
|
||||
|
||||
@@ -84,6 +84,7 @@ async def test_entity_state(hass, light_devices):
|
||||
state.attributes[ATTR_SUPPORTED_FEATURES]
|
||||
== SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION
|
||||
)
|
||||
assert isinstance(state.attributes[ATTR_BRIGHTNESS], int)
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 255
|
||||
|
||||
# Color Dimmer 1
|
||||
@@ -103,6 +104,7 @@ async def test_entity_state(hass, light_devices):
|
||||
)
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 255
|
||||
assert state.attributes[ATTR_HS_COLOR] == (273.6, 55.0)
|
||||
assert isinstance(state.attributes[ATTR_COLOR_TEMP], int)
|
||||
assert state.attributes[ATTR_COLOR_TEMP] == 222
|
||||
|
||||
|
||||
@@ -191,7 +193,7 @@ async def test_turn_on_with_brightness(hass, light_devices):
|
||||
assert state is not None
|
||||
assert state.state == "on"
|
||||
# round-trip rounding error (expected)
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 73.95
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 74
|
||||
|
||||
|
||||
async def test_turn_on_with_minimal_brightness(hass, light_devices):
|
||||
@@ -216,7 +218,7 @@ async def test_turn_on_with_minimal_brightness(hass, light_devices):
|
||||
assert state is not None
|
||||
assert state.state == "on"
|
||||
# round-trip rounding error (expected)
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 2.55
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 3
|
||||
|
||||
|
||||
async def test_turn_on_with_color(hass, light_devices):
|
||||
|
||||
@@ -22,6 +22,7 @@ from homeassistant.const import (
|
||||
CONF_PORT,
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
from homeassistant.helpers import entity_registry
|
||||
from homeassistant.setup import async_setup_component
|
||||
@@ -72,6 +73,17 @@ DEVICE_1 = {
|
||||
"upgradable": False,
|
||||
"version": "4.0.42.10433",
|
||||
}
|
||||
DEVICE_2 = {
|
||||
"board_rev": 3,
|
||||
"device_id": "mock-id",
|
||||
"has_fan": True,
|
||||
"ip": "10.0.1.1",
|
||||
"mac": "00:00:00:00:01:01",
|
||||
"model": "US16P150",
|
||||
"name": "device_1",
|
||||
"type": "usw",
|
||||
"version": "4.0.42.10433",
|
||||
}
|
||||
|
||||
CONTROLLER_DATA = {
|
||||
CONF_HOST: "mock-host",
|
||||
@@ -166,7 +178,7 @@ async def test_no_clients(hass, mock_controller):
|
||||
async def test_tracked_devices(hass, mock_controller):
|
||||
"""Test the update_items function with some clients."""
|
||||
mock_controller.mock_client_responses.append([CLIENT_1, CLIENT_2, CLIENT_3])
|
||||
mock_controller.mock_device_responses.append([DEVICE_1])
|
||||
mock_controller.mock_device_responses.append([DEVICE_1, DEVICE_2])
|
||||
mock_controller.unifi_config = {unifi_dt.CONF_SSID_FILTER: ["ssid"]}
|
||||
|
||||
await setup_controller(hass, mock_controller)
|
||||
@@ -203,6 +215,16 @@ async def test_tracked_devices(hass, mock_controller):
|
||||
device_1 = hass.states.get("device_tracker.device_1")
|
||||
assert device_1.state == "home"
|
||||
|
||||
device_1_copy = copy(DEVICE_1)
|
||||
device_1_copy["disabled"] = True
|
||||
mock_controller.mock_client_responses.append({})
|
||||
mock_controller.mock_device_responses.append([device_1_copy])
|
||||
await mock_controller.async_update()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device_1 = hass.states.get("device_tracker.device_1")
|
||||
assert device_1.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_restoring_client(hass, mock_controller):
|
||||
"""Test the update_items function with some clients."""
|
||||
@@ -233,3 +255,57 @@ async def test_restoring_client(hass, mock_controller):
|
||||
|
||||
device_1 = hass.states.get("device_tracker.client_1")
|
||||
assert device_1 is not None
|
||||
|
||||
|
||||
async def test_dont_track_clients(hass, mock_controller):
|
||||
"""Test dont track clients config works."""
|
||||
mock_controller.mock_client_responses.append([CLIENT_1])
|
||||
mock_controller.mock_device_responses.append([DEVICE_1])
|
||||
mock_controller.unifi_config = {unifi.CONF_DONT_TRACK_CLIENTS: True}
|
||||
|
||||
await setup_controller(hass, mock_controller)
|
||||
assert len(mock_controller.mock_requests) == 2
|
||||
assert len(hass.states.async_all()) == 3
|
||||
|
||||
client_1 = hass.states.get("device_tracker.client_1")
|
||||
assert client_1 is None
|
||||
|
||||
device_1 = hass.states.get("device_tracker.device_1")
|
||||
assert device_1 is not None
|
||||
assert device_1.state == "not_home"
|
||||
|
||||
|
||||
async def test_dont_track_devices(hass, mock_controller):
|
||||
"""Test dont track devices config works."""
|
||||
mock_controller.mock_client_responses.append([CLIENT_1])
|
||||
mock_controller.mock_device_responses.append([DEVICE_1])
|
||||
mock_controller.unifi_config = {unifi.CONF_DONT_TRACK_DEVICES: True}
|
||||
|
||||
await setup_controller(hass, mock_controller)
|
||||
assert len(mock_controller.mock_requests) == 2
|
||||
assert len(hass.states.async_all()) == 3
|
||||
|
||||
client_1 = hass.states.get("device_tracker.client_1")
|
||||
assert client_1 is not None
|
||||
assert client_1.state == "not_home"
|
||||
|
||||
device_1 = hass.states.get("device_tracker.device_1")
|
||||
assert device_1 is None
|
||||
|
||||
|
||||
async def test_dont_track_wired_clients(hass, mock_controller):
|
||||
"""Test dont track wired clients config works."""
|
||||
mock_controller.mock_client_responses.append([CLIENT_1, CLIENT_2])
|
||||
mock_controller.mock_device_responses.append({})
|
||||
mock_controller.unifi_config = {unifi.CONF_DONT_TRACK_WIRED_CLIENTS: True}
|
||||
|
||||
await setup_controller(hass, mock_controller)
|
||||
assert len(mock_controller.mock_requests) == 2
|
||||
assert len(hass.states.async_all()) == 3
|
||||
|
||||
client_1 = hass.states.get("device_tracker.client_1")
|
||||
assert client_1 is not None
|
||||
assert client_1.state == "not_home"
|
||||
|
||||
client_2 = hass.states.get("device_tracker.client_2")
|
||||
assert client_2 is None
|
||||
|
||||
@@ -342,11 +342,11 @@ async def test_switches(hass, mock_controller):
|
||||
|
||||
blocked = hass.states.get("switch.block_client_1")
|
||||
assert blocked is not None
|
||||
assert blocked.state == "on"
|
||||
assert blocked.state == "off"
|
||||
|
||||
unblocked = hass.states.get("switch.block_client_2")
|
||||
assert unblocked is not None
|
||||
assert unblocked.state == "off"
|
||||
assert unblocked.state == "on"
|
||||
|
||||
|
||||
async def test_new_client_discovered(hass, mock_controller):
|
||||
|
||||
@@ -2,11 +2,23 @@
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.climate.const import (
|
||||
CURRENT_HVAC_HEAT,
|
||||
CURRENT_HVAC_COOL,
|
||||
HVAC_MODES,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_OFF,
|
||||
PRESET_BOOST,
|
||||
PRESET_ECO,
|
||||
PRESET_NONE,
|
||||
SUPPORT_FAN_MODE,
|
||||
SUPPORT_PRESET_MODE,
|
||||
SUPPORT_SWING_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.components.zwave import climate
|
||||
from homeassistant.components.zwave.climate import DEFAULT_HVAC_MODES
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
|
||||
from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed
|
||||
@@ -19,9 +31,18 @@ def device(hass, mock_openzwave):
|
||||
values = MockEntityValues(
|
||||
primary=MockValue(data=1, node=node),
|
||||
temperature=MockValue(data=5, node=node, units=None),
|
||||
mode=MockValue(data="test1", data_items=[0, 1, 2], node=node),
|
||||
mode=MockValue(
|
||||
data=HVAC_MODE_HEAT,
|
||||
data_items=[
|
||||
HVAC_MODE_OFF,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
],
|
||||
node=node,
|
||||
),
|
||||
fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node),
|
||||
operating_state=MockValue(data=6, node=node),
|
||||
operating_state=MockValue(data=CURRENT_HVAC_HEAT, node=node),
|
||||
fan_state=MockValue(data=7, node=node),
|
||||
)
|
||||
device = climate.get_device(hass, node=node, values=values, node_config={})
|
||||
@@ -37,9 +58,18 @@ def device_zxt_120(hass, mock_openzwave):
|
||||
values = MockEntityValues(
|
||||
primary=MockValue(data=1, node=node),
|
||||
temperature=MockValue(data=5, node=node, units=None),
|
||||
mode=MockValue(data="test1", data_items=[0, 1, 2], node=node),
|
||||
mode=MockValue(
|
||||
data=HVAC_MODE_HEAT,
|
||||
data_items=[
|
||||
HVAC_MODE_OFF,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
],
|
||||
node=node,
|
||||
),
|
||||
fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node),
|
||||
operating_state=MockValue(data=6, node=node),
|
||||
operating_state=MockValue(data=CURRENT_HVAC_HEAT, node=node),
|
||||
fan_state=MockValue(data=7, node=node),
|
||||
zxt_120_swing_mode=MockValue(data="test3", data_items=[6, 7, 8], node=node),
|
||||
)
|
||||
@@ -55,9 +85,13 @@ def device_mapping(hass, mock_openzwave):
|
||||
values = MockEntityValues(
|
||||
primary=MockValue(data=1, node=node),
|
||||
temperature=MockValue(data=5, node=node, units=None),
|
||||
mode=MockValue(data="Off", data_items=["Off", "Cool", "Heat"], node=node),
|
||||
mode=MockValue(
|
||||
data="Heat",
|
||||
data_items=["Off", "Cool", "Heat", "Full Power", "heat_cool"],
|
||||
node=node,
|
||||
),
|
||||
fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node),
|
||||
operating_state=MockValue(data=6, node=node),
|
||||
operating_state=MockValue(data="heating", node=node),
|
||||
fan_state=MockValue(data=7, node=node),
|
||||
)
|
||||
device = climate.get_device(hass, node=node, values=values, node_config={})
|
||||
@@ -65,6 +99,83 @@ def device_mapping(hass, mock_openzwave):
|
||||
yield device
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def device_unknown(hass, mock_openzwave):
|
||||
"""Fixture to provide a precreated climate device. Test state unknown."""
|
||||
node = MockNode()
|
||||
values = MockEntityValues(
|
||||
primary=MockValue(data=1, node=node),
|
||||
temperature=MockValue(data=5, node=node, units=None),
|
||||
mode=MockValue(
|
||||
data="Heat",
|
||||
data_items=["Off", "Cool", "Heat", "heat_cool", "Abcdefg"],
|
||||
node=node,
|
||||
),
|
||||
fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node),
|
||||
operating_state=MockValue(data="test4", node=node),
|
||||
fan_state=MockValue(data=7, node=node),
|
||||
)
|
||||
device = climate.get_device(hass, node=node, values=values, node_config={})
|
||||
|
||||
yield device
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def device_heat_cool(hass, mock_openzwave):
|
||||
"""Fixture to provide a precreated climate device. Test state heat only."""
|
||||
node = MockNode()
|
||||
values = MockEntityValues(
|
||||
primary=MockValue(data=1, node=node),
|
||||
temperature=MockValue(data=5, node=node, units=None),
|
||||
mode=MockValue(
|
||||
data=HVAC_MODE_HEAT,
|
||||
data_items=[
|
||||
HVAC_MODE_OFF,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_COOL,
|
||||
"Heat Eco",
|
||||
"Cool Eco",
|
||||
],
|
||||
node=node,
|
||||
),
|
||||
fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node),
|
||||
operating_state=MockValue(data="test4", node=node),
|
||||
fan_state=MockValue(data=7, node=node),
|
||||
)
|
||||
device = climate.get_device(hass, node=node, values=values, node_config={})
|
||||
|
||||
yield device
|
||||
|
||||
|
||||
def test_default_hvac_modes():
|
||||
"""Test wether all hvac modes are included in default_hvac_modes."""
|
||||
for hvac_mode in HVAC_MODES:
|
||||
assert hvac_mode in DEFAULT_HVAC_MODES
|
||||
|
||||
|
||||
def test_supported_features(device):
|
||||
"""Test supported features flags."""
|
||||
assert device.supported_features == SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
|
||||
def test_supported_features_preset_mode(device_mapping):
|
||||
"""Test supported features flags with swing mode."""
|
||||
device = device_mapping
|
||||
assert (
|
||||
device.supported_features
|
||||
== SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE + SUPPORT_PRESET_MODE
|
||||
)
|
||||
|
||||
|
||||
def test_supported_features_swing_mode(device_zxt_120):
|
||||
"""Test supported features flags with swing mode."""
|
||||
device = device_zxt_120
|
||||
assert (
|
||||
device.supported_features
|
||||
== SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE + SUPPORT_SWING_MODE
|
||||
)
|
||||
|
||||
|
||||
def test_zxt_120_swing_mode(device_zxt_120):
|
||||
"""Test operation of the zxt 120 swing mode."""
|
||||
device = device_zxt_120
|
||||
@@ -107,7 +218,24 @@ def test_default_target_temperature(device):
|
||||
def test_data_lists(device):
|
||||
"""Test data lists from zwave value items."""
|
||||
assert device.fan_modes == [3, 4, 5]
|
||||
assert device.hvac_modes == [0, 1, 2]
|
||||
assert device.hvac_modes == [
|
||||
HVAC_MODE_OFF,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
]
|
||||
assert device.preset_modes == []
|
||||
device.values.mode = None
|
||||
assert device.preset_modes == []
|
||||
|
||||
|
||||
def test_data_lists_mapping(device_mapping):
|
||||
"""Test data lists from zwave value items."""
|
||||
device = device_mapping
|
||||
assert device.hvac_modes == ["off", "cool", "heat", "heat_cool"]
|
||||
assert device.preset_modes == ["boost", "none"]
|
||||
device.values.mode = None
|
||||
assert device.preset_modes == []
|
||||
|
||||
|
||||
def test_target_value_set(device):
|
||||
@@ -121,21 +249,56 @@ def test_target_value_set(device):
|
||||
|
||||
def test_operation_value_set(device):
|
||||
"""Test values changed for climate device."""
|
||||
assert device.values.mode.data == "test1"
|
||||
device.set_hvac_mode("test_set")
|
||||
assert device.values.mode.data == "test_set"
|
||||
assert device.values.mode.data == HVAC_MODE_HEAT
|
||||
device.set_hvac_mode(HVAC_MODE_COOL)
|
||||
assert device.values.mode.data == HVAC_MODE_COOL
|
||||
device.set_preset_mode(PRESET_ECO)
|
||||
assert device.values.mode.data == PRESET_ECO
|
||||
device.set_preset_mode(PRESET_NONE)
|
||||
assert device.values.mode.data == HVAC_MODE_HEAT_COOL
|
||||
device.values.mode = None
|
||||
device.set_hvac_mode("test_set_failes")
|
||||
assert device.values.mode is None
|
||||
device.set_preset_mode("test_set_failes")
|
||||
assert device.values.mode is None
|
||||
|
||||
|
||||
def test_operation_value_set_mapping(device_mapping):
|
||||
"""Test values changed for climate device. Mapping."""
|
||||
device = device_mapping
|
||||
assert device.values.mode.data == "Off"
|
||||
device.set_hvac_mode(HVAC_MODE_HEAT)
|
||||
assert device.values.mode.data == "Heat"
|
||||
device.set_hvac_mode(HVAC_MODE_COOL)
|
||||
assert device.values.mode.data == "Cool"
|
||||
device.set_hvac_mode(HVAC_MODE_OFF)
|
||||
assert device.values.mode.data == "Off"
|
||||
device.set_preset_mode(PRESET_BOOST)
|
||||
assert device.values.mode.data == "Full Power"
|
||||
device.set_preset_mode(PRESET_ECO)
|
||||
assert device.values.mode.data == "eco"
|
||||
|
||||
|
||||
def test_operation_value_set_unknown(device_unknown):
|
||||
"""Test values changed for climate device. Unknown."""
|
||||
device = device_unknown
|
||||
assert device.values.mode.data == "Heat"
|
||||
device.set_preset_mode("Abcdefg")
|
||||
assert device.values.mode.data == "Abcdefg"
|
||||
device.set_preset_mode(PRESET_NONE)
|
||||
assert device.values.mode.data == HVAC_MODE_HEAT_COOL
|
||||
|
||||
|
||||
def test_operation_value_set_heat_cool(device_heat_cool):
|
||||
"""Test values changed for climate device. Heat/Cool only."""
|
||||
device = device_heat_cool
|
||||
assert device.values.mode.data == HVAC_MODE_HEAT
|
||||
device.set_preset_mode("Heat Eco")
|
||||
assert device.values.mode.data == "Heat Eco"
|
||||
device.set_preset_mode(PRESET_NONE)
|
||||
assert device.values.mode.data == HVAC_MODE_HEAT
|
||||
device.set_preset_mode("Cool Eco")
|
||||
assert device.values.mode.data == "Cool Eco"
|
||||
device.set_preset_mode(PRESET_NONE)
|
||||
assert device.values.mode.data == HVAC_MODE_COOL
|
||||
|
||||
|
||||
def test_fan_mode_value_set(device):
|
||||
@@ -143,6 +306,9 @@ def test_fan_mode_value_set(device):
|
||||
assert device.values.fan_mode.data == "test2"
|
||||
device.set_fan_mode("test_fan_set")
|
||||
assert device.values.fan_mode.data == "test_fan_set"
|
||||
device.values.fan_mode = None
|
||||
device.set_fan_mode("test_fan_set_failes")
|
||||
assert device.values.fan_mode is None
|
||||
|
||||
|
||||
def test_target_value_changed(device):
|
||||
@@ -163,25 +329,85 @@ def test_temperature_value_changed(device):
|
||||
|
||||
def test_operation_value_changed(device):
|
||||
"""Test values changed for climate device."""
|
||||
assert device.hvac_mode == "test1"
|
||||
device.values.mode.data = "test_updated"
|
||||
assert device.hvac_mode == HVAC_MODE_HEAT
|
||||
assert device.preset_mode == PRESET_NONE
|
||||
device.values.mode.data = HVAC_MODE_COOL
|
||||
value_changed(device.values.mode)
|
||||
assert device.hvac_mode == "test_updated"
|
||||
assert device.hvac_mode == HVAC_MODE_COOL
|
||||
assert device.preset_mode == PRESET_NONE
|
||||
device.values.mode.data = HVAC_MODE_OFF
|
||||
value_changed(device.values.mode)
|
||||
assert device.hvac_mode == HVAC_MODE_OFF
|
||||
assert device.preset_mode == PRESET_NONE
|
||||
device.values.mode = None
|
||||
assert device.hvac_mode == HVAC_MODE_HEAT_COOL
|
||||
assert device.preset_mode == PRESET_NONE
|
||||
|
||||
|
||||
def test_operation_value_changed_preset(device_mapping):
|
||||
"""Test preset changed for climate device."""
|
||||
device = device_mapping
|
||||
assert device.hvac_mode == HVAC_MODE_HEAT
|
||||
assert device.preset_mode == PRESET_NONE
|
||||
device.values.mode.data = PRESET_ECO
|
||||
value_changed(device.values.mode)
|
||||
assert device.hvac_mode == HVAC_MODE_HEAT_COOL
|
||||
assert device.preset_mode == PRESET_ECO
|
||||
|
||||
|
||||
def test_operation_value_changed_mapping(device_mapping):
|
||||
"""Test values changed for climate device. Mapping."""
|
||||
device = device_mapping
|
||||
assert device.hvac_mode == "off"
|
||||
device.values.mode.data = "Heat"
|
||||
value_changed(device.values.mode)
|
||||
assert device.hvac_mode == HVAC_MODE_HEAT
|
||||
device.values.mode.data = "Cool"
|
||||
value_changed(device.values.mode)
|
||||
assert device.hvac_mode == HVAC_MODE_COOL
|
||||
assert device.preset_mode == PRESET_NONE
|
||||
device.values.mode.data = "Off"
|
||||
value_changed(device.values.mode)
|
||||
assert device.hvac_mode == HVAC_MODE_OFF
|
||||
assert device.preset_mode == PRESET_NONE
|
||||
device.values.mode.data = "Cool"
|
||||
value_changed(device.values.mode)
|
||||
assert device.hvac_mode == HVAC_MODE_COOL
|
||||
assert device.preset_mode == PRESET_NONE
|
||||
|
||||
|
||||
def test_operation_value_changed_mapping_preset(device_mapping):
|
||||
"""Test values changed for climate device. Mapping with presets."""
|
||||
device = device_mapping
|
||||
assert device.hvac_mode == HVAC_MODE_HEAT
|
||||
assert device.preset_mode == PRESET_NONE
|
||||
device.values.mode.data = "Full Power"
|
||||
value_changed(device.values.mode)
|
||||
assert device.hvac_mode == HVAC_MODE_HEAT_COOL
|
||||
assert device.preset_mode == PRESET_BOOST
|
||||
device.values.mode = None
|
||||
assert device.hvac_mode == HVAC_MODE_HEAT_COOL
|
||||
assert device.preset_mode == PRESET_NONE
|
||||
|
||||
|
||||
def test_operation_value_changed_unknown(device_unknown):
|
||||
"""Test preset changed for climate device. Unknown."""
|
||||
device = device_unknown
|
||||
assert device.hvac_mode == HVAC_MODE_HEAT
|
||||
assert device.preset_mode == PRESET_NONE
|
||||
device.values.mode.data = "Abcdefg"
|
||||
value_changed(device.values.mode)
|
||||
assert device.hvac_mode == HVAC_MODE_HEAT_COOL
|
||||
assert device.preset_mode == "Abcdefg"
|
||||
|
||||
|
||||
def test_operation_value_changed_heat_cool(device_heat_cool):
|
||||
"""Test preset changed for climate device. Heat/Cool only."""
|
||||
device = device_heat_cool
|
||||
assert device.hvac_mode == HVAC_MODE_HEAT
|
||||
assert device.preset_mode == PRESET_NONE
|
||||
device.values.mode.data = "Cool Eco"
|
||||
value_changed(device.values.mode)
|
||||
assert device.hvac_mode == HVAC_MODE_COOL
|
||||
assert device.preset_mode == "Cool Eco"
|
||||
device.values.mode.data = "Heat Eco"
|
||||
value_changed(device.values.mode)
|
||||
assert device.hvac_mode == HVAC_MODE_HEAT
|
||||
assert device.preset_mode == "Heat Eco"
|
||||
|
||||
|
||||
def test_fan_mode_value_changed(device):
|
||||
@@ -190,3 +416,29 @@ def test_fan_mode_value_changed(device):
|
||||
device.values.fan_mode.data = "test_updated_fan"
|
||||
value_changed(device.values.fan_mode)
|
||||
assert device.fan_mode == "test_updated_fan"
|
||||
|
||||
|
||||
def test_hvac_action_value_changed(device):
|
||||
"""Test values changed for climate device."""
|
||||
assert device.hvac_action == CURRENT_HVAC_HEAT
|
||||
device.values.operating_state.data = CURRENT_HVAC_COOL
|
||||
value_changed(device.values.operating_state)
|
||||
assert device.hvac_action == CURRENT_HVAC_COOL
|
||||
|
||||
|
||||
def test_hvac_action_value_changed_mapping(device_mapping):
|
||||
"""Test values changed for climate device."""
|
||||
device = device_mapping
|
||||
assert device.hvac_action == CURRENT_HVAC_HEAT
|
||||
device.values.operating_state.data = "cooling"
|
||||
value_changed(device.values.operating_state)
|
||||
assert device.hvac_action == CURRENT_HVAC_COOL
|
||||
|
||||
|
||||
def test_hvac_action_value_changed_unknown(device_unknown):
|
||||
"""Test values changed for climate device."""
|
||||
device = device_unknown
|
||||
assert device.hvac_action == "test4"
|
||||
device.values.operating_state.data = "another_hvac_action"
|
||||
value_changed(device.values.operating_state)
|
||||
assert device.hvac_action == "another_hvac_action"
|
||||
|
||||
@@ -116,7 +116,7 @@ async def test_setup_recovers_when_setup_raises(hass):
|
||||
|
||||
|
||||
@asynctest.patch(
|
||||
"homeassistant.helpers.entity_component.EntityComponent" "._async_setup_platform",
|
||||
"homeassistant.helpers.entity_component.EntityComponent" ".async_setup_platform",
|
||||
return_value=mock_coro(),
|
||||
)
|
||||
@asynctest.patch(
|
||||
|
||||
@@ -103,7 +103,7 @@ def test_secrets(isfile_patch, loop):
|
||||
assert res["components"].keys() == {"homeassistant", "http"}
|
||||
assert res["components"]["http"] == {
|
||||
"api_password": "abc123",
|
||||
"cors_allowed_origins": [],
|
||||
"cors_allowed_origins": ["https://cast.home-assistant.io"],
|
||||
"ip_ban_enabled": True,
|
||||
"login_attempts_threshold": -1,
|
||||
"server_host": "0.0.0.0",
|
||||
|
||||
@@ -14,6 +14,7 @@ LABEL maintainer="Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
|
||||
#ENV INSTALL_SSOCR no
|
||||
#ENV INSTALL_DLIB no
|
||||
#ENV INSTALL_IPERF3 no
|
||||
#ENV INSTALL_LOCALES no
|
||||
|
||||
VOLUME /config
|
||||
|
||||
|
||||
12
virtualization/Docker/scripts/locales
Executable file
12
virtualization/Docker/scripts/locales
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Sets up locales.
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends locales
|
||||
|
||||
# Set the locale
|
||||
sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
|
||||
locale-gen
|
||||
@@ -9,6 +9,7 @@ INSTALL_OPENALPR="${INSTALL_OPENALPR:-yes}"
|
||||
INSTALL_LIBCEC="${INSTALL_LIBCEC:-yes}"
|
||||
INSTALL_SSOCR="${INSTALL_SSOCR:-yes}"
|
||||
INSTALL_DLIB="${INSTALL_DLIB:-yes}"
|
||||
INSTALL_LOCALES="${INSTALL_LOCALES:-yes}"
|
||||
|
||||
# Required debian packages for running hass or components
|
||||
PACKAGES=(
|
||||
@@ -70,6 +71,10 @@ if [ "$INSTALL_DLIB" == "yes" ]; then
|
||||
pip3 install --no-cache-dir "dlib>=19.5"
|
||||
fi
|
||||
|
||||
if [ "$INSTALL_LOCALES" == "yes" ]; then
|
||||
virtualization/Docker/scripts/locales
|
||||
fi
|
||||
|
||||
# Remove packages
|
||||
apt-get remove -y --purge ${PACKAGES_DEV[@]}
|
||||
apt-get -y --purge autoremove
|
||||
|
||||
Reference in New Issue
Block a user