Merge pull request #5707 from home-assistant/release-0-37-1

0.37.1
This commit is contained in:
Paulus Schoutsen 2017-02-01 21:34:52 -08:00 committed by GitHub
commit 686c8466a0
18 changed files with 267 additions and 207 deletions

View File

@ -26,8 +26,6 @@ WELCOME_SENSOR_TYPES = {
"Someone known": "motion",
"Someone unknown": "motion",
"Motion": "motion",
"Tag Vibration": 'vibration',
"Tag Open": 'opening'
}
PRESENCE_SENSOR_TYPES = {
"Outdoor motion": "motion",
@ -35,11 +33,16 @@ PRESENCE_SENSOR_TYPES = {
"Outdoor animal": "motion",
"Outdoor vehicle": "motion"
}
TAG_SENSOR_TYPES = {
"Tag Vibration": 'vibration',
"Tag Open": 'opening'
}
CONF_HOME = 'home'
CONF_CAMERAS = 'cameras'
CONF_WELCOME_SENSORS = 'welcome_sensors'
CONF_PRESENCE_SENSORS = 'presence_sensors'
CONF_TAG_SENSORS = 'tag_sensors'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOME): cv.string,
@ -78,6 +81,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
CONF_WELCOME_SENSORS, WELCOME_SENSOR_TYPES)
presence_sensors = config.get(
CONF_PRESENCE_SENSORS, PRESENCE_SENSOR_TYPES)
tag_sensors = config.get(CONF_TAG_SENSORS, TAG_SENSOR_TYPES)
for camera_name in data.get_camera_names():
camera_type = data.get_camera_type(camera=camera_name, home=home)
@ -103,8 +107,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
variable)])
for module_name in data.get_module_names(camera_name):
for variable in welcome_sensors:
if variable in ('Tag Vibration', 'Tag Open'):
for variable in tag_sensors:
camera_type = None
add_devices([NetatmoBinarySensor(data, camera_name,
module_name, home,
timeout, offset,
@ -157,7 +161,7 @@ class NetatmoBinarySensor(BinarySensorDevice):
elif self._cameratype == "NOC":
return PRESENCE_SENSOR_TYPES.get(self._sensor_name)
else:
return None
return TAG_SENSOR_TYPES.get(self._sensor_name)
@property
def is_on(self):
@ -184,8 +188,6 @@ class NetatmoBinarySensor(BinarySensorDevice):
self._data.camera_data.motionDetected(self._home,
self._camera_name,
self._timeout*60)
else:
return None
elif self._cameratype == "NOC":
if self._sensor_name == "Outdoor motion":
self._state =\
@ -206,9 +208,7 @@ class NetatmoBinarySensor(BinarySensorDevice):
self._data.camera_data.carDetected(self._home,
self._camera_name,
self._offset)
else:
return None
elif self._sensor_name == "Tag Vibration":
if self._sensor_name == "Tag Vibration":
self._state =\
self._data.camera_data.moduleMotionDetected(self._home,
self._module_name,

View File

@ -18,7 +18,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_stream)
REQUIREMENTS = ['amcrest==1.1.3']
REQUIREMENTS = ['amcrest==1.1.4']
_LOGGER = logging.getLogger(__name__)

View File

@ -9,6 +9,7 @@ import logging
import requests
import voluptuous as vol
from homeassistant.const import CONF_VERIFY_SSL
from homeassistant.components.netatmo import CameraData
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.loader import get_component
@ -22,6 +23,7 @@ CONF_HOME = 'home'
CONF_CAMERAS = 'cameras'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_CAMERAS, default=[]):
vol.All(cv.ensure_list, [cv.string]),
@ -33,6 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup access to Netatmo cameras."""
netatmo = get_component('netatmo')
home = config.get(CONF_HOME)
verify_ssl = config.get(CONF_VERIFY_SSL, True)
import lnetatmo
try:
data = CameraData(netatmo.NETATMO_AUTH, home)
@ -42,7 +45,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if config[CONF_CAMERAS] != [] and \
camera_name not in config[CONF_CAMERAS]:
continue
add_devices([NetatmoCamera(data, camera_name, home, camera_type)])
add_devices([NetatmoCamera(data, camera_name, home,
camera_type, verify_ssl)])
except lnetatmo.NoDevice:
return None
@ -50,11 +54,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class NetatmoCamera(Camera):
"""Representation of the images published from a Netatmo camera."""
def __init__(self, data, camera_name, home, camera_type):
def __init__(self, data, camera_name, home, camera_type, verify_ssl):
"""Setup for access to the Netatmo camera images."""
super(NetatmoCamera, self).__init__()
self._data = data
self._camera_name = camera_name
self._verify_ssl = verify_ssl
if home:
self._name = home + ' / ' + camera_name
else:
@ -74,11 +79,17 @@ class NetatmoCamera(Camera):
if self._localurl:
response = requests.get('{0}/live/snapshot_720.jpg'.format(
self._localurl), timeout=10)
else:
elif self._vpnurl:
response = requests.get('{0}/live/snapshot_720.jpg'.format(
self._vpnurl), timeout=10)
self._vpnurl), timeout=10, verify=self._verify_ssl)
else:
_LOGGER.error('Welcome VPN url is None')
self._data.update()
(self._vpnurl, self._localurl) = \
self._data.camera_data.cameraUrls(camera=self._camera_name)
return None
except requests.exceptions.RequestException as error:
_LOGGER.error('Welcome VPN url changed: %s', error)
_LOGGER.error('Welcome url changed: %s', error)
self._data.update()
(self._vpnurl, self._localurl) = \
self._data.camera_data.cameraUrls(camera=self._camera_name)

View File

@ -92,7 +92,8 @@ class UPCDeviceScanner(DeviceScanner):
raw = yield from self._async_ws_function(CMD_DEVICES)
try:
xml_root = ET.fromstring(raw)
xml_root = yield from self.hass.loop.run_in_executor(
None, ET.fromstring, raw)
return [mac.text for mac in xml_root.iter('MACAddr')]
except (ET.ParseError, TypeError):
_LOGGER.warning("Can't read device from %s", self.host)

View File

@ -16,17 +16,16 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN, CONF_USERNAME, CONF_PASSWORD,
CONF_PLATFORM, CONF_HOSTS, CONF_NAME, ATTR_ENTITY_ID)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_interval
from homeassistant.config import load_yaml_config_file
from homeassistant.util import Throttle
DOMAIN = 'homematic'
REQUIREMENTS = ["pyhomematic==0.1.20"]
REQUIREMENTS = ["pyhomematic==0.1.21"]
MIN_TIME_BETWEEN_UPDATE_HUB = timedelta(seconds=300)
SCAN_INTERVAL = timedelta(seconds=30)
SCAN_INTERVAL_HUB = timedelta(seconds=300)
SCAN_INTERVAL_VARIABLES = timedelta(seconds=30)
DISCOVER_SWITCHES = 'homematic.switch'
DISCOVER_LIGHTS = 'homematic.light'
@ -176,8 +175,9 @@ SCHEMA_SERVICE_VIRTUALKEY = vol.Schema({
})
SCHEMA_SERVICE_SET_VAR_VALUE = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_NAME): cv.string,
vol.Required(ATTR_VALUE): cv.match_all,
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
SCHEMA_SERVICE_SET_DEV_VALUE = vol.Schema({
@ -236,8 +236,6 @@ def setup(hass, config):
"""Setup the Homematic component."""
from pyhomematic import HMConnection
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
hass.data[DATA_DELAY] = config[DOMAIN].get(CONF_DELAY)
hass.data[DATA_DEVINIT] = {}
hass.data[DATA_STORE] = []
@ -281,11 +279,10 @@ def setup(hass, config):
hass.config.components.append(DOMAIN)
# init homematic hubs
hub_entities = []
entity_hubs = []
for _, hub_data in hosts.items():
hub_entities.append(HMHub(hass, component, hub_data[CONF_NAME],
hub_data[CONF_VARIABLES]))
component.add_entities(hub_entities)
entity_hubs.append(HMHub(
hass, hub_data[CONF_NAME], hub_data[CONF_VARIABLES]))
# regeister homematic services
descriptions = load_yaml_config_file(
@ -323,14 +320,23 @@ def setup(hass, config):
schema=SCHEMA_SERVICE_VIRTUALKEY)
def _service_handle_value(service):
"""Set value on homematic variable object."""
variable_list = component.extract_from_service(service)
"""Set value on homematic variable."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
name = service.data[ATTR_NAME]
value = service.data[ATTR_VALUE]
for hm_variable in variable_list:
if isinstance(hm_variable, HMVariable):
hm_variable.hm_set(value)
if entity_ids:
entities = [entity for entity in entity_hubs if
entity.entity_id in entity_ids]
else:
entities = entity_hubs
if not entities:
_LOGGER.error("Homematic controller not found!")
return
for hub in entities:
hub.hm_set_variable(name, value)
hass.services.register(
DOMAIN, SERVICE_SET_VAR_VALUE, _service_handle_value,
@ -579,132 +585,87 @@ def _device_from_servicecall(hass, service):
class HMHub(Entity):
"""The Homematic hub. I.e. CCU2/HomeGear."""
def __init__(self, hass, component, name, use_variables):
def __init__(self, hass, name, use_variables):
"""Initialize Homematic hub."""
self.hass = hass
self.entity_id = "{}.{}".format(DOMAIN, name.lower())
self._homematic = hass.data[DATA_HOMEMATIC]
self._component = component
self._variables = {}
self._name = name
self._state = STATE_UNKNOWN
self._store = {}
self._use_variables = use_variables
# load data
self._update_hub_state()
self._init_variables()
track_time_interval(hass, self._update_hub, SCAN_INTERVAL_HUB)
self._update_hub(None)
if self._use_variables:
track_time_interval(
hass, self._update_variables, SCAN_INTERVAL_VARIABLES)
self._update_variables(None)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the entity."""
return self._state
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
return {}
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return "mdi:gradient"
def update(self):
"""Update Hub data and all HM variables."""
self._update_hub_state()
self._update_variables_state()
@Throttle(MIN_TIME_BETWEEN_UPDATE_HUB)
def _update_hub_state(self):
"""Retrieve latest state."""
state = self._homematic.getServiceMessages(self._name)
self._state = STATE_UNKNOWN if state is None else len(state)
def _update_variables_state(self):
"""Retrive all variable data and update hmvariable states."""
if not self._use_variables:
return
variables = self._homematic.getAllSystemVariables(self._name)
if variables is None:
return
for key, value in variables.items():
if key in self._store:
self._store.get(key).hm_update(value)
def _init_variables(self):
"""Load variables from hub."""
if not self._use_variables:
return
variables = self._homematic.getAllSystemVariables(self._name)
if variables is None:
return
entities = []
for key, value in variables.items():
entities.append(HMVariable(self.hass, self._name, key, value))
self._component.add_entities(entities)
class HMVariable(Entity):
"""The Homematic system variable."""
def __init__(self, hass, hub_name, name, state):
"""Initialize Homematic hub."""
self.hass = hass
self._homematic = hass.data[DATA_HOMEMATIC]
self._state = state
self._name = name
self._hub_name = hub_name
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the entity."""
return self._state
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return "mdi:code-string"
@property
def should_poll(self):
"""Return false. Homematic Hub object update variable."""
return False
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
attr = {
'hub': self._hub_name,
}
def state(self):
"""Return the state of the entity."""
return self._state
@property
def state_attributes(self):
"""Return the state attributes."""
attr = self._variables.copy()
return attr
def hm_update(self, value):
"""Update variable over Hub object."""
if value != self._state:
self._state = value
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return "mdi:gradient"
def _update_hub(self, now):
"""Retrieve latest state."""
state = self._homematic.getServiceMessages(self._name)
self._state = STATE_UNKNOWN if state is None else len(state)
self.schedule_update_ha_state()
def hm_set(self, value):
def _update_variables(self, now):
"""Retrive all variable data and update hmvariable states."""
variables = self._homematic.getAllSystemVariables(self._name)
if variables is None:
return
state_change = False
for key, value in variables.items():
if key in self._variables and value == self._variables[key]:
continue
state_change = True
self._variables.update({key: value})
if state_change:
self.schedule_update_ha_state()
def hm_set_variable(self, name, value):
"""Set variable on homematic controller."""
if isinstance(self._state, bool):
if name not in self._variables:
_LOGGER.error("Variable %s not found on %s", name, self.name)
return
old_value = self._variables.get(name)
if isinstance(old_value, bool):
value = cv.boolean(value)
else:
value = float(value)
self._homematic.setSystemVariable(self._hub_name, self._name, value)
self._state = value
self._homematic.setSystemVariable(self.name, name, value)
self._variables.update({name: value})
self.schedule_update_ha_state()
@ -817,7 +778,7 @@ class HMDevice(Entity):
have_change = True
# If available it has changed
if attribute is 'UNREACH':
if attribute == 'UNREACH':
self._available = bool(value)
have_change = True
@ -829,7 +790,7 @@ class HMDevice(Entity):
def _subscribe_homematic_events(self):
"""Subscribe all required events to handle job."""
channels_to_sub = {}
channels_to_sub = {0: True} # add channel 0 for UNREACH
# Push data to channels_to_sub from hmdevice metadata
for metadata in (self._hmdevice.SENSORNODE, self._hmdevice.BINARYNODE,
@ -856,7 +817,7 @@ class HMDevice(Entity):
# Set callbacks
for channel in channels_to_sub:
_LOGGER.debug(
"Subscribe channel %s from %s", str(channel), self._name)
"Subscribe channel %d from %s", channel, self._name)
self._hmdevice.setEventCallback(
callback=self._hm_event_callback, bequeath=False,
channel=channel)

View File

@ -59,11 +59,9 @@ DEFAULT_ALLOW_HUE_GROUPS = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_ALLOW_UNREACHABLE,
default=DEFAULT_ALLOW_UNREACHABLE): cv.boolean,
vol.Optional(CONF_FILENAME, default=PHUE_CONFIG_FILE): cv.string,
vol.Optional(CONF_ALLOW_IN_EMULATED_HUE,
default=DEFAULT_ALLOW_IN_EMULATED_HUE): cv.boolean,
vol.Optional(CONF_ALLOW_UNREACHABLE): cv.boolean,
vol.Optional(CONF_FILENAME): cv.string,
vol.Optional(CONF_ALLOW_IN_EMULATED_HUE): cv.boolean,
vol.Optional(CONF_ALLOW_HUE_GROUPS,
default=DEFAULT_ALLOW_HUE_GROUPS): cv.boolean,
})
@ -98,9 +96,11 @@ def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Hue lights."""
# Default needed in case of discovery
filename = config.get(CONF_FILENAME)
allow_unreachable = config.get(CONF_ALLOW_UNREACHABLE)
allow_in_emulated_hue = config.get(CONF_ALLOW_IN_EMULATED_HUE)
filename = config.get(CONF_FILENAME, PHUE_CONFIG_FILE)
allow_unreachable = config.get(CONF_ALLOW_UNREACHABLE,
DEFAULT_ALLOW_UNREACHABLE)
allow_in_emulated_hue = config.get(CONF_ALLOW_IN_EMULATED_HUE,
DEFAULT_ALLOW_IN_EMULATED_HUE)
allow_hue_groups = config.get(CONF_ALLOW_HUE_GROUPS)
if discovery_info is not None:
@ -200,7 +200,8 @@ def setup_bridge(host, hass, add_devices, filename, allow_unreachable,
for light_id, info in api_lights.items():
if light_id not in lights:
lights[light_id] = HueLight(int(light_id), info,
lights[light_id] = HueLight(hass,
int(light_id), info,
bridge, update_lights,
bridge_type, allow_unreachable,
allow_in_emulated_hue)
@ -218,6 +219,7 @@ def setup_bridge(host, hass, add_devices, filename, allow_unreachable,
if lightgroup_id not in lightgroups:
lightgroups[lightgroup_id] = HueLight(
hass,
int(lightgroup_id), info, bridge, update_lights,
bridge_type, allow_unreachable, allow_in_emulated_hue,
True)
@ -280,10 +282,11 @@ def request_configuration(host, hass, add_devices, filename,
class HueLight(Light):
"""Representation of a Hue light."""
def __init__(self, light_id, info, bridge, update_lights,
def __init__(self, hass, light_id, info, bridge, update_lights,
bridge_type, allow_unreachable, allow_in_emulated_hue,
is_group=False):
"""Initialize the light."""
self.hass = hass
self.light_id = light_id
self.info = info
self.bridge = bridge

View File

@ -21,11 +21,21 @@ ATTR_NOTIFICATION = 'notification'
ATTR_LOCK_STATUS = 'lock_status'
ATTR_CODE_SLOT = 'code_slot'
ATTR_USERCODE = 'usercode'
CONFIG_ADVANCED = 'Advanced'
SERVICE_SET_USERCODE = 'set_usercode'
SERVICE_GET_USERCODE = 'get_usercode'
SERVICE_CLEAR_USERCODE = 'clear_usercode'
POLYCONTROL = 0x10E
DANALOCK_V2_BTZE = 0x2
POLYCONTROL_DANALOCK_V2_BTZE_LOCK = (POLYCONTROL, DANALOCK_V2_BTZE)
WORKAROUND_V2BTZE = 'v2btze'
DEVICE_MAPPINGS = {
POLYCONTROL_DANALOCK_V2_BTZE_LOCK: WORKAROUND_V2BTZE
}
LOCK_NOTIFICATION = {
1: 'Manual Lock',
2: 'Manual Unlock',
@ -110,7 +120,7 @@ CLEAR_USERCODE_SCHEMA = vol.Schema({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and return Z-Wave switches."""
"""Find and return Z-Wave locks."""
if discovery_info is None or zwave.NETWORK is None:
return
@ -197,29 +207,61 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice):
"""Representation of a Z-Wave switch."""
"""Representation of a Z-Wave Lock."""
def __init__(self, value):
"""Initialize the Z-Wave switch device."""
"""Initialize the Z-Wave lock device."""
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._node = value.node
self._state = None
self._notification = None
self._lock_status = None
self._v2btze = None
# Enable appropriate workaround flags for our device
# Make sure that we have values for the key before converting to int
if (value.node.manufacturer_id.strip() and
value.node.product_id.strip()):
specific_sensor_key = (int(value.node.manufacturer_id, 16),
int(value.node.product_id, 16))
if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_V2BTZE:
self._v2btze = 1
_LOGGER.debug("Polycontrol Danalock v2 BTZE "
"workaround enabled")
self.update_properties()
def update_properties(self):
"""Callback on data changes for node values."""
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_DOOR_LOCK).values():
if value.type != zwave.const.TYPE_BOOL:
continue
if value.genre != zwave.const.GENRE_USER:
continue
self._state = value.data
_LOGGER.debug('Lock state set from Bool value and'
' is %s', value.data)
break
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_ALARM).values():
if value.label != "Access Control":
continue
self._notification = LOCK_NOTIFICATION.get(value.data)
if self._notification:
self._state = LOCK_STATUS.get(value.data)
_LOGGER.debug('Lock state set from Access Control value and'
' is %s', value.data)
notification_data = value.data
if self._v2btze:
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION)
.values()):
if value.index != 12:
continue
if value.data == CONFIG_ADVANCED:
self._state = LOCK_STATUS.get(notification_data)
_LOGGER.debug('Lock state set from Access Control '
'value and is %s', notification_data)
break
break
for value in self._node.get_values(
@ -227,10 +269,6 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice):
if value.label != "Alarm Type":
continue
alarm_type = LOCK_ALARM_TYPE.get(value.data)
if alarm_type:
self._state = LOCK_STATUS.get(value.data)
_LOGGER.debug('Lock state set from Alarm Type value and'
' is %s', value.data)
break
for value in self._node.get_values(
@ -256,18 +294,6 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice):
self._lock_status = LOCK_ALARM_TYPE.get(alarm_type)
break
if not self._notification and not self._lock_status:
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_DOOR_LOCK).values():
if value.type != zwave.const.TYPE_BOOL:
continue
if value.genre != zwave.const.GENRE_USER:
continue
self._state = value.data
_LOGGER.debug('Lock state set from Bool value and'
' is %s', value.data)
break
@property
def is_locked(self):
"""Return true if device is locked."""

