Skip to content
Merged

0.68.1 #14199

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion homeassistant/components/config/automation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Provide configuration end points for Automations."""
import asyncio
from collections import OrderedDict
import uuid

from homeassistant.const import CONF_ID
from homeassistant.components.config import EditIdBasedConfigView
Expand Down Expand Up @@ -29,7 +30,12 @@ def _write_value(self, hass, data, config_key, new_value):
"""Set value."""
index = None
for index, cur_value in enumerate(data):
if cur_value[CONF_ID] == config_key:
# When people copy paste their automations to the config file,
# they sometimes forget to add IDs. Fix it here.
if CONF_ID not in cur_value:
cur_value[CONF_ID] = uuid.uuid4().hex

elif cur_value[CONF_ID] == config_key:
break
else:
cur_value = OrderedDict()
Expand Down
11 changes: 8 additions & 3 deletions homeassistant/components/google_assistant/smart_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,23 @@ def sync_serialize(self):
if state.state == STATE_UNAVAILABLE:
return None

entity_config = self.config.entity_config.get(state.entity_id, {})
name = (entity_config.get(CONF_NAME) or state.name).strip()

# If an empty string
if not name:
return None

traits = self.traits()

# Found no supported traits for this entity
if not traits:
return None

entity_config = self.config.entity_config.get(state.entity_id, {})

device = {
'id': state.entity_id,
'name': {
'name': entity_config.get(CONF_NAME) or state.name
'name': name
},
'attributes': {},
'traits': [trait.name for trait in traits],
Expand Down
16 changes: 11 additions & 5 deletions homeassistant/components/homekit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/homekit/
"""
import ipaddress
import logging
from zlib import adler32

Expand All @@ -12,8 +13,8 @@
SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION)
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT,
ATTR_DEVICE_CLASS, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
ATTR_DEVICE_CLASS, CONF_IP_ADDRESS, CONF_PORT, TEMP_CELSIUS,
TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entityfilter import FILTER_SCHEMA
from homeassistant.util import get_local_ip
Expand All @@ -35,6 +36,8 @@
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All({
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_IP_ADDRESS):
vol.All(ipaddress.ip_address, cv.string),
vol.Optional(CONF_AUTO_START, default=DEFAULT_AUTO_START): cv.boolean,
vol.Optional(CONF_FILTER, default={}): FILTER_SCHEMA,
vol.Optional(CONF_ENTITY_CONFIG, default={}): validate_entity_config,
Expand All @@ -48,11 +51,12 @@ async def async_setup(hass, config):

conf = config[DOMAIN]
port = conf[CONF_PORT]
ip_address = conf.get(CONF_IP_ADDRESS)
auto_start = conf[CONF_AUTO_START]
entity_filter = conf[CONF_FILTER]
entity_config = conf[CONF_ENTITY_CONFIG]

homekit = HomeKit(hass, port, entity_filter, entity_config)
homekit = HomeKit(hass, port, ip_address, entity_filter, entity_config)
homekit.setup()

if auto_start:
Expand Down Expand Up @@ -151,10 +155,11 @@ def generate_aid(entity_id):
class HomeKit():
"""Class to handle all actions between HomeKit and Home Assistant."""

def __init__(self, hass, port, entity_filter, entity_config):
def __init__(self, hass, port, ip_address, entity_filter, entity_config):
"""Initialize a HomeKit object."""
self.hass = hass
self._port = port
self._ip_address = ip_address
self._filter = entity_filter
self._config = entity_config
self.started = False
Expand All @@ -169,9 +174,10 @@ def setup(self):
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, self.stop)

ip_addr = self._ip_address or get_local_ip()
path = self.hass.config.path(HOMEKIT_FILE)
self.bridge = HomeBridge(self.hass)
self.driver = HomeDriver(self.bridge, self._port, get_local_ip(), path)
self.driver = HomeDriver(self.bridge, self._port, ip_addr, path)

