Merge pull request #4342 from home-assistant/release-0-32-3

0.32.3
This commit is contained in:
Paulus Schoutsen 2016-11-10 21:59:39 -08:00 committed by GitHub
commit 080f56e0f5
18 changed files with 154 additions and 113 deletions

View File

@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
add_devices([AlarmDotCom(hass, name, code, username, password)])
add_devices([AlarmDotCom(hass, name, code, username, password)], True)
class AlarmDotCom(alarm.AlarmControlPanel):
@ -54,12 +54,17 @@ class AlarmDotCom(alarm.AlarmControlPanel):
self._code = str(code) if code else None
self._username = username
self._password = password
self._state = STATE_UNKNOWN
@property
def should_poll(self):
"""No polling needed."""
return True
def update(self):
"""Fetch the latest state."""
self._state = self._alarm.state
@property
def name(self):
"""Return the name of the alarm."""
@ -73,11 +78,11 @@ class AlarmDotCom(alarm.AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
if self._alarm.state == 'Disarmed':
if self._state == 'Disarmed':
return STATE_ALARM_DISARMED
elif self._alarm.state == 'Armed Stay':
elif self._state == 'Armed Stay':
return STATE_ALARM_ARMED_HOME
elif self._alarm.state == 'Armed Away':
elif self._state == 'Armed Away':
return STATE_ALARM_ARMED_AWAY
else:
return STATE_UNKNOWN

View File

@ -91,7 +91,7 @@ class GenericCamera(Camera):
if url == self._last_url and self._limit_refetch:
return self._last_image
# aiohttp don't support DigestAuth jet
# aiohttp don't support DigestAuth yet
if self._authentication == HTTP_DIGEST_AUTHENTICATION:
def fetch():
"""Read image from a URL."""
@ -109,15 +109,17 @@ class GenericCamera(Camera):
else:
try:
with async_timeout.timeout(10, loop=self.hass.loop):
respone = yield from self.hass.websession.get(
url,
auth=self._auth
)
self._last_image = yield from respone.read()
yield from respone.release()
response = yield from self.hass.websession.get(
url, auth=self._auth)
self._last_image = yield from response.read()
yield from response.release()
except asyncio.TimeoutError:
_LOGGER.error('Timeout getting camera image')
return self._last_image
except (aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as err:
_LOGGER.error('Error getting new camera image: %s', err)
return self._last_image
self._last_url = url
return self._last_image

View File

@ -9,13 +9,14 @@ import logging
import voluptuous as vol
import aiohttp
from aiohttp import web
from aiohttp.web_exceptions import HTTPGatewayTimeout
import async_timeout
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL)
CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP)
from homeassistant.components.camera import (
Camera, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
@ -57,6 +58,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a Synology IP Camera."""
if not config.get(CONF_VERIFY_SSL):
connector = aiohttp.TCPConnector(verify_ssl=False)
else:
connector = None
websession_init = aiohttp.ClientSession(
loop=hass.loop,
connector=connector
)
# Determine API to use for authentication
syno_api_url = SYNO_API_URL.format(
config.get(CONF_URL), WEBAPI_PATH, QUERY_CGI)
@ -69,13 +80,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
}
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
query_req = yield from hass.websession.get(
query_req = yield from websession_init.get(
syno_api_url,
params=query_payload,
verify_ssl=config.get(CONF_VERIFY_SSL)
params=query_payload
)
except asyncio.TimeoutError:
_LOGGER.error("Timeout on %s", syno_api_url)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", syno_api_url)
return False
query_resp = yield from query_req.json()
@ -93,12 +103,26 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
session_id = yield from get_session_id(
hass,
websession_init,
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
syno_auth_url,
config.get(CONF_VERIFY_SSL)
syno_auth_url
)
websession_init.detach()
# init websession
websession = aiohttp.ClientSession(
loop=hass.loop, connector=connector, cookies={'id': session_id})
@asyncio.coroutine
def _async_close_websession(event):
"""Close webssesion on shutdown."""
yield from websession.close()
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, _async_close_websession)
# Use SessionID to get cameras in system
syno_camera_url = SYNO_API_URL.format(
config.get(CONF_URL), WEBAPI_PATH, camera_api)
@ -110,14 +134,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
}
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
camera_req = yield from hass.websession.get(
camera_req = yield from websession.get(
syno_camera_url,
params=camera_payload,
verify_ssl=config.get(CONF_VERIFY_SSL),
cookies={'id': session_id}
params=camera_payload
)
except asyncio.TimeoutError:
_LOGGER.error("Timeout on %s", syno_camera_url)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", syno_camera_url)
return False
camera_resp = yield from camera_req.json()
@ -126,13 +148,14 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
# add cameras
devices = []
tasks = []
for camera in cameras:
if not config.get(CONF_WHITELIST):
camera_id = camera['id']
snapshot_path = camera['snapshot_path']
device = SynologyCamera(
hass,
websession,
config,
camera_id,
camera['name'],
@ -141,15 +164,13 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
camera_path,
auth_path
)
tasks.append(device.async_read_sid())
devices.append(device)
yield from asyncio.gather(*tasks, loop=hass.loop)
hass.loop.create_task(async_add_devices(devices))
yield from async_add_devices(devices)
@asyncio.coroutine
def get_session_id(hass, username, password, login_url, valid_cert):
def get_session_id(hass, websession, username, password, login_url):
"""Get a session id."""
auth_payload = {
'api': AUTH_API,
@ -162,13 +183,12 @@ def get_session_id(hass, username, password, login_url, valid_cert):
}
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
auth_req = yield from hass.websession.get(
auth_req = yield from websession.get(
login_url,
params=auth_payload,
verify_ssl=valid_cert
params=auth_payload
)
except asyncio.TimeoutError:
_LOGGER.error("Timeout on %s", login_url)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", login_url)
return False
auth_resp = yield from auth_req.json()
@ -180,36 +200,22 @@ def get_session_id(hass, username, password, login_url, valid_cert):
class SynologyCamera(Camera):
"""An implementation of a Synology NAS based IP camera."""
def __init__(self, config, camera_id, camera_name,
snapshot_path, streaming_path, camera_path, auth_path):
def __init__(self, hass, websession, config, camera_id,
camera_name, snapshot_path, streaming_path, camera_path,
auth_path):
"""Initialize a Synology Surveillance Station camera."""
super().__init__()
self.hass = hass
self._websession = websession
self._name = camera_name
self._username = config.get(CONF_USERNAME)
self._password = config.get(CONF_PASSWORD)
self._synology_url = config.get(CONF_URL)
self._api_url = config.get(CONF_URL) + 'webapi/'
self._login_url = config.get(CONF_URL) + '/webapi/' + 'auth.cgi'
self._camera_name = config.get(CONF_CAMERA_NAME)
self._stream_id = config.get(CONF_STREAM_ID)
self._valid_cert = config.get(CONF_VERIFY_SSL)
self._camera_id = camera_id
self._snapshot_path = snapshot_path
self._streaming_path = streaming_path
self._camera_path = camera_path
self._auth_path = auth_path
self._session_id = None
@asyncio.coroutine
def async_read_sid(self):
"""Get a session id."""
self._session_id = yield from get_session_id(
self.hass,
self._username,
self._password,
self._login_url,
self._valid_cert
)
def camera_image(self):
"""Return bytes of camera image."""
@ -230,14 +236,12 @@ class SynologyCamera(Camera):
}
try:
with async_timeout.timeout(TIMEOUT, loop=self.hass.loop):
response = yield from self.hass.websession.get(
response = yield from self._websession.get(
image_url,
params=image_payload,
verify_ssl=self._valid_cert,
cookies={'id': self._session_id}
params=image_payload
)
except asyncio.TimeoutError:
_LOGGER.error("Timeout on %s", image_url)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", image_url)
return None
image = yield from response.read()
@ -260,13 +264,12 @@ class SynologyCamera(Camera):
}
try:
with async_timeout.timeout(TIMEOUT, loop=self.hass.loop):
stream = yield from self.hass.websession.get(
stream = yield from self._websession.get(
streaming_url,
payload=streaming_payload,
verify_ssl=self._valid_cert,
cookies={'id': self._session_id}
params=streaming_payload
)
except asyncio.TimeoutError:
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", streaming_url)
raise HTTPGatewayTimeout()
response = web.StreamResponse()
@ -281,7 +284,7 @@ class SynologyCamera(Camera):
break
response.write(data)
finally:
self.hass.loop.create_task(stream.release())
self.hass.async_add_job(stream.release())
yield from response.write_eof()
@property

View File

@ -56,6 +56,8 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
self._unit_of_measurement = TEMP_CELSIUS # KNX always used celsius
self._away = False # not yet supported
self._is_fan_on = False # not yet supported
self._current_temp = None
self._target_temp = None
@property
def should_poll(self):
@ -70,16 +72,12 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
from knxip.conversion import knx2_to_float
return knx2_to_float(self.value('temperature'))
return self._current_temp
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
from knxip.conversion import knx2_to_float
return knx2_to_float(self.value('setpoint'))
return self._target_temp
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@ -94,3 +92,12 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
raise NotImplementedError()
def update(self):
"""Update KNX climate."""
from knxip.conversion import knx2_to_float
super().update()
self._current_temp = knx2_to_float(self.value('temperature'))
self._target_temp = knx2_to_float(self.value('setpoint'))

View File

@ -161,8 +161,6 @@ class KNXGroupAddress(Entity):
@property
def is_on(self):
"""Return True if the value is not 0 is on, else False."""
if self.should_poll:
self.update()
return self._state != 0
@property

View File

@ -23,6 +23,7 @@ REQUIREMENTS = ['https://github.com/Danielhiversen/flux_led/archive/0.8.zip'
_LOGGER = logging.getLogger(__name__)
CONF_AUTOMATIC_ADD = 'automatic_add'
ATTR_MODE = 'mode'
DOMAIN = 'flux_led'
@ -31,6 +32,8 @@ SUPPORT_FLUX_LED = (SUPPORT_BRIGHTNESS | SUPPORT_EFFECT |
DEVICE_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(ATTR_MODE, default='rgbw'):
vol.All(cv.string, vol.In(['rgbw', 'rgb'])),
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -48,6 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
device = {}
device['name'] = device_config[CONF_NAME]
device['ipaddr'] = ipaddr
device[ATTR_MODE] = device_config[ATTR_MODE]
light = FluxLight(device)
if light.is_valid:
lights.append(light)
@ -65,6 +69,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if ipaddr in light_ips:
continue
device['name'] = device['id'] + " " + ipaddr
device[ATTR_MODE] = 'rgbw'
light = FluxLight(device)
if light.is_valid:
lights.append(light)
@ -82,6 +87,7 @@ class FluxLight(Light):
self._name = device['name']
self._ipaddr = device['ipaddr']
self._mode = device[ATTR_MODE]
self.is_valid = True
self._bulb = None
try:
@ -132,7 +138,11 @@ class FluxLight(Light):
if rgb:
self._bulb.setRgb(*tuple(rgb))
elif brightness:
self._bulb.setWarmWhite255(brightness)
if self._mode == 'rgbw':
self._bulb.setWarmWhite255(brightness)
elif self._mode == 'rgb':
(red, green, blue) = self._bulb.getRgb()
self._bulb.setRgb(red, green, blue, brightness=brightness)
elif effect == EFFECT_RANDOM:
self._bulb.setRgb(random.randrange(0, 255),
random.randrange(0, 255),

View File

@ -79,16 +79,16 @@ class PanasonicVieraTVDevice(MediaPlayerDevice):
self._playing = True
self._state = STATE_UNKNOWN
self._remote = remote
self._volume = 0
def update(self):
"""Retrieve the latest data."""
try:
self._muted = self._remote.get_mute()
self._volume = self._remote.get_volume() / 100
self._state = STATE_ON
except OSError:
self._state = STATE_OFF
return False
return True
def send_key(self, key):
"""Send a key to the tv and handles exceptions."""
@ -113,13 +113,7 @@ class PanasonicVieraTVDevice(MediaPlayerDevice):
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
volume = 0
try:
volume = self._remote.get_volume() / 100
self._state = STATE_ON
except OSError:
self._state = STATE_OFF
return volume
return self._volume
@property
def is_volume_muted(self):

View File

@ -21,9 +21,7 @@ from homeassistant.const import (
from homeassistant.config import load_yaml_config_file
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['https://github.com/SoCo/SoCo/archive/'
'cf8c2701165562eccbf1ecc879bf7060ceb0993e.zip#'
'SoCo==0.12']
REQUIREMENTS = ['SoCo==0.12']
_LOGGER = logging.getLogger(__name__)

View File

@ -18,7 +18,7 @@ from homeassistant.const import (CONF_NAME, CONF_HOST, STATE_OFF, STATE_ON,
STATE_PLAYING, STATE_IDLE)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['rxv==0.3.0']
REQUIREMENTS = ['rxv==0.3.1']
_LOGGER = logging.getLogger(__name__)
@ -35,6 +35,7 @@ CONF_SOURCE_IGNORE = 'source_ignore'
CONF_ZONE_IGNORE = 'zone_ignore'
DEFAULT_NAME = 'Yamaha Receiver'
KNOWN = 'yamaha_known_receivers'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@ -50,6 +51,11 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Yamaha platform."""
import rxv
# keep track of configured receivers so that we don't end up
# discovering a receiver dynamically that we have static config
# for.
if hass.data.get(KNOWN, None) is None:
hass.data[KNOWN] = set()
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
@ -62,12 +68,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
model = discovery_info[1]
ctrl_url = discovery_info[2]
desc_url = discovery_info[3]
if ctrl_url in hass.data[KNOWN]:
_LOGGER.info("%s already manually configured", ctrl_url)
return
receivers = rxv.RXV(
ctrl_url,
model_name=model,
friendly_name=name,
unit_desc_url=desc_url).zone_controllers()
_LOGGER.info("Receivers: %s", receivers)
# when we are dynamically discovered config is empty
zone_ignore = []
elif host is None:
receivers = []
for recv in rxv.find():
@ -78,6 +89,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for receiver in receivers:
if receiver.zone not in zone_ignore:
hass.data[KNOWN].add(receiver.ctrl_url)
add_devices([
YamahaDevice(name, receiver, source_ignore, source_names)])

View File

@ -113,15 +113,24 @@ class KNXSensorFloatClass(KNXGroupAddress, KNXSensorBaseClass):
self._unit_of_measurement = unit_of_measurement
self._minimum_value = minimum_sensor_value
self._maximum_value = maximum_sensor_value
self._value = None
KNXGroupAddress.__init__(self, hass, config)
@property
def state(self):
"""Return the Value of the KNX Sensor."""
return self._value
def update(self):
"""Update KNX sensor."""
from knxip.conversion import knx2_to_float
super().update()
self._value = None
if self._data:
from knxip.conversion import knx2_to_float
value = knx2_to_float(self._data)
if self._minimum_value <= value <= self._maximum_value:
return value
return None
self._value = value

View File

@ -98,6 +98,7 @@ class TellstickSensor(Entity):
self.datatype = datatype
self.sensor = sensor
self._unit_of_measurement = sensor_info.unit or None
self._value = None
self._name = '{} {}'.format(name, sensor_info.name)
@ -109,9 +110,13 @@ class TellstickSensor(Entity):
@property
def state(self):
"""Return the state of the sensor."""
return self.sensor.value(self.datatype).value
return self._value
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement
def update(self):
"""Update tellstick sensor."""
self._value = self.sensor.value(self.datatype).value

View File

@ -10,7 +10,7 @@ import logging
from xml.parsers.expat import ExpatError
import async_timeout
from aiohttp.web import HTTPException
import aiohttp
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
@ -154,12 +154,9 @@ class YrData(object):
try_again('{} returned {}'.format(self._url, resp.status))
return
text = yield from resp.text()
self.hass.loop.create_task(resp.release())
except asyncio.TimeoutError as err:
try_again(err)
return
except HTTPException as err:
resp.close()
self.hass.async_add_job(resp.release)
except (asyncio.TimeoutError, aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as err:
try_again(err)
return
@ -218,4 +215,5 @@ class YrData(object):
dev._state = new_state
tasks.append(dev.async_update_ha_state())
yield from asyncio.gather(*tasks, loop=self.hass.loop)
if tasks:
yield from asyncio.wait(tasks, loop=self.hass.loop)

View File

@ -27,10 +27,10 @@ DEPENDENCIES = ['pilight']
COMMAND_SCHEMA = pilight.RF_CODE_SCHEMA.extend({
vol.Optional('on'): cv.positive_int,
vol.Optional('off'): cv.positive_int,
vol.Optional(CONF_UNIT): cv.string,
vol.Optional(CONF_UNIT): cv.positive_int,
vol.Optional(CONF_ID): cv.positive_int,
vol.Optional(CONF_STATE): cv.string,
vol.Optional(CONF_SYSTEMCODE): cv.string,
vol.Optional(CONF_SYSTEMCODE): cv.positive_int,
})
SWITCHES_SCHEMA = vol.Schema({

View File

@ -12,11 +12,12 @@ import voluptuous as vol
from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, CONF_RESOURCE, CONF_TIMEOUT)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.template import Template
CONF_BODY_OFF = 'body_off'
CONF_BODY_ON = 'body_on'
DEFAULT_BODY_OFF = 'OFF'
DEFAULT_BODY_ON = 'ON'
DEFAULT_BODY_OFF = Template('OFF')
DEFAULT_BODY_ON = Template('ON')
DEFAULT_NAME = 'REST Switch'
DEFAULT_TIMEOUT = 10
CONF_IS_ON_TEMPLATE = 'is_on_template'

View File

@ -67,7 +67,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
data = WeatherData(owm, latitude, longitude)
add_devices([OpenWeatherMapWeather(
name, data, hass.config.units.temperature_unit)])
name, data, hass.config.units.temperature_unit)], True)
class OpenWeatherMapWeather(WeatherEntity):
@ -78,8 +78,7 @@ class OpenWeatherMapWeather(WeatherEntity):
self._name = name
self._owm = owm
self._temperature_unit = temperature_unit
self.date = None
self.update()
self.data = None
@property
def name(self):

