Merge pull request #13369 from home-assistant/release-0-65-6

0.65.6
pull/13430/head 0.65.6
Paulus Schoutsen 2018-03-21 10:59:58 -07:00 committed by GitHub
commit c3974e540b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 72 additions and 45 deletions

View File

@ -56,34 +56,6 @@ async def async_setup_platform(hass, config, async_add_devices,
async_add_devices([ProxyCamera(hass, config)])
async def _read_frame(req):
"""Read a single frame from an MJPEG stream."""
# based on https://gist.github.com/russss/1143799
import cgi
# Read in HTTP headers:
stream = req.content
# multipart/x-mixed-replace; boundary=--frameboundary
_mimetype, options = cgi.parse_header(req.headers['content-type'])
boundary = options.get('boundary').encode('utf-8')
if not boundary:
_LOGGER.error("Malformed MJPEG missing boundary")
raise Exception("Can't find content-type")
line = await stream.readline()
# Seek ahead to the first chunk
while line.strip() != boundary:
line = await stream.readline()
# Read in chunk headers
while line.strip() != b'':
parts = line.split(b':')
if len(parts) > 1 and parts[0].lower() == b'content-length':
# Grab chunk length
length = int(parts[1].strip())
line = await stream.readline()
image = await stream.read(length)
return image
def _resize_image(image, opts):
"""Resize image."""
from PIL import Image
@ -227,9 +199,9 @@ class ProxyCamera(Camera):
'boundary=--frameboundary')
await response.prepare(request)
def write(img_bytes):
async def write(img_bytes):
"""Write image to stream."""
response.write(bytes(
await response.write(bytes(
'--frameboundary\r\n'
'Content-Type: {}\r\n'
'Content-Length: {}\r\n\r\n'.format(
@ -240,13 +212,23 @@ class ProxyCamera(Camera):
req = await stream_coro
try:
# This would be nicer as an async generator
# But that would only be supported for python >=3.6
data = b''
stream = req.content
while True:
image = await _read_frame(req)
if not image:
chunk = await stream.read(102400)
if not chunk:
break
image = await self.hass.async_add_job(
_resize_image, image, self._stream_opts)
write(image)
data += chunk
jpg_start = data.find(b'\xff\xd8')
jpg_end = data.find(b'\xff\xd9')
if jpg_start != -1 and jpg_end != -1:
image = data[jpg_start:jpg_end + 2]
image = await self.hass.async_add_job(
_resize_image, image, self._stream_opts)
await write(image)
data = data[jpg_end + 2:]
except asyncio.CancelledError:
_LOGGER.debug("Stream closed by frontend.")
req.close()

View File

@ -100,7 +100,7 @@ class TadoDeviceScanner(DeviceScanner):
last_results = []
try:
with async_timeout.timeout(10, loop=self.hass.loop):
with async_timeout.timeout(10):
# Format the URL here, so we can log the template URL if
# anything goes wrong without exposing username and password.
url = self.tadoapiurl.format(

View File

@ -94,9 +94,16 @@ class _GoogleEntity:
https://developers.google.com/actions/smarthome/create-app#actiondevicessync
"""
traits = self.traits()
state = self.state
# When a state is unavailable, the attributes that describe
# capabilities will be stripped. For example, a light entity will miss
# the min/max mireds. Therefore they will be excluded from a sync.
if state.state == STATE_UNAVAILABLE:
return None
traits = self.traits()
# Found no supported traits for this entity
if not traits:
return None

View File

@ -54,6 +54,7 @@ class DemoLight(Light):
self._white = white
self._effect_list = effect_list
self._effect = effect
self._available = True
@property
def should_poll(self) -> bool:
@ -75,7 +76,7 @@ class DemoLight(Light):
"""Return availability."""
# This demo light is always available, but well-behaving components
# should implement this to inform Home Assistant accordingly.
return True
return self._available
@property
def brightness(self) -> int:

View File

@ -194,13 +194,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
master = [device for device in hass.data[DATA_SONOS].devices
if device.entity_id == service.data[ATTR_MASTER]]
if master:
master[0].join(devices)
with hass.data[DATA_SONOS].topology_lock:
master[0].join(devices)
return
if service.service == SERVICE_UNJOIN:
with hass.data[DATA_SONOS].topology_lock:
for device in devices:
device.unjoin()
return
for device in devices:
if service.service == SERVICE_UNJOIN:
device.unjoin()
elif service.service == SERVICE_SNAPSHOT:
if service.service == SERVICE_SNAPSHOT:
device.snapshot(service.data[ATTR_WITH_GROUP])
elif service.service == SERVICE_RESTORE:
device.restore(service.data[ATTR_WITH_GROUP])
@ -799,7 +804,9 @@ class SonosDevice(MediaPlayerDevice):
src = fav.pop()
uri = src.reference.get_uri()
if _is_radio_uri(uri):
self.soco.play_uri(uri, title=source)
# SoCo 0.14 fails to XML escape the title parameter
from xml.sax.saxutils import escape
self.soco.play_uri(uri, title=escape(source))
else:
self.soco.clear_queue()
self.soco.add_to_queue(src.reference)
@ -893,16 +900,19 @@ class SonosDevice(MediaPlayerDevice):
def join(self, slaves):
"""Form a group with other players."""
if self._coordinator:
self.soco.unjoin()
self.unjoin()
for slave in slaves:
if slave.unique_id != self.unique_id:
slave.soco.join(self.soco)
# pylint: disable=protected-access
slave._coordinator = self
@soco_error()
def unjoin(self):
"""Unjoin the player from a group."""
self.soco.unjoin()
self._coordinator = None
@soco_error()
def snapshot(self, with_group=True):

View File

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

View File

@ -259,3 +259,30 @@ def test_serialize_input_boolean():
'type': 'action.devices.types.SWITCH',
'willReportState': False,
}
async def test_unavailable_state_doesnt_sync(hass):
"""Test that an unavailable entity does not sync over."""
light = DemoLight(
None, 'Demo Light',
state=False,
)
light.hass = hass
light.entity_id = 'light.demo_light'
light._available = False
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': []
}
}