def add_bridge_accessory(self, state):
"""Try adding accessory to bridge if configured beforehand."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/homekit_controller/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
_LOGGER = logging.getLogger(__name__)


def homekit_http_send(self, message_body=None):
def homekit_http_send(self, message_body=None, encode_chunked=False):
r"""Send the currently buffered request and clear the buffer.

Appends an extra \r\n to the buffer.
Expand Down
19 changes: 3 additions & 16 deletions homeassistant/components/light/hue.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,26 +242,13 @@ def _color_mode(self):
@property
def hs_color(self):
"""Return the hs color value."""
# pylint: disable=redefined-outer-name
mode = self._color_mode

if mode not in ('hs', 'xy'):
return

source = self.light.action if self.is_group else self.light.state

hue = source.get('hue')
sat = source.get('sat')

# Sometimes the state will not include valid hue/sat values.
# Reported as issue 13434
if hue is not None and sat is not None:
return hue / 65535 * 360, sat / 255 * 100

if 'xy' not in source:
return None
if mode in ('xy', 'hs'):
return color.color_xy_to_hs(*source['xy'])

return color.color_xy_to_hs(*source['xy'])
return None

@property
def color_temp(self):
Expand Down
16 changes: 8 additions & 8 deletions homeassistant/components/light/tplink.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

from homeassistant.const import (CONF_HOST, CONF_NAME)
from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_KELVIN, ATTR_HS_COLOR,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_COLOR, PLATFORM_SCHEMA)
Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS,
SUPPORT_COLOR_TEMP, SUPPORT_COLOR, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
from homeassistant.util.color import \
color_temperature_mired_to_kelvin as mired_to_kelvin
Expand Down Expand Up @@ -90,15 +90,15 @@ def turn_on(self, **kwargs):
if ATTR_COLOR_TEMP in kwargs:
self.smartbulb.color_temp = \
mired_to_kelvin(kwargs[ATTR_COLOR_TEMP])
if ATTR_KELVIN in kwargs:
self.smartbulb.color_temp = kwargs[ATTR_KELVIN]
if ATTR_BRIGHTNESS in kwargs:
brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255)
self.smartbulb.brightness = brightness_to_percentage(brightness)

brightness = brightness_to_percentage(
kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255))
if ATTR_HS_COLOR in kwargs:
hue, sat = kwargs.get(ATTR_HS_COLOR)
hsv = (hue, sat, 100)
hsv = (int(hue), int(sat), brightness)
self.smartbulb.hsv = hsv
elif ATTR_BRIGHTNESS in kwargs:
self.smartbulb.brightness = brightness

def turn_off(self, **kwargs):
"""Turn the light off."""
Expand Down
26 changes: 18 additions & 8 deletions homeassistant/components/media_player/cast.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,18 @@ def async_cast_discovered(discover: ChromecastInfo):
_LOGGER.debug("Discovered chromecast with same UUID: %s", discover)
self.hass.async_add_job(self.async_set_cast_info(discover))

async def async_stop(event):
"""Disconnect socket on Home Assistant stop."""
await self._async_disconnect()

async_dispatcher_connect(self.hass, SIGNAL_CAST_DISCOVERED,
async_cast_discovered)
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop)
self.hass.async_add_job(self.async_set_cast_info(self._cast_info))

async def async_will_remove_from_hass(self) -> None:
"""Disconnect Chromecast object when removed."""
self._async_disconnect()
await self._async_disconnect()
if self._cast_info.uuid is not None:
# Remove the entity from the added casts so that it can dynamically
# be re-added again.
Expand All @@ -328,7 +333,7 @@ async def async_set_cast_info(self, cast_info):
if old_cast_info.host_port == cast_info.host_port:
# Nothing connection-related updated
return
self._async_disconnect()
await self._async_disconnect()

# Failed connection will unfortunately never raise an exception, it
# will instead just try connecting indefinitely.
Expand All @@ -348,22 +353,27 @@ async def async_set_cast_info(self, cast_info):
_LOGGER.debug("Connection successful!")
self.async_schedule_update_ha_state()

