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

View File

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

View File

@ -9,6 +9,7 @@ import logging
import requests import requests
import voluptuous as vol import voluptuous as vol
from homeassistant.const import CONF_VERIFY_SSL
from homeassistant.components.netatmo import CameraData from homeassistant.components.netatmo import CameraData
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.loader import get_component from homeassistant.loader import get_component
@ -22,6 +23,7 @@ CONF_HOME = 'home'
CONF_CAMERAS = 'cameras' CONF_CAMERAS = 'cameras'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
vol.Optional(CONF_HOME): cv.string, vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_CAMERAS, default=[]): vol.Optional(CONF_CAMERAS, default=[]):
vol.All(cv.ensure_list, [cv.string]), 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.""" """Setup access to Netatmo cameras."""
netatmo = get_component('netatmo') netatmo = get_component('netatmo')
home = config.get(CONF_HOME) home = config.get(CONF_HOME)
verify_ssl = config.get(CONF_VERIFY_SSL, True)
import lnetatmo import lnetatmo
try: try:
data = CameraData(netatmo.NETATMO_AUTH, home) 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 \ if config[CONF_CAMERAS] != [] and \
camera_name not in config[CONF_CAMERAS]: camera_name not in config[CONF_CAMERAS]:
continue continue
add_devices([NetatmoCamera(data, camera_name, home, camera_type)]) add_devices([NetatmoCamera(data, camera_name, home,
camera_type, verify_ssl)])
except lnetatmo.NoDevice: except lnetatmo.NoDevice:
return None return None
@ -50,11 +54,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class NetatmoCamera(Camera): class NetatmoCamera(Camera):
"""Representation of the images published from a Netatmo 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.""" """Setup for access to the Netatmo camera images."""
super(NetatmoCamera, self).__init__() super(NetatmoCamera, self).__init__()
self._data = data self._data = data
self._camera_name = camera_name self._camera_name = camera_name
self._verify_ssl = verify_ssl
if home: if home:
self._name = home + ' / ' + camera_name self._name = home + ' / ' + camera_name
else: else:
@ -74,11 +79,17 @@ class NetatmoCamera(Camera):
if self._localurl: if self._localurl:
response = requests.get('{0}/live/snapshot_720.jpg'.format( response = requests.get('{0}/live/snapshot_720.jpg'.format(
self._localurl), timeout=10) self._localurl), timeout=10)
else: elif self._vpnurl:
response = requests.get('{0}/live/snapshot_720.jpg'.format( 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: 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._data.update()
(self._vpnurl, self._localurl) = \ (self._vpnurl, self._localurl) = \
self._data.camera_data.cameraUrls(camera=self._camera_name) 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) raw = yield from self._async_ws_function(CMD_DEVICES)
try: 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')] return [mac.text for mac in xml_root.iter('MACAddr')]
except (ET.ParseError, TypeError): except (ET.ParseError, TypeError):
_LOGGER.warning("Can't read device from %s", self.host) _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 ( from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN, CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN, CONF_USERNAME, CONF_PASSWORD,
CONF_PLATFORM, CONF_HOSTS, CONF_NAME, ATTR_ENTITY_ID) 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 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.config import load_yaml_config_file
from homeassistant.util import Throttle
DOMAIN = 'homematic' DOMAIN = 'homematic'
REQUIREMENTS = ["pyhomematic==0.1.20"] REQUIREMENTS = ["pyhomematic==0.1.21"]
MIN_TIME_BETWEEN_UPDATE_HUB = timedelta(seconds=300) SCAN_INTERVAL_HUB = timedelta(seconds=300)
SCAN_INTERVAL = timedelta(seconds=30) SCAN_INTERVAL_VARIABLES = timedelta(seconds=30)
DISCOVER_SWITCHES = 'homematic.switch' DISCOVER_SWITCHES = 'homematic.switch'
DISCOVER_LIGHTS = 'homematic.light' DISCOVER_LIGHTS = 'homematic.light'
@ -176,8 +175,9 @@ SCHEMA_SERVICE_VIRTUALKEY = vol.Schema({
}) })
SCHEMA_SERVICE_SET_VAR_VALUE = 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.Required(ATTR_VALUE): cv.match_all,
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
}) })
SCHEMA_SERVICE_SET_DEV_VALUE = vol.Schema({ SCHEMA_SERVICE_SET_DEV_VALUE = vol.Schema({
@ -236,8 +236,6 @@ def setup(hass, config):
"""Setup the Homematic component.""" """Setup the Homematic component."""
from pyhomematic import HMConnection from pyhomematic import HMConnection
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
hass.data[DATA_DELAY] = config[DOMAIN].get(CONF_DELAY) hass.data[DATA_DELAY] = config[DOMAIN].get(CONF_DELAY)
hass.data[DATA_DEVINIT] = {} hass.data[DATA_DEVINIT] = {}
hass.data[DATA_STORE] = [] hass.data[DATA_STORE] = []
@ -281,11 +279,10 @@ def setup(hass, config):
hass.config.components.append(DOMAIN) hass.config.components.append(DOMAIN)
# init homematic hubs # init homematic hubs
hub_entities = [] entity_hubs = []
for _, hub_data in hosts.items(): for _, hub_data in hosts.items():
hub_entities.append(HMHub(hass, component, hub_data[CONF_NAME], entity_hubs.append(HMHub(
hub_data[CONF_VARIABLES])) hass, hub_data[CONF_NAME], hub_data[CONF_VARIABLES]))
component.add_entities(hub_entities)
# regeister homematic services # regeister homematic services
descriptions = load_yaml_config_file( descriptions = load_yaml_config_file(
@ -323,14 +320,23 @@ def setup(hass, config):
schema=SCHEMA_SERVICE_VIRTUALKEY) schema=SCHEMA_SERVICE_VIRTUALKEY)
def _service_handle_value(service): def _service_handle_value(service):
"""Set value on homematic variable object.""" """Set value on homematic variable."""
variable_list = component.extract_from_service(service) entity_ids = service.data.get(ATTR_ENTITY_ID)
name = service.data[ATTR_NAME]
value = service.data[ATTR_VALUE] value = service.data[ATTR_VALUE]
for hm_variable in variable_list: if entity_ids:
if isinstance(hm_variable, HMVariable): entities = [entity for entity in entity_hubs if
hm_variable.hm_set(value) 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( hass.services.register(
DOMAIN, SERVICE_SET_VAR_VALUE, _service_handle_value, DOMAIN, SERVICE_SET_VAR_VALUE, _service_handle_value,
@ -579,132 +585,87 @@ def _device_from_servicecall(hass, service):
class HMHub(Entity): class HMHub(Entity):
"""The Homematic hub. I.e. CCU2/HomeGear.""" """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.""" """Initialize Homematic hub."""
self.hass = hass self.hass = hass
self.entity_id = "{}.{}".format(DOMAIN, name.lower())
self._homematic = hass.data[DATA_HOMEMATIC] self._homematic = hass.data[DATA_HOMEMATIC]
self._component = component self._variables = {}
self._name = name self._name = name
self._state = STATE_UNKNOWN self._state = STATE_UNKNOWN
self._store = {}
self._use_variables = use_variables self._use_variables = use_variables
# load data # load data
self._update_hub_state() track_time_interval(hass, self._update_hub, SCAN_INTERVAL_HUB)
self._init_variables() self._update_hub(None)
if self._use_variables:
track_time_interval(
hass, self._update_variables, SCAN_INTERVAL_VARIABLES)
self._update_variables(None)
@property @property
def name(self): def name(self):
"""Return the name of the device.""" """Return the name of the device."""
return self._name 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 @property
def should_poll(self): def should_poll(self):
"""Return false. Homematic Hub object update variable.""" """Return false. Homematic Hub object update variable."""
return False return False
@property @property
def device_state_attributes(self): def state(self):
"""Return device specific state attributes.""" """Return the state of the entity."""
attr = { return self._state
'hub': self._hub_name,
} @property
def state_attributes(self):
"""Return the state attributes."""
attr = self._variables.copy()
return attr return attr
def hm_update(self, value): @property
"""Update variable over Hub object.""" def icon(self):
if value != self._state: """Return the icon to use in the frontend, if any."""
self._state = value 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 _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() self.schedule_update_ha_state()
def hm_set(self, value): def hm_set_variable(self, name, value):
"""Set variable on homematic controller.""" """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) value = cv.boolean(value)
else: else:
value = float(value) value = float(value)
self._homematic.setSystemVariable(self._hub_name, self._name, value) self._homematic.setSystemVariable(self.name, name, value)
self._state = value
self._variables.update({name: value})
self.schedule_update_ha_state() self.schedule_update_ha_state()
@ -817,7 +778,7 @@ class HMDevice(Entity):
have_change = True have_change = True
# If available it has changed # If available it has changed
if attribute is 'UNREACH': if attribute == 'UNREACH':
self._available = bool(value) self._available = bool(value)
have_change = True have_change = True
@ -829,7 +790,7 @@ class HMDevice(Entity):
def _subscribe_homematic_events(self): def _subscribe_homematic_events(self):
"""Subscribe all required events to handle job.""" """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 # Push data to channels_to_sub from hmdevice metadata
for metadata in (self._hmdevice.SENSORNODE, self._hmdevice.BINARYNODE, for metadata in (self._hmdevice.SENSORNODE, self._hmdevice.BINARYNODE,
@ -856,7 +817,7 @@ class HMDevice(Entity):
# Set callbacks # Set callbacks
for channel in channels_to_sub: for channel in channels_to_sub:
_LOGGER.debug( _LOGGER.debug(
"Subscribe channel %s from %s", str(channel), self._name) "Subscribe channel %d from %s", channel, self._name)
self._hmdevice.setEventCallback( self._hmdevice.setEventCallback(
callback=self._hm_event_callback, bequeath=False, callback=self._hm_event_callback, bequeath=False,
channel=channel) channel=channel)

View File

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

View File

@ -21,11 +21,21 @@ ATTR_NOTIFICATION = 'notification'
ATTR_LOCK_STATUS = 'lock_status' ATTR_LOCK_STATUS = 'lock_status'
ATTR_CODE_SLOT = 'code_slot' ATTR_CODE_SLOT = 'code_slot'
ATTR_USERCODE = 'usercode' ATTR_USERCODE = 'usercode'
CONFIG_ADVANCED = 'Advanced'
SERVICE_SET_USERCODE = 'set_usercode' SERVICE_SET_USERCODE = 'set_usercode'
SERVICE_GET_USERCODE = 'get_usercode' SERVICE_GET_USERCODE = 'get_usercode'
SERVICE_CLEAR_USERCODE = 'clear_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 = { LOCK_NOTIFICATION = {
1: 'Manual Lock', 1: 'Manual Lock',
2: 'Manual Unlock', 2: 'Manual Unlock',
@ -110,7 +120,7 @@ CLEAR_USERCODE_SCHEMA = vol.Schema({
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): 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: if discovery_info is None or zwave.NETWORK is None:
return return
@ -197,29 +207,61 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice): class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice):
"""Representation of a Z-Wave switch.""" """Representation of a Z-Wave Lock."""
def __init__(self, value): def __init__(self, value):
"""Initialize the Z-Wave switch device.""" """Initialize the Z-Wave lock device."""
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._node = value.node self._node = value.node
self._state = None self._state = None
self._notification = None self._notification = None
self._lock_status = 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() self.update_properties()
def update_properties(self): def update_properties(self):
"""Callback on data changes for node values.""" """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( for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_ALARM).values(): class_id=zwave.const.COMMAND_CLASS_ALARM).values():
if value.label != "Access Control": if value.label != "Access Control":
continue continue
self._notification = LOCK_NOTIFICATION.get(value.data) self._notification = LOCK_NOTIFICATION.get(value.data)
if self._notification: notification_data = value.data
self._state = LOCK_STATUS.get(value.data) if self._v2btze:
_LOGGER.debug('Lock state set from Access Control value and' for value in (self._node.get_values(
' is %s', value.data) 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 break
for value in self._node.get_values( for value in self._node.get_values(
@ -227,10 +269,6 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice):
if value.label != "Alarm Type": if value.label != "Alarm Type":
continue continue
alarm_type = LOCK_ALARM_TYPE.get(value.data) 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 break
for value in self._node.get_values( 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) self._lock_status = LOCK_ALARM_TYPE.get(alarm_type)
break 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 @property
def is_locked(self): def is_locked(self):
"""Return true if device is locked.""" """Return true if device is locked."""

View File

@ -342,18 +342,20 @@ class SonosDevice(MediaPlayerDevice):
if is_available: if is_available:
if self._player.group.coordinator != self._player: # set group coordinator
if self._player.is_coordinator:
self._coordinator = None
else:
try: try:
self._coordinator = _get_entity_from_soco( self._coordinator = _get_entity_from_soco(
self.hass, self._player.group.coordinator) self.hass, self._player.group.coordinator)
# protect for loop
if not self._coordinator.is_coordinator:
# pylint: disable=protected-access
self._coordinator._coordinator = None
except ValueError: except ValueError:
self._coordinator = None self._coordinator = None
else:
self._coordinator = None
if self._coordinator == self:
_LOGGER.warning("Coordinator loop on: %s", self.unique_id)
self._coordinator = None
track_info = None track_info = None
if self._last_avtransport_event: if self._last_avtransport_event:
@ -521,10 +523,6 @@ class SonosDevice(MediaPlayerDevice):
update_media_position |= rel_time is not None and \ update_media_position |= rel_time is not None and \
self._media_position is None self._media_position is None
# used only if a media is playing
if self.state != STATE_PLAYING:
update_media_position = None
# position changed? # position changed?
if rel_time is not None and \ if rel_time is not None and \
self._media_position is not None: self._media_position is not None:
@ -539,7 +537,7 @@ class SonosDevice(MediaPlayerDevice):
update_media_position = \ update_media_position = \
abs(calculated_position - rel_time) > 1.5 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 = rel_time
media_position_updated_at = utcnow() media_position_updated_at = utcnow()
else: else:
@ -828,7 +826,7 @@ class SonosDevice(MediaPlayerDevice):
"""List of available input sources.""" """List of available input sources."""
model_name = self._speaker_info['model_name'] model_name = self._speaker_info['model_name']
sources = self._favorite_sources sources = self._favorite_sources.copy()
if 'PLAY:5' in model_name: if 'PLAY:5' in model_name:
sources += [SUPPORT_SOURCE_LINEIN] sources += [SUPPORT_SOURCE_LINEIN]
@ -957,7 +955,7 @@ class SonosDevice(MediaPlayerDevice):
try: try:
# need catch exception if a coordinator is going to slave. # need catch exception if a coordinator is going to slave.
# this state will recover with group part. # this state will recover with group part.
self.soco_snapshot.restore(True) self.soco_snapshot.restore(False)
except (TypeError, SoCoException): except (TypeError, SoCoException):
_LOGGER.debug("Error on restore %s", self.entity_id) _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, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE, EVENT_SERVICE_EXECUTED,
EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL)
from homeassistant.core import EventOrigin, State from homeassistant.core import EventOrigin, State
import homeassistant.helpers.config_validation as cv
from homeassistant.remote import JSONEncoder from homeassistant.remote import JSONEncoder
from .mqtt import EVENT_MQTT_MESSAGE_RECEIVED
DOMAIN = "mqtt_eventstream" DOMAIN = "mqtt_eventstream"
DEPENDENCIES = ['mqtt'] DEPENDENCIES = ['mqtt']
CONF_PUBLISH_TOPIC = 'publish_topic' CONF_PUBLISH_TOPIC = 'publish_topic'
CONF_SUBSCRIBE_TOPIC = 'subscribe_topic' CONF_SUBSCRIBE_TOPIC = 'subscribe_topic'
CONF_PUBLISH_EVENTSTREAM_RECEIVED = 'publish_eventstream_received'
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({ DOMAIN: vol.Schema({
vol.Optional(CONF_PUBLISH_TOPIC): valid_publish_topic, vol.Optional(CONF_PUBLISH_TOPIC): valid_publish_topic,
vol.Optional(CONF_SUBSCRIBE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_SUBSCRIBE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_PUBLISH_EVENTSTREAM_RECEIVED, default=False):
cv.boolean,
}), }),
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@ -45,6 +50,15 @@ def setup(hass, config):
if event.event_type == EVENT_TIME_CHANGED: if event.event_type == EVENT_TIME_CHANGED:
return 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 # Filter out the events that were triggered by publishing
# to the MQTT topic, or you will end up in an infinite loop. # to the MQTT topic, or you will end up in an infinite loop.
if event.event_type == EVENT_CALL_SERVICE: if event.event_type == EVENT_CALL_SERVICE:

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ COMMAND_SCHEMA = vol.Schema({
vol.Optional('off'): cv.positive_int, vol.Optional('off'): cv.positive_int,
vol.Optional(CONF_UNIT): cv.positive_int, vol.Optional(CONF_UNIT): cv.positive_int,
vol.Optional(CONF_UNITCODE): 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_STATE): cv.string,
vol.Optional(CONF_SYSTEMCODE): cv.positive_int, vol.Optional(CONF_SYSTEMCODE): cv.positive_int,
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)

View File

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

View File

@ -2,7 +2,7 @@
"""Constants used by Home Assistant components.""" """Constants used by Home Assistant components."""
MAJOR_VERSION = 0 MAJOR_VERSION = 0
MINOR_VERSION = 37 MINOR_VERSION = 37
PATCH_VERSION = '0' PATCH_VERSION = '1'
__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, 4, 2) REQUIRED_PYTHON_VER = (3, 4, 2)

View File

@ -39,7 +39,7 @@ aiohttp_cors==0.5.0
# homeassistant.components.camera.amcrest # homeassistant.components.camera.amcrest
# homeassistant.components.sensor.amcrest # homeassistant.components.sensor.amcrest
amcrest==1.1.3 amcrest==1.1.4
# homeassistant.components.media_player.anthemav # homeassistant.components.media_player.anthemav
anthemav==1.1.8 anthemav==1.1.8
@ -444,7 +444,7 @@ pyharmony==1.0.12
pyhik==0.0.7 pyhik==0.0.7
# homeassistant.components.homematic # homeassistant.components.homematic
pyhomematic==0.1.20 pyhomematic==0.1.21
# homeassistant.components.device_tracker.icloud # homeassistant.components.device_tracker.icloud
pyicloud==0.9.1 pyicloud==0.9.1
@ -620,7 +620,7 @@ steamodd==4.21
tellcore-py==1.1.2 tellcore-py==1.1.2
# homeassistant.components.tellduslive # homeassistant.components.tellduslive
tellduslive==0.1.13 tellduslive==0.3.0
# homeassistant.components.sensor.temper # homeassistant.components.sensor.temper
temperusb==1.5.1 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._snapshot_coordinator.soco_device = SoCoMock('192.0.2.17')
device.restore() device.restore()
self.assertEqual(restoreMock.call_count, 1) 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.""" """The tests for the MQTT eventstream component."""
from collections import namedtuple
import json import json
import unittest import unittest
from unittest.mock import ANY, patch from unittest.mock import ANY, patch
from homeassistant.bootstrap import setup_component from homeassistant.bootstrap import setup_component
import homeassistant.components.mqtt_eventstream as eventstream import homeassistant.components.mqtt_eventstream as eventstream
import homeassistant.components.mqtt as mqtt
from homeassistant.const import EVENT_STATE_CHANGED from homeassistant.const import EVENT_STATE_CHANGED
from homeassistant.core import State, callback from homeassistant.core import State, callback
from homeassistant.remote import JSONEncoder from homeassistant.remote import JSONEncoder
@ -146,3 +148,44 @@ class TestMqttEventStream(unittest.TestCase):
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(1, len(calls)) 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)