View File

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

View File

@ -242,7 +242,7 @@ class Entity(object):
end = timer()
if end - start > 0.2:
if end - start > 0.4:
_LOGGER.warning('Updating state for %s took %.3f seconds. '
'Please report platform to the developers at '
'https://goo.gl/Nvioub', self.entity_id,

View File

@ -24,6 +24,9 @@ PyMata==2.13
# homeassistant.components.rpi_gpio
# RPi.GPIO==0.6.1
# homeassistant.components.media_player.sonos
SoCo==0.12
# homeassistant.components.notify.twitter
TwitterAPI==2.4.2
@ -157,9 +160,6 @@ https://github.com/GadgetReactor/pyHS100/archive/1f771b7d8090a91c6a58931532e4273
# homeassistant.components.switch.dlink
https://github.com/LinuxChristian/pyW215/archive/v0.3.5.zip#pyW215==0.3.5
# homeassistant.components.media_player.sonos
https://github.com/SoCo/SoCo/archive/cf8c2701165562eccbf1ecc879bf7060ceb0993e.zip#SoCo==0.12
# homeassistant.components.media_player.webostv
# homeassistant.components.notify.webostv
https://github.com/TheRealLink/pylgtv/archive/v0.1.2.zip#pylgtv==0.1.2
@ -464,7 +464,7 @@ radiotherm==1.2
# rpi-rf==0.9.5
# homeassistant.components.media_player.yamaha
rxv==0.3.0
rxv==0.3.1
# homeassistant.components.media_player.samsungtv
samsungctl==0.5.1