@callback
def _async_disconnect(self):
async def _async_disconnect(self):
"""Disconnect Chromecast object if it is set."""
if self._chromecast is None:
# Can't disconnect if not connected.
return
_LOGGER.debug("Disconnecting from previous chromecast socket.")
_LOGGER.debug("Disconnecting from chromecast socket.")
self._available = False
self._chromecast.disconnect(blocking=False)
self.async_schedule_update_ha_state()

await self.hass.async_add_job(self._chromecast.disconnect)

# Invalidate some attributes
self._chromecast = None
self.cast_status = None
self.media_status = None
self.media_status_received = None
self._status_listener.invalidate()
self._status_listener = None
if self._status_listener is not None:
self._status_listener.invalidate()
self._status_listener = None

self.async_schedule_update_ha_state()

# ========== Callbacks ==========
def new_cast_status(self, cast_status):
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/sensor/eliqonline.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv

REQUIREMENTS = ['eliqonline==1.0.13']
# pylint: disable=import-error, no-member
REQUIREMENTS = [] # ['eliqonline==1.0.13'] - package disappeared

_LOGGER = logging.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 68
PATCH_VERSION = '0'
PATCH_VERSION = '1'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 5, 3)
Expand Down
3 changes: 0 additions & 3 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,6 @@ dsmr_parser==0.11
# homeassistant.components.sensor.dweet
dweepy==0.3.0

# homeassistant.components.sensor.eliqonline
eliqonline==1.0.13

# homeassistant.components.enocean
enocean==0.40

Expand Down
67 changes: 60 additions & 7 deletions tests/components/config/test_automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ async def test_update_device_config(hass, aiohttp_client):
client = await aiohttp_client(hass.http.app)

orig_data = [
{
'id': 'sun',
},
{
'id': 'moon',
}
]
{
'id': 'sun',
},
{
'id': 'moon',
}
]

def mock_read(path):
"""Mock reading data."""
Expand Down Expand Up @@ -81,3 +81,56 @@ def mock_write(path, data):
'action': [],
}
assert written[0] == orig_data


async def test_bad_formatted_automations(hass, aiohttp_client):
"""Test that we handle automations without ID."""
with patch.object(config, 'SECTIONS', ['automation']):
await async_setup_component(hass, 'config', {})

client = await aiohttp_client(hass.http.app)

orig_data = [
{
# No ID
'action': {
'event': 'hello'
}
},
{
'id': 'moon',
}
]

def mock_read(path):
"""Mock reading data."""
return orig_data

written = []

def mock_write(path, data):
"""Mock writing data."""
written.append(data)

with patch('homeassistant.components.config._read', mock_read), \
patch('homeassistant.components.config._write', mock_write):
resp = await client.post(
'/api/config/automation/config/moon', data=json.dumps({
'trigger': [],
'action': [],
'condition': [],
}))

assert resp.status == 200
result = await resp.json()
assert result == {'result': 'ok'}

# Verify ID added to orig_data
assert 'id' in orig_data[0]

assert orig_data[1] == {
'id': 'moon',
'trigger': [],
'condition': [],
'action': [],
}
26 changes: 26 additions & 0 deletions tests/components/google_assistant/test_smart_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,29 @@ async def test_unavailable_state_doesnt_sync(hass):
'devices': []
}
}


async def test_empty_name_doesnt_sync(hass):
"""Test that an entity with empty name does not sync over."""
light = DemoLight(
None, ' ',
state=False,
)
light.hass = hass
light.entity_id = 'light.demo_light'
await light.async_update_ha_state()

result = await sh.async_handle_message(hass, BASIC_CONFIG, {
"requestId": REQ_ID,
"inputs": [{
"intent": "action.devices.SYNC"
}]
})

assert result == {
'requestId': REQ_ID,
'payload': {
'agentUserId': 'test-agent',
'devices': []
}
}
Loading