View File

@ -342,17 +342,19 @@ class SonosDevice(MediaPlayerDevice):
if is_available:
if self._player.group.coordinator != self._player:
# set group coordinator
if self._player.is_coordinator:
self._coordinator = None
else:
try:
self._coordinator = _get_entity_from_soco(
self.hass, self._player.group.coordinator)
except ValueError:
self._coordinator = None
else:
self._coordinator = None
if self._coordinator == self:
_LOGGER.warning("Coordinator loop on: %s", self.unique_id)
# protect for loop
if not self._coordinator.is_coordinator:
# pylint: disable=protected-access
self._coordinator._coordinator = None
except ValueError:
self._coordinator = None
track_info = None
@ -521,10 +523,6 @@ class SonosDevice(MediaPlayerDevice):
update_media_position |= rel_time is not None and \
self._media_position is None
# used only if a media is playing
if self.state != STATE_PLAYING:
update_media_position = None
# position changed?
if rel_time is not None and \
self._media_position is not None:
@ -539,7 +537,7 @@ class SonosDevice(MediaPlayerDevice):
update_media_position = \
abs(calculated_position - rel_time) > 1.5
if update_media_position:
if update_media_position and self.state == STATE_PLAYING:
media_position = rel_time
media_position_updated_at = utcnow()
else:
@ -828,7 +826,7 @@ class SonosDevice(MediaPlayerDevice):
"""List of available input sources."""
model_name = self._speaker_info['model_name']
sources = self._favorite_sources
sources = self._favorite_sources.copy()
if 'PLAY:5' in model_name:
sources += [SUPPORT_SOURCE_LINEIN]
@ -957,7 +955,7 @@ class SonosDevice(MediaPlayerDevice):
try:
# need catch exception if a coordinator is going to slave.
# this state will recover with group part.
self.soco_snapshot.restore(True)
self.soco_snapshot.restore(False)
except (TypeError, SoCoException):
_LOGGER.debug("Error on restore %s", self.entity_id)

