mirror of
https://github.com/home-assistant/core.git
synced 2025-10-12 21:29:34 +00:00
Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fbc53a886f | ||
![]() |
5675f16b61 | ||
![]() |
4d0a093ec4 | ||
![]() |
60a9de40aa | ||
![]() |
ed6b95f14a | ||
![]() |
ce114b81c3 | ||
![]() |
79b47157b8 | ||
![]() |
ae42736235 | ||
![]() |
fdd090bae0 | ||
![]() |
48d35a4550 | ||
![]() |
31b17e2b95 | ||
![]() |
720d63f496 | ||
![]() |
e9b19e0465 | ||
![]() |
e74c4c5d99 | ||
![]() |
697833bc91 | ||
![]() |
3e7699b886 | ||
![]() |
d448ed9f6d | ||
![]() |
f229cf2796 | ||
![]() |
5def51835e | ||
![]() |
12f1a322a7 | ||
![]() |
488f26d55b | ||
![]() |
20a0557be7 | ||
![]() |
e58291015c | ||
![]() |
e6a27983d3 | ||
![]() |
939ca63cbc | ||
![]() |
50714fbedf | ||
![]() |
c9de5b9fef | ||
![]() |
aae80dca1c |
@@ -77,6 +77,12 @@ class CloudGoogleConfig(AbstractConfig):
|
|||||||
"""Return Cloud User account."""
|
"""Return Cloud User account."""
|
||||||
return self._user
|
return self._user
|
||||||
|
|
||||||
|
async def async_initialize(self):
|
||||||
|
"""Perform async initialization of config."""
|
||||||
|
await super().async_initialize()
|
||||||
|
# Remove bad data that was there until 0.103.6 - Jan 6, 2020
|
||||||
|
self._store.pop_agent_user_id(self._user)
|
||||||
|
|
||||||
def should_expose(self, state):
|
def should_expose(self, state):
|
||||||
"""If a state object should be exposed."""
|
"""If a state object should be exposed."""
|
||||||
return self._should_expose_entity_id(state.entity_id)
|
return self._should_expose_entity_id(state.entity_id)
|
||||||
@@ -93,6 +99,15 @@ class CloudGoogleConfig(AbstractConfig):
|
|||||||
entity_config = entity_configs.get(entity_id, {})
|
entity_config = entity_configs.get(entity_id, {})
|
||||||
return entity_config.get(PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE)
|
return entity_config.get(PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def agent_user_id(self):
|
||||||
|
"""Return Agent User Id to use for query responses."""
|
||||||
|
return self._cloud.username
|
||||||
|
|
||||||
|
def get_agent_user_id(self, context):
|
||||||
|
"""Get agent user ID making request."""
|
||||||
|
return self.agent_user_id
|
||||||
|
|
||||||
def should_2fa(self, state):
|
def should_2fa(self, state):
|
||||||
"""If an entity should be checked for 2FA."""
|
"""If an entity should be checked for 2FA."""
|
||||||
entity_configs = self._prefs.google_entity_configs
|
entity_configs = self._prefs.google_entity_configs
|
||||||
|
@@ -175,7 +175,7 @@ class GoogleActionsSyncView(HomeAssistantView):
|
|||||||
hass = request.app["hass"]
|
hass = request.app["hass"]
|
||||||
cloud: Cloud = hass.data[DOMAIN]
|
cloud: Cloud = hass.data[DOMAIN]
|
||||||
gconf = await cloud.client.get_google_config()
|
gconf = await cloud.client.get_google_config()
|
||||||
status = await gconf.async_sync_entities(gconf.cloud_user)
|
status = await gconf.async_sync_entities(gconf.agent_user_id)
|
||||||
return self.json({}, status_code=status)
|
return self.json({}, status_code=status)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -158,12 +158,21 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
title="deCONZ-" + self.deconz_config[CONF_BRIDGEID], data=self.deconz_config
|
title="deCONZ-" + self.deconz_config[CONF_BRIDGEID], data=self.deconz_config
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _update_entry(self, entry, host):
|
def _update_entry(self, entry, host, port, api_key=None):
|
||||||
"""Update existing entry."""
|
"""Update existing entry."""
|
||||||
if entry.data[CONF_HOST] == host:
|
if (
|
||||||
|
entry.data[CONF_HOST] == host
|
||||||
|
and entry.data[CONF_PORT] == port
|
||||||
|
and (api_key is None or entry.data[CONF_API_KEY] == api_key)
|
||||||
|
):
|
||||||
return self.async_abort(reason="already_configured")
|
return self.async_abort(reason="already_configured")
|
||||||
|
|
||||||
entry.data[CONF_HOST] = host
|
entry.data[CONF_HOST] = host
|
||||||
|
entry.data[CONF_PORT] = port
|
||||||
|
|
||||||
|
if api_key is not None:
|
||||||
|
entry.data[CONF_API_KEY] = api_key
|
||||||
|
|
||||||
self.hass.config_entries.async_update_entry(entry)
|
self.hass.config_entries.async_update_entry(entry)
|
||||||
return self.async_abort(reason="updated_instance")
|
return self.async_abort(reason="updated_instance")
|
||||||
|
|
||||||
@@ -182,7 +191,11 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
||||||
if uuid == entry.data.get(CONF_UUID):
|
if uuid == entry.data.get(CONF_UUID):
|
||||||
return await self._update_entry(entry, discovery_info[CONF_HOST])
|
if entry.source == "hassio":
|
||||||
|
return self.async_abort(reason="already_configured")
|
||||||
|
return self._update_entry(
|
||||||
|
entry, discovery_info[CONF_HOST], entry.data.get(CONF_PORT)
|
||||||
|
)
|
||||||
|
|
||||||
bridgeid = discovery_info[ATTR_SERIAL]
|
bridgeid = discovery_info[ATTR_SERIAL]
|
||||||
if any(
|
if any(
|
||||||
@@ -212,7 +225,12 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
if bridgeid in gateway_entries:
|
if bridgeid in gateway_entries:
|
||||||
entry = gateway_entries[bridgeid]
|
entry = gateway_entries[bridgeid]
|
||||||
return await self._update_entry(entry, user_input[CONF_HOST])
|
return self._update_entry(
|
||||||
|
entry,
|
||||||
|
user_input[CONF_HOST],
|
||||||
|
user_input[CONF_PORT],
|
||||||
|
user_input[CONF_API_KEY],
|
||||||
|
)
|
||||||
|
|
||||||
self._hassio_discovery = user_input
|
self._hassio_discovery = user_input
|
||||||
|
|
||||||
|
@@ -59,7 +59,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
entity_handler.add_entity(new_sensor)
|
entity_handler.add_entity(new_sensor)
|
||||||
entities.append(new_sensor)
|
entities.append(new_sensor)
|
||||||
|
|
||||||
if sensor.battery:
|
if sensor.battery is not None:
|
||||||
new_battery = DeconzBattery(sensor, gateway)
|
new_battery = DeconzBattery(sensor, gateway)
|
||||||
if new_battery.unique_id not in batteries:
|
if new_battery.unique_id not in batteries:
|
||||||
batteries.add(new_battery.unique_id)
|
batteries.add(new_battery.unique_id)
|
||||||
@@ -225,6 +225,9 @@ class DeconzBatteryHandler:
|
|||||||
@callback
|
@callback
|
||||||
def create_tracker(self, sensor):
|
def create_tracker(self, sensor):
|
||||||
"""Create new tracker for battery state."""
|
"""Create new tracker for battery state."""
|
||||||
|
for tracker in self._trackers:
|
||||||
|
if sensor == tracker.sensor:
|
||||||
|
return
|
||||||
self._trackers.add(DeconzSensorStateTracker(sensor, self.gateway))
|
self._trackers.add(DeconzSensorStateTracker(sensor, self.gateway))
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@@ -15,11 +15,11 @@ from homeassistant.components.image_processing import (
|
|||||||
CONF_SOURCE,
|
CONF_SOURCE,
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
ImageProcessingEntity,
|
ImageProcessingEntity,
|
||||||
draw_box,
|
|
||||||
)
|
)
|
||||||
from homeassistant.core import split_entity_id
|
from homeassistant.core import split_entity_id
|
||||||
from homeassistant.helpers import template
|
from homeassistant.helpers import template
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.util.pil import draw_box
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@@ -1,10 +1,8 @@
|
|||||||
{
|
{
|
||||||
"domain": "doods",
|
"domain": "doods",
|
||||||
"name": "DOODS - Distributed Outside Object Detection Service",
|
"name": "DOODS - Distributed Outside Object Detection Service",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/doods",
|
"documentation": "https://www.home-assistant.io/integrations/doods",
|
||||||
"requirements": [
|
"requirements": ["pydoods==1.0.2", "pillow==6.2.1"],
|
||||||
"pydoods==1.0.2"
|
"dependencies": [],
|
||||||
],
|
"codeowners": []
|
||||||
"dependencies": [],
|
|
||||||
"codeowners": []
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
def dsmr_transform(value):
|
def dsmr_transform(value):
|
||||||
"""Transform DSMR version value to right format."""
|
"""Transform DSMR version value to right format."""
|
||||||
return float(value) / 10
|
if value.isdigit():
|
||||||
|
return float(value) / 10
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def tariff_transform(value):
|
def tariff_transform(value):
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"name": "Environment Canada",
|
"name": "Environment Canada",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
|
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"env_canada==0.0.30"
|
"env_canada==0.0.31"
|
||||||
],
|
],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
"""Helper classes for Google Assistant integration."""
|
"""Helper classes for Google Assistant integration."""
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
from asyncio import gather
|
from asyncio import gather
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
import logging
|
import logging
|
||||||
@@ -35,7 +36,7 @@ SYNC_DELAY = 15
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AbstractConfig:
|
class AbstractConfig(ABC):
|
||||||
"""Hold the configuration for Google Assistant."""
|
"""Hold the configuration for Google Assistant."""
|
||||||
|
|
||||||
_unsub_report_state = None
|
_unsub_report_state = None
|
||||||
@@ -95,9 +96,13 @@ class AbstractConfig:
|
|||||||
"""Return the user ID to be used for actions received via the local SDK."""
|
"""Return the user ID to be used for actions received via the local SDK."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_agent_user_id(self, context):
|
||||||
|
"""Get agent user ID from context."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
def should_expose(self, state) -> bool:
|
def should_expose(self, state) -> bool:
|
||||||
"""Return if entity should be exposed."""
|
"""Return if entity should be exposed."""
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def should_2fa(self, state):
|
def should_2fa(self, state):
|
||||||
"""If an entity should have 2FA checked."""
|
"""If an entity should have 2FA checked."""
|
||||||
|
@@ -121,6 +121,10 @@ class GoogleConfig(AbstractConfig):
|
|||||||
|
|
||||||
return is_default_exposed or explicit_expose
|
return is_default_exposed or explicit_expose
|
||||||
|
|
||||||
|
def get_agent_user_id(self, context):
|
||||||
|
"""Get agent user ID making request."""
|
||||||
|
return context.user_id
|
||||||
|
|
||||||
def should_2fa(self, state):
|
def should_2fa(self, state):
|
||||||
"""If an entity should have 2FA checked."""
|
"""If an entity should have 2FA checked."""
|
||||||
return True
|
return True
|
||||||
|
@@ -79,7 +79,7 @@ async def async_devices_sync(hass, data, payload):
|
|||||||
EVENT_SYNC_RECEIVED, {"request_id": data.request_id}, context=data.context
|
EVENT_SYNC_RECEIVED, {"request_id": data.request_id}, context=data.context
|
||||||
)
|
)
|
||||||
|
|
||||||
agent_user_id = data.context.user_id
|
agent_user_id = data.config.get_agent_user_id(data.context)
|
||||||
|
|
||||||
devices = await asyncio.gather(
|
devices = await asyncio.gather(
|
||||||
*(
|
*(
|
||||||
|
@@ -303,6 +303,7 @@ class HomeKit:
|
|||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
"""Set up bridge and accessory driver."""
|
"""Set up bridge and accessory driver."""
|
||||||
|
# pylint: disable=import-outside-toplevel
|
||||||
from .accessories import HomeBridge, HomeDriver
|
from .accessories import HomeBridge, HomeDriver
|
||||||
|
|
||||||
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop)
|
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop)
|
||||||
|
@@ -88,8 +88,11 @@ class Fan(HomeAccessory):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if CHAR_ROTATION_SPEED in chars:
|
if CHAR_ROTATION_SPEED in chars:
|
||||||
|
# Initial value is set to 100 because 0 is a special value (off). 100 is
|
||||||
|
# an arbitrary non-zero value. It is updated immediately by update_state
|
||||||
|
# to set to the correct initial value.
|
||||||
self.char_speed = serv_fan.configure_char(
|
self.char_speed = serv_fan.configure_char(
|
||||||
CHAR_ROTATION_SPEED, value=0, setter_callback=self.set_speed
|
CHAR_ROTATION_SPEED, value=100, setter_callback=self.set_speed
|
||||||
)
|
)
|
||||||
|
|
||||||
if CHAR_SWING_MODE in chars:
|
if CHAR_SWING_MODE in chars:
|
||||||
@@ -156,7 +159,22 @@ class Fan(HomeAccessory):
|
|||||||
speed = new_state.attributes.get(ATTR_SPEED)
|
speed = new_state.attributes.get(ATTR_SPEED)
|
||||||
hk_speed_value = self.speed_mapping.speed_to_homekit(speed)
|
hk_speed_value = self.speed_mapping.speed_to_homekit(speed)
|
||||||
if hk_speed_value is not None and self.char_speed.value != hk_speed_value:
|
if hk_speed_value is not None and self.char_speed.value != hk_speed_value:
|
||||||
self.char_speed.set_value(hk_speed_value)
|
# If the homeassistant component reports its speed as the first entry
|
||||||
|
# in its speed list but is not off, the hk_speed_value is 0. But 0
|
||||||
|
# is a special value in homekit. When you turn on a homekit accessory
|
||||||
|
# it will try to restore the last rotation speed state which will be
|
||||||
|
# the last value saved by char_speed.set_value. But if it is set to
|
||||||
|
# 0, HomeKit will update the rotation speed to 100 as it thinks 0 is
|
||||||
|
# off.
|
||||||
|
#
|
||||||
|
# Therefore, if the hk_speed_value is 0 and the device is still on,
|
||||||
|
# the rotation speed is mapped to 1 otherwise the update is ignored
|
||||||
|
# in order to avoid this incorrect behavior.
|
||||||
|
if hk_speed_value == 0:
|
||||||
|
if state == STATE_ON:
|
||||||
|
self.char_speed.set_value(1)
|
||||||
|
else:
|
||||||
|
self.char_speed.set_value(hk_speed_value)
|
||||||
|
|
||||||
# Handle Oscillating
|
# Handle Oscillating
|
||||||
if self.char_swing is not None:
|
if self.char_swing is not None:
|
||||||
|
@@ -82,8 +82,11 @@ class Light(HomeAccessory):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if CHAR_BRIGHTNESS in self.chars:
|
if CHAR_BRIGHTNESS in self.chars:
|
||||||
|
# Initial value is set to 100 because 0 is a special value (off). 100 is
|
||||||
|
# an arbitrary non-zero value. It is updated immediately by update_state
|
||||||
|
# to set to the correct initial value.
|
||||||
self.char_brightness = serv_light.configure_char(
|
self.char_brightness = serv_light.configure_char(
|
||||||
CHAR_BRIGHTNESS, value=0, setter_callback=self.set_brightness
|
CHAR_BRIGHTNESS, value=100, setter_callback=self.set_brightness
|
||||||
)
|
)
|
||||||
if CHAR_COLOR_TEMPERATURE in self.chars:
|
if CHAR_COLOR_TEMPERATURE in self.chars:
|
||||||
min_mireds = self.hass.states.get(self.entity_id).attributes.get(
|
min_mireds = self.hass.states.get(self.entity_id).attributes.get(
|
||||||
@@ -183,7 +186,21 @@ class Light(HomeAccessory):
|
|||||||
if not self._flag[CHAR_BRIGHTNESS] and isinstance(brightness, int):
|
if not self._flag[CHAR_BRIGHTNESS] and isinstance(brightness, int):
|
||||||
brightness = round(brightness / 255 * 100, 0)
|
brightness = round(brightness / 255 * 100, 0)
|
||||||
if self.char_brightness.value != brightness:
|
if self.char_brightness.value != brightness:
|
||||||
self.char_brightness.set_value(brightness)
|
# The homeassistant component might report its brightness as 0 but is
|
||||||
|
# not off. But 0 is a special value in homekit. When you turn on a
|
||||||
|
# homekit accessory it will try to restore the last brightness state
|
||||||
|
# which will be the last value saved by char_brightness.set_value.
|
||||||
|
# But if it is set to 0, HomeKit will update the brightness to 100 as
|
||||||
|
# it thinks 0 is off.
|
||||||
|
#
|
||||||
|
# Therefore, if the the brighness is 0 and the device is still on,
|
||||||
|
# the brightness is mapped to 1 otherwise the update is ignored in
|
||||||
|
# order to avoid this incorrect behavior.
|
||||||
|
if brightness == 0:
|
||||||
|
if state == STATE_ON:
|
||||||
|
self.char_brightness.set_value(1)
|
||||||
|
else:
|
||||||
|
self.char_brightness.set_value(brightness)
|
||||||
self._flag[CHAR_BRIGHTNESS] = False
|
self._flag[CHAR_BRIGHTNESS] = False
|
||||||
|
|
||||||
# Handle color temperature
|
# Handle color temperature
|
||||||
|
@@ -2,9 +2,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
from PIL import ImageDraw
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME, CONF_ENTITY_ID, CONF_NAME
|
from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME, CONF_ENTITY_ID, CONF_NAME
|
||||||
@@ -65,46 +63,6 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
|
|||||||
PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema)
|
PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema)
|
||||||
|
|
||||||
|
|
||||||
def draw_box(
|
|
||||||
draw: ImageDraw,
|
|
||||||
box: Tuple[float, float, float, float],
|
|
||||||
img_width: int,
|
|
||||||
img_height: int,
|
|
||||||
text: str = "",
|
|
||||||
color: Tuple[int, int, int] = (255, 255, 0),
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Draw a bounding box on and image.
|
|
||||||
|
|
||||||
The bounding box is defined by the tuple (y_min, x_min, y_max, x_max)
|
|
||||||
where the coordinates are floats in the range [0.0, 1.0] and
|
|
||||||
relative to the width and height of the image.
|
|
||||||
|
|
||||||
For example, if an image is 100 x 200 pixels (height x width) and the bounding
|
|
||||||
box is `(0.1, 0.2, 0.5, 0.9)`, the upper-left and bottom-right coordinates of
|
|
||||||
the bounding box will be `(40, 10)` to `(180, 50)` (in (x,y) coordinates).
|
|
||||||
"""
|
|
||||||
|
|
||||||
line_width = 3
|
|
||||||
font_height = 8
|
|
||||||
y_min, x_min, y_max, x_max = box
|
|
||||||
(left, right, top, bottom) = (
|
|
||||||
x_min * img_width,
|
|
||||||
x_max * img_width,
|
|
||||||
y_min * img_height,
|
|
||||||
y_max * img_height,
|
|
||||||
)
|
|
||||||
draw.line(
|
|
||||||
[(left, top), (left, bottom), (right, bottom), (right, top), (left, top)],
|
|
||||||
width=line_width,
|
|
||||||
fill=color,
|
|
||||||
)
|
|
||||||
if text:
|
|
||||||
draw.text(
|
|
||||||
(left + line_width, abs(top - line_width - font_height)), text, fill=color
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up the image processing."""
|
"""Set up the image processing."""
|
||||||
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
||||||
|
@@ -2,11 +2,7 @@
|
|||||||
"domain": "image_processing",
|
"domain": "image_processing",
|
||||||
"name": "Image processing",
|
"name": "Image processing",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/image_processing",
|
"documentation": "https://www.home-assistant.io/integrations/image_processing",
|
||||||
"requirements": [
|
"requirements": [],
|
||||||
"pillow==6.2.1"
|
"dependencies": ["camera"],
|
||||||
],
|
|
||||||
"dependencies": [
|
|
||||||
"camera"
|
|
||||||
],
|
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@@ -63,7 +63,7 @@ class RachioControllerBinarySensor(BinarySensorDevice):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# For this device
|
# For this device
|
||||||
self._handle_update()
|
self._handle_update(args, kwargs)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _poll_update(self, data=None) -> bool:
|
def _poll_update(self, data=None) -> bool:
|
||||||
@@ -119,9 +119,9 @@ class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor):
|
|||||||
|
|
||||||
def _handle_update(self, *args, **kwargs) -> None:
|
def _handle_update(self, *args, **kwargs) -> None:
|
||||||
"""Handle an update to the state of this sensor."""
|
"""Handle an update to the state of this sensor."""
|
||||||
if args[0][KEY_SUBTYPE] == SUBTYPE_ONLINE:
|
if args[0][0][KEY_SUBTYPE] == SUBTYPE_ONLINE:
|
||||||
self._state = True
|
self._state = True
|
||||||
elif args[0][KEY_SUBTYPE] == SUBTYPE_OFFLINE:
|
elif args[0][0][KEY_SUBTYPE] == SUBTYPE_OFFLINE:
|
||||||
self._state = False
|
self._state = False
|
||||||
|
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
@@ -107,7 +107,7 @@ class RachioStandbySwitch(RachioSwitch):
|
|||||||
dispatcher_connect(
|
dispatcher_connect(
|
||||||
hass, SIGNAL_RACHIO_CONTROLLER_UPDATE, self._handle_any_update
|
hass, SIGNAL_RACHIO_CONTROLLER_UPDATE, self._handle_any_update
|
||||||
)
|
)
|
||||||
super().__init__(controller, poll=False)
|
super().__init__(controller, poll=True)
|
||||||
self._poll_update(controller.init_data)
|
self._poll_update(controller.init_data)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -134,9 +134,9 @@ class RachioStandbySwitch(RachioSwitch):
|
|||||||
|
|
||||||
def _handle_update(self, *args, **kwargs) -> None:
|
def _handle_update(self, *args, **kwargs) -> None:
|
||||||
"""Update the state using webhook data."""
|
"""Update the state using webhook data."""
|
||||||
if args[0][KEY_SUBTYPE] == SUBTYPE_SLEEP_MODE_ON:
|
if args[0][0][KEY_SUBTYPE] == SUBTYPE_SLEEP_MODE_ON:
|
||||||
self._state = True
|
self._state = True
|
||||||
elif args[0][KEY_SUBTYPE] == SUBTYPE_SLEEP_MODE_OFF:
|
elif args[0][0][KEY_SUBTYPE] == SUBTYPE_SLEEP_MODE_OFF:
|
||||||
self._state = False
|
self._state = False
|
||||||
|
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
@@ -2,11 +2,7 @@
|
|||||||
"domain": "ring",
|
"domain": "ring",
|
||||||
"name": "Ring",
|
"name": "Ring",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/ring",
|
"documentation": "https://www.home-assistant.io/integrations/ring",
|
||||||
"requirements": [
|
"requirements": ["ring_doorbell==0.2.8"],
|
||||||
"ring_doorbell==0.2.3"
|
"dependencies": ["ffmpeg"],
|
||||||
],
|
|
||||||
"dependencies": [
|
|
||||||
"ffmpeg"
|
|
||||||
],
|
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"domain": "seven_segments",
|
"domain": "seven_segments",
|
||||||
"name": "Seven segments",
|
"name": "Seven segments",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/seven_segments",
|
"documentation": "https://www.home-assistant.io/integrations/seven_segments",
|
||||||
"requirements": [],
|
"requirements": ["pillow==6.2.1"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"name": "Smartthings",
|
"name": "Smartthings",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/smartthings",
|
"documentation": "https://www.home-assistant.io/integrations/smartthings",
|
||||||
"requirements": ["pysmartapp==0.3.2", "pysmartthings==0.6.9"],
|
"requirements": ["pysmartapp==0.3.2", "pysmartthings==0.7.0"],
|
||||||
"dependencies": ["webhook"],
|
"dependencies": ["webhook"],
|
||||||
"after_dependencies": ["cloud"],
|
"after_dependencies": ["cloud"],
|
||||||
"codeowners": ["@andrewsayre"]
|
"codeowners": ["@andrewsayre"]
|
||||||
|
@@ -2,9 +2,7 @@
|
|||||||
"domain": "starlingbank",
|
"domain": "starlingbank",
|
||||||
"name": "Starlingbank",
|
"name": "Starlingbank",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/starlingbank",
|
"documentation": "https://www.home-assistant.io/integrations/starlingbank",
|
||||||
"requirements": [
|
"requirements": ["starlingbank==3.2"],
|
||||||
"starlingbank==3.1"
|
|
||||||
],
|
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@@ -15,11 +15,11 @@ from homeassistant.components.image_processing import (
|
|||||||
CONF_SOURCE,
|
CONF_SOURCE,
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
ImageProcessingEntity,
|
ImageProcessingEntity,
|
||||||
draw_box,
|
|
||||||
)
|
)
|
||||||
from homeassistant.core import split_entity_id
|
from homeassistant.core import split_entity_id
|
||||||
from homeassistant.helpers import template
|
from homeassistant.helpers import template
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.util.pil import draw_box
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@@ -2,7 +2,12 @@
|
|||||||
"domain": "tensorflow",
|
"domain": "tensorflow",
|
||||||
"name": "Tensorflow",
|
"name": "Tensorflow",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/tensorflow",
|
"documentation": "https://www.home-assistant.io/integrations/tensorflow",
|
||||||
"requirements": ["tensorflow==1.13.2", "numpy==1.17.4", "protobuf==3.6.1"],
|
"requirements": [
|
||||||
|
"tensorflow==1.13.2",
|
||||||
|
"numpy==1.17.4",
|
||||||
|
"protobuf==3.6.1",
|
||||||
|
"pillow==6.2.1"
|
||||||
|
],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@@ -131,6 +131,9 @@ class UniFiClientTracker(ScannerEntity):
|
|||||||
self.is_wired = self.client.mac not in controller.wireless_clients
|
self.is_wired = self.client.mac not in controller.wireless_clients
|
||||||
self.wired_bug = None
|
self.wired_bug = None
|
||||||
|
|
||||||
|
if self.is_wired != self.client.is_wired:
|
||||||
|
self.wired_bug = dt_util.utcnow() - self.controller.option_detection_time
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entity_registry_enabled_default(self):
|
def entity_registry_enabled_default(self):
|
||||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 103
|
MINOR_VERSION = 103
|
||||||
PATCH_VERSION = "1"
|
PATCH_VERSION = "6"
|
||||||
__short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION)
|
__short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION)
|
||||||
__version__ = "{}.{}".format(__short_version__, PATCH_VERSION)
|
__version__ = "{}.{}".format(__short_version__, PATCH_VERSION)
|
||||||
REQUIRED_PYTHON_VER = (3, 6, 1)
|
REQUIRED_PYTHON_VER = (3, 6, 1)
|
||||||
|
@@ -3,7 +3,7 @@ import asyncio
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional, Set
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.util.package as pkg_util
|
import homeassistant.util.package as pkg_util
|
||||||
@@ -28,16 +28,19 @@ class RequirementsNotFound(HomeAssistantError):
|
|||||||
|
|
||||||
|
|
||||||
async def async_get_integration_with_requirements(
|
async def async_get_integration_with_requirements(
|
||||||
hass: HomeAssistant, domain: str
|
hass: HomeAssistant, domain: str, done: Set[str] = None
|
||||||
) -> Integration:
|
) -> Integration:
|
||||||
"""Get an integration with installed requirements.
|
"""Get an integration with installed requirements.
|
||||||
|
|
||||||
This can raise IntegrationNotFound if manifest or integration
|
This can raise IntegrationNotFound if manifest or integration
|
||||||
is invalid, RequirementNotFound if there was some type of
|
is invalid, RequirementNotFound if there was some type of
|
||||||
failure to install requirements.
|
failure to install requirements.
|
||||||
|
|
||||||
Does not handle circular dependencies.
|
|
||||||
"""
|
"""
|
||||||
|
if done is None:
|
||||||
|
done = {domain}
|
||||||
|
else:
|
||||||
|
done.add(domain)
|
||||||
|
|
||||||
integration = await async_get_integration(hass, domain)
|
integration = await async_get_integration(hass, domain)
|
||||||
|
|
||||||
if hass.config.skip_pip:
|
if hass.config.skip_pip:
|
||||||
@@ -48,11 +51,18 @@ async def async_get_integration_with_requirements(
|
|||||||
hass, integration.domain, integration.requirements
|
hass, integration.domain, integration.requirements
|
||||||
)
|
)
|
||||||
|
|
||||||
deps = integration.dependencies + (integration.after_dependencies or [])
|
deps_to_check = [
|
||||||
|
dep
|
||||||
|
for dep in integration.dependencies + (integration.after_dependencies or [])
|
||||||
|
if dep not in done
|
||||||
|
]
|
||||||
|
|
||||||
if deps:
|
if deps_to_check:
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
*[async_get_integration_with_requirements(hass, dep) for dep in deps]
|
*[
|
||||||
|
async_get_integration_with_requirements(hass, dep, done)
|
||||||
|
for dep in deps_to_check
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
return integration
|
return integration
|
||||||
|
47
homeassistant/util/pil.py
Normal file
47
homeassistant/util/pil.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"""PIL utilities.
|
||||||
|
|
||||||
|
Can only be used by integrations that have pillow in their requirements.
|
||||||
|
"""
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
from PIL import ImageDraw
|
||||||
|
|
||||||
|
|
||||||
|
def draw_box(
|
||||||
|
draw: ImageDraw,
|
||||||
|
box: Tuple[float, float, float, float],
|
||||||
|
img_width: int,
|
||||||
|
img_height: int,
|
||||||
|
text: str = "",
|
||||||
|
color: Tuple[int, int, int] = (255, 255, 0),
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Draw a bounding box on and image.
|
||||||
|
|
||||||
|
The bounding box is defined by the tuple (y_min, x_min, y_max, x_max)
|
||||||
|
where the coordinates are floats in the range [0.0, 1.0] and
|
||||||
|
relative to the width and height of the image.
|
||||||
|
|
||||||
|
For example, if an image is 100 x 200 pixels (height x width) and the bounding
|
||||||
|
box is `(0.1, 0.2, 0.5, 0.9)`, the upper-left and bottom-right coordinates of
|
||||||
|
the bounding box will be `(40, 10)` to `(180, 50)` (in (x,y) coordinates).
|
||||||
|
"""
|
||||||
|
|
||||||
|
line_width = 3
|
||||||
|
font_height = 8
|
||||||
|
y_min, x_min, y_max, x_max = box
|
||||||
|
(left, right, top, bottom) = (
|
||||||
|
x_min * img_width,
|
||||||
|
x_max * img_width,
|
||||||
|
y_min * img_height,
|
||||||
|
y_max * img_height,
|
||||||
|
)
|
||||||
|
draw.line(
|
||||||
|
[(left, top), (left, bottom), (right, bottom), (right, top), (left, top)],
|
||||||
|
width=line_width,
|
||||||
|
fill=color,
|
||||||
|
)
|
||||||
|
if text:
|
||||||
|
draw.text(
|
||||||
|
(left + line_width, abs(top - line_width - font_height)), text, fill=color
|
||||||
|
)
|
@@ -475,7 +475,7 @@ enocean==0.50
|
|||||||
enturclient==0.2.1
|
enturclient==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.environment_canada
|
# homeassistant.components.environment_canada
|
||||||
env_canada==0.0.30
|
env_canada==0.0.31
|
||||||
|
|
||||||
# homeassistant.components.envirophat
|
# homeassistant.components.envirophat
|
||||||
# envirophat==0.0.6
|
# envirophat==0.0.6
|
||||||
@@ -980,9 +980,11 @@ piglow==1.2.4
|
|||||||
# homeassistant.components.pilight
|
# homeassistant.components.pilight
|
||||||
pilight==0.1.1
|
pilight==0.1.1
|
||||||
|
|
||||||
# homeassistant.components.image_processing
|
# homeassistant.components.doods
|
||||||
# homeassistant.components.proxy
|
# homeassistant.components.proxy
|
||||||
# homeassistant.components.qrcode
|
# homeassistant.components.qrcode
|
||||||
|
# homeassistant.components.seven_segments
|
||||||
|
# homeassistant.components.tensorflow
|
||||||
pillow==6.2.1
|
pillow==6.2.1
|
||||||
|
|
||||||
# homeassistant.components.dominos
|
# homeassistant.components.dominos
|
||||||
@@ -1483,7 +1485,7 @@ pysma==0.3.4
|
|||||||
pysmartapp==0.3.2
|
pysmartapp==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==0.6.9
|
pysmartthings==0.7.0
|
||||||
|
|
||||||
# homeassistant.components.smarty
|
# homeassistant.components.smarty
|
||||||
pysmarty==0.8
|
pysmarty==0.8
|
||||||
@@ -1730,7 +1732,7 @@ rfk101py==0.0.1
|
|||||||
rflink==0.0.46
|
rflink==0.0.46
|
||||||
|
|
||||||
# homeassistant.components.ring
|
# homeassistant.components.ring
|
||||||
ring_doorbell==0.2.3
|
ring_doorbell==0.2.8
|
||||||
|
|
||||||
# homeassistant.components.fleetgo
|
# homeassistant.components.fleetgo
|
||||||
ritassist==0.9.2
|
ritassist==0.9.2
|
||||||
@@ -1868,7 +1870,7 @@ sqlalchemy==1.3.11
|
|||||||
starline==0.1.3
|
starline==0.1.3
|
||||||
|
|
||||||
# homeassistant.components.starlingbank
|
# homeassistant.components.starlingbank
|
||||||
starlingbank==3.1
|
starlingbank==3.2
|
||||||
|
|
||||||
# homeassistant.components.statsd
|
# homeassistant.components.statsd
|
||||||
statsd==3.2.1
|
statsd==3.2.1
|
||||||
|
@@ -320,11 +320,6 @@ pexpect==4.6.0
|
|||||||
# homeassistant.components.pilight
|
# homeassistant.components.pilight
|
||||||
pilight==0.1.1
|
pilight==0.1.1
|
||||||
|
|
||||||
# homeassistant.components.image_processing
|
|
||||||
# homeassistant.components.proxy
|
|
||||||
# homeassistant.components.qrcode
|
|
||||||
pillow==6.2.1
|
|
||||||
|
|
||||||
# homeassistant.components.plex
|
# homeassistant.components.plex
|
||||||
plexapi==3.3.0
|
plexapi==3.3.0
|
||||||
|
|
||||||
@@ -495,7 +490,7 @@ pysma==0.3.4
|
|||||||
pysmartapp==0.3.2
|
pysmartapp==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==0.6.9
|
pysmartthings==0.7.0
|
||||||
|
|
||||||
# homeassistant.components.soma
|
# homeassistant.components.soma
|
||||||
pysoma==0.0.10
|
pysoma==0.0.10
|
||||||
@@ -549,7 +544,7 @@ restrictedpython==5.0
|
|||||||
rflink==0.0.46
|
rflink==0.0.46
|
||||||
|
|
||||||
# homeassistant.components.ring
|
# homeassistant.components.ring
|
||||||
ring_doorbell==0.2.3
|
ring_doorbell==0.2.8
|
||||||
|
|
||||||
# homeassistant.components.yamaha
|
# homeassistant.components.yamaha
|
||||||
rxv==0.6.0
|
rxv==0.6.0
|
||||||
|
@@ -102,13 +102,17 @@ async def test_handler_google_actions(hass):
|
|||||||
reqid = "5711642932632160983"
|
reqid = "5711642932632160983"
|
||||||
data = {"requestId": reqid, "inputs": [{"intent": "action.devices.SYNC"}]}
|
data = {"requestId": reqid, "inputs": [{"intent": "action.devices.SYNC"}]}
|
||||||
|
|
||||||
config = await cloud.client.get_google_config()
|
with patch(
|
||||||
resp = await cloud.client.async_google_message(data)
|
"hass_nabucasa.Cloud._decode_claims",
|
||||||
|
return_value={"cognito:username": "myUserName"},
|
||||||
|
):
|
||||||
|
await cloud.client.get_google_config()
|
||||||
|
resp = await cloud.client.async_google_message(data)
|
||||||
|
|
||||||
assert resp["requestId"] == reqid
|
assert resp["requestId"] == reqid
|
||||||
payload = resp["payload"]
|
payload = resp["payload"]
|
||||||
|
|
||||||
assert payload["agentUserId"] == config.cloud_user
|
assert payload["agentUserId"] == "myUserName"
|
||||||
|
|
||||||
devices = payload["devices"]
|
devices = payload["devices"]
|
||||||
assert len(devices) == 1
|
assert len(devices) == 1
|
||||||
|
@@ -323,32 +323,53 @@ async def test_hassio_update_instance(hass):
|
|||||||
"""Test we can update an existing config entry."""
|
"""Test we can update an existing config entry."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=config_flow.DOMAIN,
|
domain=config_flow.DOMAIN,
|
||||||
data={config_flow.CONF_BRIDGEID: "id", config_flow.CONF_HOST: "1.2.3.4"},
|
data={
|
||||||
|
config_flow.CONF_BRIDGEID: "id",
|
||||||
|
config_flow.CONF_HOST: "1.2.3.4",
|
||||||
|
config_flow.CONF_PORT: 40850,
|
||||||
|
config_flow.CONF_API_KEY: "secret",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
config_flow.DOMAIN,
|
config_flow.DOMAIN,
|
||||||
data={config_flow.CONF_HOST: "mock-deconz", config_flow.CONF_SERIAL: "id"},
|
data={
|
||||||
|
config_flow.CONF_HOST: "mock-deconz",
|
||||||
|
config_flow.CONF_PORT: 8080,
|
||||||
|
config_flow.CONF_API_KEY: "updated",
|
||||||
|
config_flow.CONF_SERIAL: "id",
|
||||||
|
},
|
||||||
context={"source": "hassio"},
|
context={"source": "hassio"},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "abort"
|
assert result["type"] == "abort"
|
||||||
assert result["reason"] == "updated_instance"
|
assert result["reason"] == "updated_instance"
|
||||||
assert entry.data[config_flow.CONF_HOST] == "mock-deconz"
|
assert entry.data[config_flow.CONF_HOST] == "mock-deconz"
|
||||||
|
assert entry.data[config_flow.CONF_PORT] == 8080
|
||||||
|
assert entry.data[config_flow.CONF_API_KEY] == "updated"
|
||||||
|
|
||||||
|
|
||||||
async def test_hassio_dont_update_instance(hass):
|
async def test_hassio_dont_update_instance(hass):
|
||||||
"""Test we can update an existing config entry."""
|
"""Test we can update an existing config entry."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=config_flow.DOMAIN,
|
domain=config_flow.DOMAIN,
|
||||||
data={config_flow.CONF_BRIDGEID: "id", config_flow.CONF_HOST: "1.2.3.4"},
|
data={
|
||||||
|
config_flow.CONF_BRIDGEID: "id",
|
||||||
|
config_flow.CONF_HOST: "1.2.3.4",
|
||||||
|
config_flow.CONF_PORT: 8080,
|
||||||
|
config_flow.CONF_API_KEY: "secret",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
config_flow.DOMAIN,
|
config_flow.DOMAIN,
|
||||||
data={config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_SERIAL: "id"},
|
data={
|
||||||
|
config_flow.CONF_HOST: "1.2.3.4",
|
||||||
|
config_flow.CONF_PORT: 8080,
|
||||||
|
config_flow.CONF_API_KEY: "secret",
|
||||||
|
config_flow.CONF_SERIAL: "id",
|
||||||
|
},
|
||||||
context={"source": "hassio"},
|
context={"source": "hassio"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -63,6 +63,10 @@ class MockConfig(helpers.AbstractConfig):
|
|||||||
"""Return local SDK webhook id."""
|
"""Return local SDK webhook id."""
|
||||||
return self._local_sdk_user_id
|
return self._local_sdk_user_id
|
||||||
|
|
||||||
|
def get_agent_user_id(self, context):
|
||||||
|
"""Get agent user ID making request."""
|
||||||
|
return context.user_id
|
||||||
|
|
||||||
def should_expose(self, state):
|
def should_expose(self, state):
|
||||||
"""Expose it all."""
|
"""Expose it all."""
|
||||||
return self._should_expose is None or self._should_expose(state)
|
return self._should_expose is None or self._should_expose(state)
|
||||||
|
@@ -197,7 +197,10 @@ async def test_fan_speed(hass, hk_driver, cls, events):
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 2, None)
|
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 2, None)
|
||||||
assert acc.char_speed.value == 0
|
|
||||||
|
# Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
|
||||||
|
# speed to 100 when turning on a fan on a freshly booted up server.
|
||||||
|
assert acc.char_speed.value != 0
|
||||||
|
|
||||||
await hass.async_add_job(acc.run)
|
await hass.async_add_job(acc.run)
|
||||||
assert (
|
assert (
|
||||||
|
@@ -101,7 +101,9 @@ async def test_light_brightness(hass, hk_driver, cls, events):
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 2, None)
|
acc = cls.light(hass, hk_driver, "Light", entity_id, 2, None)
|
||||||
|
|
||||||
assert acc.char_brightness.value == 0
|
# Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
|
||||||
|
# brightness to 100 when turning on a light on a freshly booted up server.
|
||||||
|
assert acc.char_brightness.value != 0
|
||||||
|
|
||||||
await hass.async_add_job(acc.run)
|
await hass.async_add_job(acc.run)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@@ -60,6 +60,7 @@ async def setup_unifi_integration(
|
|||||||
clients_response,
|
clients_response,
|
||||||
devices_response,
|
devices_response,
|
||||||
clients_all_response,
|
clients_all_response,
|
||||||
|
known_wireless_clients=None,
|
||||||
):
|
):
|
||||||
"""Create the UniFi controller."""
|
"""Create the UniFi controller."""
|
||||||
if UNIFI_CONFIG not in hass.data:
|
if UNIFI_CONFIG not in hass.data:
|
||||||
@@ -77,6 +78,11 @@ async def setup_unifi_integration(
|
|||||||
entry_id=1,
|
entry_id=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if known_wireless_clients:
|
||||||
|
hass.data[UNIFI_WIRELESS_CLIENTS].update_data(
|
||||||
|
known_wireless_clients, config_entry
|
||||||
|
)
|
||||||
|
|
||||||
mock_client_responses = deque()
|
mock_client_responses = deque()
|
||||||
mock_client_responses.append(clients_response)
|
mock_client_responses.append(clients_response)
|
||||||
|
|
||||||
|
@@ -44,6 +44,14 @@ CLIENT_3 = {
|
|||||||
"last_seen": 1562600145,
|
"last_seen": 1562600145,
|
||||||
"mac": "00:00:00:00:00:03",
|
"mac": "00:00:00:00:00:03",
|
||||||
}
|
}
|
||||||
|
CLIENT_4 = {
|
||||||
|
"essid": "ssid",
|
||||||
|
"hostname": "client_4",
|
||||||
|
"ip": "10.0.0.4",
|
||||||
|
"is_wired": True,
|
||||||
|
"last_seen": 1562600145,
|
||||||
|
"mac": "00:00:00:00:00:04",
|
||||||
|
}
|
||||||
|
|
||||||
DEVICE_1 = {
|
DEVICE_1 = {
|
||||||
"board_rev": 3,
|
"board_rev": 3,
|
||||||
@@ -103,16 +111,20 @@ async def test_no_clients(hass):
|
|||||||
|
|
||||||
async def test_tracked_devices(hass):
|
async def test_tracked_devices(hass):
|
||||||
"""Test the update_items function with some clients."""
|
"""Test the update_items function with some clients."""
|
||||||
|
client_4_copy = copy(CLIENT_4)
|
||||||
|
client_4_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow())
|
||||||
|
|
||||||
controller = await setup_unifi_integration(
|
controller = await setup_unifi_integration(
|
||||||
hass,
|
hass,
|
||||||
ENTRY_CONFIG,
|
ENTRY_CONFIG,
|
||||||
options={CONF_SSID_FILTER: ["ssid"]},
|
options={CONF_SSID_FILTER: ["ssid"]},
|
||||||
sites=SITES,
|
sites=SITES,
|
||||||
clients_response=[CLIENT_1, CLIENT_2, CLIENT_3],
|
clients_response=[CLIENT_1, CLIENT_2, CLIENT_3, client_4_copy],
|
||||||
devices_response=[DEVICE_1, DEVICE_2],
|
devices_response=[DEVICE_1, DEVICE_2],
|
||||||
clients_all_response={},
|
clients_all_response={},
|
||||||
|
known_wireless_clients=(CLIENT_4["mac"],),
|
||||||
)
|
)
|
||||||
assert len(hass.states.async_all()) == 5
|
assert len(hass.states.async_all()) == 6
|
||||||
|
|
||||||
client_1 = hass.states.get("device_tracker.client_1")
|
client_1 = hass.states.get("device_tracker.client_1")
|
||||||
assert client_1 is not None
|
assert client_1 is not None
|
||||||
@@ -125,6 +137,11 @@ async def test_tracked_devices(hass):
|
|||||||
client_3 = hass.states.get("device_tracker.client_3")
|
client_3 = hass.states.get("device_tracker.client_3")
|
||||||
assert client_3 is None
|
assert client_3 is None
|
||||||
|
|
||||||
|
# Wireless client with wired bug, if bug active on restart mark device away
|
||||||
|
client_4 = hass.states.get("device_tracker.client_4")
|
||||||
|
assert client_4 is not None
|
||||||
|
assert client_4.state == "not_home"
|
||||||
|
|
||||||
device_1 = hass.states.get("device_tracker.device_1")
|
device_1 = hass.states.get("device_tracker.device_1")
|
||||||
assert device_1 is not None
|
assert device_1 is not None
|
||||||
assert device_1.state == "not_home"
|
assert device_1.state == "not_home"
|
||||||
|
Reference in New Issue
Block a user