Compare commits

..

45 Commits

Author SHA1 Message Date
Paulus Schoutsen
7cbb818dc3 Merge pull request #25819 from home-assistant/rc
0.97.1
2019-08-09 13:17:39 -07:00
Santobert
77d984e980 Add script to install locale (#25791) 2019-08-09 13:17:12 -07:00
Andrew Sayre
e57ecc9d7d Fix brightness type (#25818) 2019-08-09 11:42:09 -07:00
Paulus Schoutsen
e1fee1bd45 Bumped version to 0.97.1 2019-08-09 11:17:31 -07:00
Nikolay Vasilchuk
20e279a7ac Fix deconz allow_clip_sensor and allow_deconz_groups options (#25811) 2019-08-09 11:17:24 -07:00
jjlawren
34b5083c27 Don't track unstable attributes (#25787) 2019-08-09 11:17:23 -07:00
Finbarr Brady
ebf8d5fc66 Update Cisco Mobility Express module version (#25770)
* Update manifest.json

* Update requirements_all.txt
2019-08-09 11:17:22 -07:00
Dustin Essington
e355012229 Update HIBP sensor to use API v3 and API Key (#25699)
* Update HIBP sensor to use API v3 and API Key

* ran black code formatter

* fixed stray , that was invalid in multiple json formatters
2019-08-09 11:17:21 -07:00
Paulus Schoutsen
dffdbda8e2 Merge pull request #25756 from home-assistant/rc
0.97.0
2019-08-07 11:23:42 -07:00
Paulus Schoutsen
a20c631410 Update requirements 2019-08-07 10:35:24 -07:00
Paulus Schoutsen
0f8f4f4b54 Bumped version to 0.97.0 2019-08-07 09:25:10 -07:00
Robert Svensson
609118d3ac Fix last seen not available on certain devices (#25735) 2019-08-07 09:23:24 -07:00
Paulus Schoutsen
52de2f4ffb Revert emulated hue changes (#25732) 2019-08-07 09:23:23 -07:00
David Bonnes
a1302a9dfb initial commit (#25731) 2019-08-07 09:23:23 -07:00
Tsvi Mostovicz
3a78250cad Bump hdate==0.9.0 (use pytz instead of dateutil) (#25726)
Use new hdate version of library which uses pytz for timezones.
dateutil expects /usr/share/timezone files, as these are not available
in the docker image and in HASSIO, the timezone offsets are broken.

This should fix
 - #23032
 - #18731
2019-08-07 09:23:22 -07:00
Paulus Schoutsen
d702b17a4f Bumped version to 0.97.0b3 2019-08-06 09:00:20 -07:00
Robert Svensson
27cfda11f7 UniFi - handle device not having a name (#25713)
* Handle device not having a name
2019-08-06 08:59:00 -07:00
Paulus Schoutsen
fee1568a85 Update HTTP defaults (#25702)
* Update HTTP defaults

* Fix tests
2019-08-06 08:58:59 -07:00
Paulus Schoutsen
eceef82ffa Add service to reload scenes from configuration.yaml (#25680)
* Allow reloading scenes

* Update requirements

* address comments

* fix typing

* fix tests

* Update homeassistant/components/homeassistant/scene.py

Co-Authored-By: Martin Hjelmare <marhje52@kth.se>

* Address comments
2019-08-06 08:57:54 -07:00
Jesse Rizzo
b011dd0b02 Bump envoy_reader to 0.8.6, fix missing dependency (#25679)
* Bump envoy_reader to 0.8.6, fix missing dependency

* Bump envoy_reader to 0.8.6, fix missing dependency
2019-08-06 08:56:22 -07:00
SukramJ
e1c23b1686 Add HmIP-SCI to Homematic IP Cloud, Fix HmIP-SWDM (#25639)
* Add HmIP-SCI to Homematic IP Cloud

* Bump upstream dependency

* Fix HmIP-SWDM
2019-08-06 08:56:21 -07:00
Paulus Schoutsen
387323a8c1 Updated frontend to 20190805.0 2019-08-05 22:32:18 -07:00
Paulus Schoutsen
6e61b21919 Bumped version to 0.97.0b2 2019-08-04 22:50:46 -07:00
Pascal Vizeli
868c6f4f71 Fix roku lxml requirement (#25696) 2019-08-04 22:49:03 -07:00
Robert Svensson
4e2094c893 UniFi - reverse connectivity logic (#25691)
* Make connectivity control in line with other implementations
2019-08-04 22:49:02 -07:00
Robert Svensson
2925f9e57a In some circumstances device.last_seen can be None (#25690) 2019-08-04 22:49:01 -07:00
Aaron Bach
f8753a0c92 Fix issue with incorrect Notion bridge IDs (#25683)
* Fix issue with incorrect Notion bridge IDs

* Less aggressive

* Member comments
2019-08-04 22:49:01 -07:00
Robert Svensson
7a71669027 Options to not track wired clients (#25669) 2019-08-04 22:49:00 -07:00
Anders Melchiorsen
476607787a Revert flux_led to 0.89 (#25653)
* Revert Black

* Revert "Introduce support for color temperature (#25503)"

This reverts commit e1d884a484.

* Revert "Fix flux_led only-white controllers (#22210)"

This reverts commit 48138189b3.

* Revert "Fix MagicHome LEDs with flux_led component (#20733)"

This reverts commit 1444a684e0.

* Re-Black

* Use mode detection for scanned bulbs
2019-08-04 22:48:59 -07:00
Paulus Schoutsen
d95c86e964 Add preset to be away and eco (#25643) 2019-08-04 22:48:58 -07:00
Robert Svensson
b40d324e0e UniFi - allow configuration to not track clients or devices (#25642)
* Allow configuration to not track clients or devices
2019-08-04 22:48:57 -07:00
SukramJ
e001b12430 Add PRESET_AWAY to HomematicIP Cloud climate (#25641)
* enable climate away_mode and home.refresh

* Add Party eco modes
2019-08-04 22:48:57 -07:00
Santobert
8d1deef9cc Feature zwave preset modes (#25537)
* Initial commit

* Add some more code

* Local tests passing

* Remove unnecessary line

* Add preset attributes to __init__

* Remove some more debugger lines

* Add some tests

* Fix comparision to None

* Improve test coverage

* Use unknown modes as presets

* Bugfixes and test improvements

* Add tests for unknown preset modes

* linting

* Improve mappings

* Move PRESET_MANUFACTURER_SPECIFIC to zwave

* Replace isinstance with cast

* Add test for hvac_action

* hvac_mode is never None

* Improved mapping of current mode to hvac/preset modes

* Fix bugs where hvac_mode is None

* Add default hvac mode

* Fixed default hvac mode

* Fix linting

* Make flake happy

* Another linting

* Make black happy

* Complete list of default hvac modes

* Add mapping to heat/cool eco

* Fixed another bug where mapping goes wrong
2019-08-04 22:48:56 -07:00
Paulus Schoutsen
949875ae50 Updated frontend to 20190804.0 2019-08-04 22:30:54 -07:00
Paulus Schoutsen
ad341e2152 Bumped version to 0.97.0b1 2019-08-01 13:36:24 -07:00
Paulus Schoutsen
d64730a3cf Updated frontend to 20190801.0 2019-08-01 13:35:20 -07:00
Paulus Schoutsen
a8c4fc33f6 Upgrade hass-nabucasa to 0.16 (#25636) 2019-08-01 13:35:13 -07:00
Paulus Schoutsen
476a727df3 Filter out empty results in history API (#25633) 2019-08-01 13:35:12 -07:00
Jc2k
1d5709f49f Bump homekit_python to 0.15 (#25631) 2019-08-01 13:35:11 -07:00
Oncleben31
725d5c636e Meteofrance improve log error messages (#25630)
* Improve log error messages

* remove unique_id not ready yet
2019-08-01 13:35:11 -07:00
Jc2k
414b85c253 Fix polling HomeKit devices with multiple services per accessory (#25629) 2019-08-01 13:35:10 -07:00
Robert Svensson
56ca0edaa7 Handle disabled devices (#25625) 2019-08-01 13:35:09 -07:00
David F. Mulcahey
7168dd6cec bump quirks (#25618) 2019-08-01 13:35:08 -07:00
Martin Eberhardt
d3f6c43bbd Fix handling of empty results from Rejseplanen (#25610)
* Improve handling of empty results from Rejseplanen (Fixes #25566)

* Exclude attributes with null value

* Add period back into docstring

* Fix formatting
2019-08-01 13:35:08 -07:00
Paulus Schoutsen
59b42b4236 Expose comfort presets as HA presets (#25491)
* Expose comfort presets as HA presets

* Fix bugs

* Handle unavailable

* log level debug on update

* Lint
2019-08-01 13:35:07 -07:00
66 changed files with 1095 additions and 470 deletions

View File

@@ -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

View File

@@ -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"]

View File

@@ -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"]
}

View File

@@ -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."""

View File

@@ -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):

View File

@@ -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))

View File

@@ -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,

View File

@@ -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": []

View File

@@ -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]

View File

@@ -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",

View File

@@ -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": []
}

View File

@@ -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)

View File

@@ -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:

View File

@@ -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.

View File

@@ -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"])

View File

@@ -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."],

View File

@@ -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,

View File

@@ -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):

View File

@@ -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."""

View File

@@ -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": []

View File

@@ -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,

View File

@@ -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

View File

@@ -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": [

View File

@@ -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": [

View File

@@ -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
)

View File

@@ -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.

View File

@@ -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):

View File

@@ -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"]

View File

@@ -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))

View File

@@ -3,7 +3,7 @@
"name": "Roku",
"documentation": "https://www.home-assistant.io/components/roku",
"requirements": [
"roku==3.0.0"
"roku==3.1"
],
"dependencies": [],
"codeowners": []

View File

@@ -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."""

View File

@@ -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(

View File

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

View File

@@ -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"

View File

@@ -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

View File

@@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/unifi",
"requirements": [
"aiounifi==8"
"aiounifi==10"
],
"dependencies": [],
"codeowners": [

View File

@@ -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)

View File

@@ -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"]
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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:

View File

@@ -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"]},
)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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."""

View File

@@ -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

View 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

View File

@@ -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"]

View File

@@ -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):

View File

@@ -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

View File

@@ -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):

View File

@@ -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"

View File

@@ -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(

View File

@@ -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",

View File

@@ -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

View 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

View File

@@ -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