View File

@ -15,18 +15,23 @@ from homeassistant.const import (
ATTR_SERVICE_DATA, EVENT_CALL_SERVICE, EVENT_SERVICE_EXECUTED,
EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL)
from homeassistant.core import EventOrigin, State
import homeassistant.helpers.config_validation as cv
from homeassistant.remote import JSONEncoder
from .mqtt import EVENT_MQTT_MESSAGE_RECEIVED
DOMAIN = "mqtt_eventstream"
DEPENDENCIES = ['mqtt']
CONF_PUBLISH_TOPIC = 'publish_topic'
CONF_SUBSCRIBE_TOPIC = 'subscribe_topic'
CONF_PUBLISH_EVENTSTREAM_RECEIVED = 'publish_eventstream_received'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_PUBLISH_TOPIC): valid_publish_topic,
vol.Optional(CONF_SUBSCRIBE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_PUBLISH_EVENTSTREAM_RECEIVED, default=False):
cv.boolean,
}),
}, extra=vol.ALLOW_EXTRA)
@ -45,6 +50,15 @@ def setup(hass, config):
if event.event_type == EVENT_TIME_CHANGED:
return
# MQTT fires a bus event for every incoming message, also messages from
# eventstream. Disable publishing these messages to other HA instances
# and possibly creating an infinite loop if these instances publish
# back to this one.
if all([not conf.get(CONF_PUBLISH_EVENTSTREAM_RECEIVED),
event.event_type == EVENT_MQTT_MESSAGE_RECEIVED,
event.data.get('topic') == sub_topic]):
return
# Filter out the events that were triggered by publishing
# to the MQTT topic, or you will end up in an infinite loop.
if event.event_type == EVENT_CALL_SERVICE:

View File

@ -20,7 +20,7 @@ import homeassistant.loader as loader
from requests.exceptions import HTTPError, ConnectTimeout
REQUIREMENTS = ['amcrest==1.1.3']
REQUIREMENTS = ['amcrest==1.1.4']
_LOGGER = logging.getLogger(__name__)

View File

@ -58,7 +58,7 @@ class TelldusLiveSensor(TelldusLiveEntity):
@property
def _value(self):
"""Return value of the sensor."""
return self.device.value(self._id[1:])
return self.device.value(*self._id[1:])
@property
def _value_as_temperature(self):

View File

@ -99,8 +99,12 @@ homematic:
fields:
entity_id:
description: Name(s) of entities to set value
example: 'homematic.my_variable'
description: Name(s) of homematic central to set value
example: 'homematic.ccu2'
name:
description: Name of the varaible to set
example: 'testvariable'
value:
description: New value

View File

@ -33,7 +33,7 @@ COMMAND_SCHEMA = vol.Schema({
vol.Optional('off'): cv.positive_int,
vol.Optional(CONF_UNIT): cv.positive_int,
vol.Optional(CONF_UNITCODE): cv.positive_int,
vol.Optional(CONF_ID): cv.positive_int,
vol.Optional(CONF_ID): vol.Any(cv.positive_int, cv.string),
vol.Optional(CONF_STATE): cv.string,
vol.Optional(CONF_SYSTEMCODE): cv.positive_int,
}, extra=vol.ALLOW_EXTRA)

View File

@ -17,7 +17,7 @@ import voluptuous as vol
DOMAIN = 'tellduslive'
REQUIREMENTS = ['tellduslive==0.1.13']
REQUIREMENTS = ['tellduslive==0.3.0']
_LOGGER = logging.getLogger(__name__)
@ -133,8 +133,8 @@ class TelldusLiveClient(object):
if device.device_id in known_ids:
continue
if device.is_sensor:
for item_id in device.items:
discover((device.device_id,) + item_id,
for item in device.items:
discover((device.device_id, item.name, item.scale),
'sensor')
else:
discover(device.device_id,
@ -145,8 +145,7 @@ class TelldusLiveClient(object):
def device(self, device_id):
"""Return device representation."""
import tellduslive
return tellduslive.Device(self._client, device_id)
return self._client.device(device_id)
def is_available(self, device_id):
"""Return device availability."""
@ -221,5 +220,5 @@ class TelldusLiveEntity(Entity):
@property
def _last_updated(self):
"""Return the last update of a device."""
return str(datetime.fromtimestamp(self.device.last_updated)) \
if self.device.last_updated else None
return str(datetime.fromtimestamp(self.device.lastUpdated)) \
if self.device.lastUpdated else None

View File

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

View File

@ -39,7 +39,7 @@ aiohttp_cors==0.5.0
# homeassistant.components.camera.amcrest
# homeassistant.components.sensor.amcrest
amcrest==1.1.3
amcrest==1.1.4
# homeassistant.components.media_player.anthemav
anthemav==1.1.8
@ -444,7 +444,7 @@ pyharmony==1.0.12
pyhik==0.0.7
# homeassistant.components.homematic
pyhomematic==0.1.20
pyhomematic==0.1.21
# homeassistant.components.device_tracker.icloud
pyicloud==0.9.1
@ -620,7 +620,7 @@ steamodd==4.21
tellcore-py==1.1.2
# homeassistant.components.tellduslive
tellduslive==0.1.13
tellduslive==0.3.0
# homeassistant.components.sensor.temper
temperusb==1.5.1

View File

@ -314,4 +314,4 @@ class TestSonosMediaPlayer(unittest.TestCase):
device._snapshot_coordinator.soco_device = SoCoMock('192.0.2.17')
device.restore()
self.assertEqual(restoreMock.call_count, 1)
self.assertEqual(restoreMock.call_args, mock.call(True))
self.assertEqual(restoreMock.call_args, mock.call(False))

View File

@ -1,10 +1,12 @@
"""The tests for the MQTT eventstream component."""
from collections import namedtuple
import json
import unittest
from unittest.mock import ANY, patch
from homeassistant.bootstrap import setup_component
import homeassistant.components.mqtt_eventstream as eventstream
import homeassistant.components.mqtt as mqtt
from homeassistant.const import EVENT_STATE_CHANGED
from homeassistant.core import State, callback
from homeassistant.remote import JSONEncoder
@ -146,3 +148,44 @@ class TestMqttEventStream(unittest.TestCase):
self.hass.block_till_done()
self.assertEqual(1, len(calls))
@patch('homeassistant.components.mqtt.publish')
def test_mqtt_received_event(self, mock_pub):
"""Don't filter events from the mqtt component about received message.
Mqtt component sends an event if a message is received. Also
messages that originate from an incoming eventstream.
Broadcasting these messages result in an infinite loop if two HA
instances are crossconfigured for the same mqtt topics.
"""
SUB_TOPIC = 'from_slaves'
self.assertTrue(
self.add_eventstream(
pub_topic='bar',
sub_topic=SUB_TOPIC))
self.hass.block_till_done()
# Reset the mock because it will have already gotten calls for the
# mqtt_eventstream state change on initialization, etc.
mock_pub.reset_mock()
# Use MQTT component message handler to simulate firing message
# received event.
MQTTMessage = namedtuple('MQTTMessage', ['topic', 'qos', 'payload'])
message = MQTTMessage(SUB_TOPIC, 1, 'Hello World!'.encode('utf-8'))
mqtt.MQTT._mqtt_on_message(self, None, {'hass': self.hass}, message)
self.hass.block_till_done()
# 'normal' incoming mqtt messages should be broadcasted
self.assertEqual(mock_pub.call_count, 0)
MQTTMessage = namedtuple('MQTTMessage', ['topic', 'qos', 'payload'])
message = MQTTMessage('test_topic', 1, 'Hello World!'.encode('utf-8'))
mqtt.MQTT._mqtt_on_message(self, None, {'hass': self.hass}, message)
self.hass.block_till_done()
# but event from the event stream not
self.assertEqual(mock_pub.call_count, 1)