mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Merge remote-tracking branch 'refs/remotes/home-assistant/dev' into Homematic_fix
This commit is contained in:
commit
d0b1619946
@ -3,7 +3,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
@ -12,21 +11,15 @@ import voluptuous as vol
|
|||||||
|
|
||||||
import homeassistant.components as core_components
|
import homeassistant.components as core_components
|
||||||
from homeassistant.components import group, persistent_notification
|
from homeassistant.components import group, persistent_notification
|
||||||
import homeassistant.config as config_util
|
import homeassistant.config as conf_util
|
||||||
import homeassistant.core as core
|
import homeassistant.core as core
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
import homeassistant.loader as loader
|
import homeassistant.loader as loader
|
||||||
import homeassistant.util.dt as date_util
|
|
||||||
import homeassistant.util.location as loc_util
|
|
||||||
import homeassistant.util.package as pkg_util
|
import homeassistant.util.package as pkg_util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
|
||||||
CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME,
|
|
||||||
CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, EVENT_COMPONENT_LOADED,
|
|
||||||
TEMP_CELSIUS, TEMP_FAHRENHEIT, PLATFORM_FORMAT, __version__)
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
event_decorators, service, config_per_platform, extract_domain_configs,
|
event_decorators, service, config_per_platform, extract_domain_configs)
|
||||||
entity)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
_SETUP_LOCK = RLock()
|
_SETUP_LOCK = RLock()
|
||||||
@ -208,11 +201,6 @@ def prepare_setup_platform(hass, config, domain, platform_name):
|
|||||||
return platform
|
return platform
|
||||||
|
|
||||||
|
|
||||||
def mount_local_lib_path(config_dir):
|
|
||||||
"""Add local library to Python Path."""
|
|
||||||
sys.path.insert(0, os.path.join(config_dir, 'deps'))
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches, too-many-statements, too-many-arguments
|
# pylint: disable=too-many-branches, too-many-statements, too-many-arguments
|
||||||
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
|
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
|
||||||
verbose=False, skip_pip=False,
|
verbose=False, skip_pip=False,
|
||||||
@ -226,18 +214,17 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
|
|||||||
if config_dir is not None:
|
if config_dir is not None:
|
||||||
config_dir = os.path.abspath(config_dir)
|
config_dir = os.path.abspath(config_dir)
|
||||||
hass.config.config_dir = config_dir
|
hass.config.config_dir = config_dir
|
||||||
mount_local_lib_path(config_dir)
|
_mount_local_lib_path(config_dir)
|
||||||
|
|
||||||
core_config = config.get(core.DOMAIN, {})
|
core_config = config.get(core.DOMAIN, {})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
process_ha_core_config(hass, config_util.CORE_CONFIG_SCHEMA(
|
conf_util.process_ha_core_config(hass, core_config)
|
||||||
core_config))
|
except vol.Invalid as ex:
|
||||||
except vol.MultipleInvalid as ex:
|
|
||||||
cv.log_exception(_LOGGER, ex, 'homeassistant', core_config)
|
cv.log_exception(_LOGGER, ex, 'homeassistant', core_config)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
process_ha_config_upgrade(hass)
|
conf_util.process_ha_config_upgrade(hass)
|
||||||
|
|
||||||
if enable_log:
|
if enable_log:
|
||||||
enable_logging(hass, verbose, log_rotate_days)
|
enable_logging(hass, verbose, log_rotate_days)
|
||||||
@ -292,12 +279,12 @@ def from_config_file(config_path, hass=None, verbose=False, skip_pip=True,
|
|||||||
# Set config dir to directory holding config file
|
# Set config dir to directory holding config file
|
||||||
config_dir = os.path.abspath(os.path.dirname(config_path))
|
config_dir = os.path.abspath(os.path.dirname(config_path))
|
||||||
hass.config.config_dir = config_dir
|
hass.config.config_dir = config_dir
|
||||||
mount_local_lib_path(config_dir)
|
_mount_local_lib_path(config_dir)
|
||||||
|
|
||||||
enable_logging(hass, verbose, log_rotate_days)
|
enable_logging(hass, verbose, log_rotate_days)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config_dict = config_util.load_yaml_config_file(config_path)
|
config_dict = conf_util.load_yaml_config_file(config_path)
|
||||||
except HomeAssistantError:
|
except HomeAssistantError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -356,100 +343,12 @@ def enable_logging(hass, verbose=False, log_rotate_days=None):
|
|||||||
'Unable to setup error log %s (access denied)', err_log_path)
|
'Unable to setup error log %s (access denied)', err_log_path)
|
||||||
|
|
||||||
|
|
||||||
def process_ha_config_upgrade(hass):
|
|
||||||
"""Upgrade config if necessary."""
|
|
||||||
version_path = hass.config.path('.HA_VERSION')
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(version_path, 'rt') as inp:
|
|
||||||
conf_version = inp.readline().strip()
|
|
||||||
except FileNotFoundError:
|
|
||||||
# Last version to not have this file
|
|
||||||
conf_version = '0.7.7'
|
|
||||||
|
|
||||||
if conf_version == __version__:
|
|
||||||
return
|
|
||||||
|
|
||||||
_LOGGER.info('Upgrading config directory from %s to %s', conf_version,
|
|
||||||
__version__)
|
|
||||||
|
|
||||||
# This was where dependencies were installed before v0.18
|
|
||||||
# Probably should keep this around until ~v0.20.
|
|
||||||
lib_path = hass.config.path('lib')
|
|
||||||
if os.path.isdir(lib_path):
|
|
||||||
shutil.rmtree(lib_path)
|
|
||||||
|
|
||||||
lib_path = hass.config.path('deps')
|
|
||||||
if os.path.isdir(lib_path):
|
|
||||||
shutil.rmtree(lib_path)
|
|
||||||
|
|
||||||
with open(version_path, 'wt') as outp:
|
|
||||||
outp.write(__version__)
|
|
||||||
|
|
||||||
|
|
||||||
def process_ha_core_config(hass, config):
|
|
||||||
"""Process the [homeassistant] section from the config."""
|
|
||||||
hac = hass.config
|
|
||||||
|
|
||||||
def set_time_zone(time_zone_str):
|
|
||||||
"""Helper method to set time zone."""
|
|
||||||
if time_zone_str is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
time_zone = date_util.get_time_zone(time_zone_str)
|
|
||||||
|
|
||||||
if time_zone:
|
|
||||||
hac.time_zone = time_zone
|
|
||||||
date_util.set_default_time_zone(time_zone)
|
|
||||||
else:
|
|
||||||
_LOGGER.error('Received invalid time zone %s', time_zone_str)
|
|
||||||
|
|
||||||
for key, attr in ((CONF_LATITUDE, 'latitude'),
|
|
||||||
(CONF_LONGITUDE, 'longitude'),
|
|
||||||
(CONF_NAME, 'location_name')):
|
|
||||||
if key in config:
|
|
||||||
setattr(hac, attr, config[key])
|
|
||||||
|
|
||||||
if CONF_TIME_ZONE in config:
|
|
||||||
set_time_zone(config.get(CONF_TIME_ZONE))
|
|
||||||
|
|
||||||
entity.set_customize(config.get(CONF_CUSTOMIZE))
|
|
||||||
|
|
||||||
if CONF_TEMPERATURE_UNIT in config:
|
|
||||||
hac.temperature_unit = config[CONF_TEMPERATURE_UNIT]
|
|
||||||
|
|
||||||
# If we miss some of the needed values, auto detect them
|
|
||||||
if None not in (
|
|
||||||
hac.latitude, hac.longitude, hac.temperature_unit, hac.time_zone):
|
|
||||||
return
|
|
||||||
|
|
||||||
_LOGGER.warning('Incomplete core config. Auto detecting location and '
|
|
||||||
'temperature unit')
|
|
||||||
|
|
||||||
info = loc_util.detect_location_info()
|
|
||||||
|
|
||||||
if info is None:
|
|
||||||
_LOGGER.error('Could not detect location information')
|
|
||||||
return
|
|
||||||
|
|
||||||
if hac.latitude is None and hac.longitude is None:
|
|
||||||
hac.latitude = info.latitude
|
|
||||||
hac.longitude = info.longitude
|
|
||||||
|
|
||||||
if hac.temperature_unit is None:
|
|
||||||
if info.use_fahrenheit:
|
|
||||||
hac.temperature_unit = TEMP_FAHRENHEIT
|
|
||||||
else:
|
|
||||||
hac.temperature_unit = TEMP_CELSIUS
|
|
||||||
|
|
||||||
if hac.location_name is None:
|
|
||||||
hac.location_name = info.city
|
|
||||||
|
|
||||||
if hac.time_zone is None:
|
|
||||||
set_time_zone(info.time_zone)
|
|
||||||
|
|
||||||
|
|
||||||
def _ensure_loader_prepared(hass):
|
def _ensure_loader_prepared(hass):
|
||||||
"""Ensure Home Assistant loader is prepared."""
|
"""Ensure Home Assistant loader is prepared."""
|
||||||
if not loader.PREPARED:
|
if not loader.PREPARED:
|
||||||
loader.prepare(hass)
|
loader.prepare(hass)
|
||||||
|
|
||||||
|
|
||||||
|
def _mount_local_lib_path(config_dir):
|
||||||
|
"""Add local library to Python Path."""
|
||||||
|
sys.path.insert(0, os.path.join(config_dir, 'deps'))
|
||||||
|
@ -121,16 +121,16 @@ def setup(hass, config):
|
|||||||
def handle_reload_config(call):
|
def handle_reload_config(call):
|
||||||
"""Service handler for reloading core config."""
|
"""Service handler for reloading core config."""
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant import config, bootstrap
|
from homeassistant import config as conf_util
|
||||||
|
|
||||||
try:
|
try:
|
||||||
path = config.find_config_file(hass.config.config_dir)
|
path = conf_util.find_config_file(hass.config.config_dir)
|
||||||
conf = config.load_yaml_config_file(path)
|
conf = conf_util.load_yaml_config_file(path)
|
||||||
except HomeAssistantError as err:
|
except HomeAssistantError as err:
|
||||||
_LOGGER.error(err)
|
_LOGGER.error(err)
|
||||||
return
|
return
|
||||||
|
|
||||||
bootstrap.process_ha_core_config(hass, conf.get(ha.DOMAIN) or {})
|
conf_util.process_ha_core_config(hass, conf.get(ha.DOMAIN) or {})
|
||||||
|
|
||||||
hass.services.register(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG,
|
hass.services.register(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG,
|
||||||
handle_reload_config)
|
handle_reload_config)
|
||||||
|
@ -6,7 +6,7 @@ https://home-assistant.io/developers/api/
|
|||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from time import time
|
import queue
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.remote as rem
|
import homeassistant.remote as rem
|
||||||
@ -72,19 +72,14 @@ class APIEventStream(HomeAssistantView):
|
|||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""Provide a streaming interface for the event bus."""
|
"""Provide a streaming interface for the event bus."""
|
||||||
from eventlet.queue import LightQueue, Empty
|
|
||||||
import eventlet
|
|
||||||
|
|
||||||
cur_hub = eventlet.hubs.get_hub()
|
|
||||||
request.environ['eventlet.minimum_write_chunk_size'] = 0
|
|
||||||
to_write = LightQueue()
|
|
||||||
stop_obj = object()
|
stop_obj = object()
|
||||||
|
to_write = queue.Queue()
|
||||||
|
|
||||||
restrict = request.args.get('restrict')
|
restrict = request.args.get('restrict')
|
||||||
if restrict:
|
if restrict:
|
||||||
restrict = restrict.split(',')
|
restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP]
|
||||||
|
|
||||||
def thread_forward_events(event):
|
def forward_events(event):
|
||||||
"""Forward events to the open request."""
|
"""Forward events to the open request."""
|
||||||
if event.event_type == EVENT_TIME_CHANGED:
|
if event.event_type == EVENT_TIME_CHANGED:
|
||||||
return
|
return
|
||||||
@ -99,28 +94,20 @@ class APIEventStream(HomeAssistantView):
|
|||||||
else:
|
else:
|
||||||
data = json.dumps(event, cls=rem.JSONEncoder)
|
data = json.dumps(event, cls=rem.JSONEncoder)
|
||||||
|
|
||||||
cur_hub.schedule_call_global(0, lambda: to_write.put(data))
|
to_write.put(data)
|
||||||
|
|
||||||
def stream():
|
def stream():
|
||||||
"""Stream events to response."""
|
"""Stream events to response."""
|
||||||
self.hass.bus.listen(MATCH_ALL, thread_forward_events)
|
self.hass.bus.listen(MATCH_ALL, forward_events)
|
||||||
|
|
||||||
_LOGGER.debug('STREAM %s ATTACHED', id(stop_obj))
|
_LOGGER.debug('STREAM %s ATTACHED', id(stop_obj))
|
||||||
|
|
||||||
last_msg = time()
|
|
||||||
# Fire off one message right away to have browsers fire open event
|
# Fire off one message right away to have browsers fire open event
|
||||||
to_write.put(STREAM_PING_PAYLOAD)
|
to_write.put(STREAM_PING_PAYLOAD)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
# Somehow our queue.get sometimes takes too long to
|
payload = to_write.get(timeout=STREAM_PING_INTERVAL)
|
||||||
# be notified of arrival of data. Probably
|
|
||||||
# because of our spawning on hub in other thread
|
|
||||||
# hack. Because current goal is to get this out,
|
|
||||||
# We just timeout every second because it will
|
|
||||||
# return right away if qsize() > 0.
|
|
||||||
# So yes, we're basically polling :(
|
|
||||||
payload = to_write.get(timeout=1)
|
|
||||||
|
|
||||||
if payload is stop_obj:
|
if payload is stop_obj:
|
||||||
break
|
break
|
||||||
@ -129,15 +116,13 @@ class APIEventStream(HomeAssistantView):
|
|||||||
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
|
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
|
||||||
msg.strip())
|
msg.strip())
|
||||||
yield msg.encode("UTF-8")
|
yield msg.encode("UTF-8")
|
||||||
last_msg = time()
|
except queue.Empty:
|
||||||
except Empty:
|
to_write.put(STREAM_PING_PAYLOAD)
|
||||||
if time() - last_msg > 50:
|
|
||||||
to_write.put(STREAM_PING_PAYLOAD)
|
|
||||||
except GeneratorExit:
|
except GeneratorExit:
|
||||||
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
|
|
||||||
break
|
break
|
||||||
|
|
||||||
self.hass.bus.remove_listener(MATCH_ALL, thread_forward_events)
|
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
|
||||||
|
self.hass.bus.remove_listener(MATCH_ALL, forward_events)
|
||||||
|
|
||||||
return self.Response(stream(), mimetype='text/event-stream')
|
return self.Response(stream(), mimetype='text/event-stream')
|
||||||
|
|
||||||
|
@ -1,33 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
The homematic binary sensor platform.
|
Support for Homematic binary sensors.
|
||||||
|
|
||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/binary_sensor.homematic/
|
https://home-assistant.io/components/binary_sensor.homematic/
|
||||||
|
|
||||||
Important: For this platform to work the homematic component has to be
|
|
||||||
properly configured.
|
|
||||||
|
|
||||||
Configuration (single channel, simple device):
|
|
||||||
|
|
||||||
binary_sensor:
|
|
||||||
- platform: homematic
|
|
||||||
address: "<Homematic address for device>" # e.g. "JEQ0XXXXXXX"
|
|
||||||
name: "<User defined name>" (optional)
|
|
||||||
|
|
||||||
|
|
||||||
Configuration (multiple channels, like motion detector with buttons):
|
|
||||||
|
|
||||||
binary_sensor:
|
|
||||||
- platform: homematic
|
|
||||||
address: "<Homematic address for device>" # e.g. "JEQ0XXXXXXX"
|
|
||||||
param: <MOTION|PRESS_SHORT...> (device-dependent) (optional)
|
|
||||||
button: n (integer of channel to map, device-dependent) (optional)
|
|
||||||
name: "<User defined name>" (optional)
|
|
||||||
binary_sensor:
|
|
||||||
- platform: homematic
|
|
||||||
...
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from homeassistant.const import STATE_UNKNOWN
|
from homeassistant.const import STATE_UNKNOWN
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
@ -47,44 +23,25 @@ SENSOR_TYPES_CLASS = {
|
|||||||
"RemoteMotion": None
|
"RemoteMotion": None
|
||||||
}
|
}
|
||||||
|
|
||||||
SUPPORT_HM_EVENT_AS_BINMOD = [
|
|
||||||
"PRESS_LONG",
|
|
||||||
"PRESS_SHORT"
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||||
"""Setup the platform."""
|
"""Setup the Homematic binary sensor platform."""
|
||||||
if discovery_info:
|
if discovery_info is None:
|
||||||
return homematic.setup_hmdevice_discovery_helper(HMBinarySensor,
|
return
|
||||||
discovery_info,
|
|
||||||
add_callback_devices)
|
return homematic.setup_hmdevice_discovery_helper(HMBinarySensor,
|
||||||
# Manual
|
discovery_info,
|
||||||
return homematic.setup_hmdevice_entity_helper(HMBinarySensor,
|
add_callback_devices)
|
||||||
config,
|
|
||||||
add_callback_devices)
|
|
||||||
|
|
||||||
|
|
||||||
class HMBinarySensor(homematic.HMDevice, BinarySensorDevice):
|
class HMBinarySensor(homematic.HMDevice, BinarySensorDevice):
|
||||||
"""Represents diverse binary Homematic units in Home Assistant."""
|
"""Representation of a binary Homematic device."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return True if switch is on."""
|
"""Return true if switch is on."""
|
||||||
if not self.available:
|
if not self.available:
|
||||||
return False
|
return False
|
||||||
# no binary is defined, check all!
|
|
||||||
if self._state is None:
|
|
||||||
available_bin = self._create_binary_list_from_hm()
|
|
||||||
for binary in available_bin:
|
|
||||||
try:
|
|
||||||
if binary in self._data and self._data[binary] == 1:
|
|
||||||
return True
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
_LOGGER.warning("%s datatype error!", self._name)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# single binary
|
|
||||||
return bool(self._hm_get_state())
|
return bool(self._hm_get_state())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -107,63 +64,37 @@ class HMBinarySensor(homematic.HMDevice, BinarySensorDevice):
|
|||||||
if not super()._check_hm_to_ha_object():
|
if not super()._check_hm_to_ha_object():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# check if the homematic device correct for this HA device
|
# check if the Homematic device correct for this HA device
|
||||||
if not isinstance(self._hmdevice, pyHMBinarySensor):
|
if not isinstance(self._hmdevice, pyHMBinarySensor):
|
||||||
_LOGGER.critical("This %s can't be use as binary!", self._name)
|
_LOGGER.critical("This %s can't be use as binary", self._name)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# load possible binary sensor
|
|
||||||
available_bin = self._create_binary_list_from_hm()
|
|
||||||
|
|
||||||
# if exists user value?
|
# if exists user value?
|
||||||
if self._state and self._state not in available_bin:
|
if self._state and self._state not in self._hmdevice.BINARYNODE:
|
||||||
_LOGGER.critical("This %s have no binary with %s!", self._name,
|
_LOGGER.critical("This %s have no binary with %s", self._name,
|
||||||
self._state)
|
self._state)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# only check and give a warining to User
|
# only check and give a warning to the user
|
||||||
if self._state is None and len(available_bin) > 1:
|
if self._state is None and len(self._hmdevice.BINARYNODE) > 1:
|
||||||
_LOGGER.warning("%s have multible binary params. It use all " +
|
_LOGGER.critical("%s have multiple binary params. It use all "
|
||||||
"binary nodes as one. Possible param values: %s",
|
"binary nodes as one. Possible param values: %s",
|
||||||
self._name, str(available_bin))
|
self._name, str(self._hmdevice.BINARYNODE))
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _init_data_struct(self):
|
def _init_data_struct(self):
|
||||||
"""Generate a data struct (self._data) from hm metadata."""
|
"""Generate a data struct (self._data) from the Homematic metadata."""
|
||||||
super()._init_data_struct()
|
super()._init_data_struct()
|
||||||
|
|
||||||
# load possible binary sensor
|
|
||||||
available_bin = self._create_binary_list_from_hm()
|
|
||||||
|
|
||||||
# object have 1 binary
|
# object have 1 binary
|
||||||
if self._state is None and len(available_bin) == 1:
|
if self._state is None and len(self._hmdevice.BINARYNODE) == 1:
|
||||||
for value in available_bin:
|
for value in self._hmdevice.BINARYNODE:
|
||||||
self._state = value
|
self._state = value
|
||||||
|
|
||||||
# no binary is definit, use all binary for state
|
|
||||||
if self._state is None and len(available_bin) > 1:
|
|
||||||
for node in available_bin:
|
|
||||||
self._data.update({node: STATE_UNKNOWN})
|
|
||||||
|
|
||||||
# add state to data struct
|
# add state to data struct
|
||||||
if self._state:
|
if self._state:
|
||||||
_LOGGER.debug("%s init datastruct with main node '%s'", self._name,
|
_LOGGER.debug("%s init datastruct with main node '%s'", self._name,
|
||||||
self._state)
|
self._state)
|
||||||
self._data.update({self._state: STATE_UNKNOWN})
|
self._data.update({self._state: STATE_UNKNOWN})
|
||||||
|
|
||||||
def _create_binary_list_from_hm(self):
|
|
||||||
"""Generate a own metadata for binary_sensors."""
|
|
||||||
bin_data = {}
|
|
||||||
if not self._hmdevice:
|
|
||||||
return bin_data
|
|
||||||
|
|
||||||
# copy all data from BINARYNODE
|
|
||||||
bin_data.update(self._hmdevice.BINARYNODE)
|
|
||||||
|
|
||||||
# copy all hm event they are supportet by this object
|
|
||||||
for event, channel in self._hmdevice.EVENTNODE.items():
|
|
||||||
if event in SUPPORT_HM_EVENT_AS_BINMOD:
|
|
||||||
bin_data.update({event: channel})
|
|
||||||
|
|
||||||
return bin_data
|
|
||||||
|
@ -7,10 +7,12 @@ at https://home-assistant.io/components/sensor.wink/
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL
|
from homeassistant.components.sensor.wink import WinkDevice
|
||||||
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.loader import get_component
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.7.7']
|
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
|
||||||
|
|
||||||
# These are the available sensors mapped to binary_sensor class
|
# These are the available sensors mapped to binary_sensor class
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
@ -41,14 +43,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
add_devices([WinkBinarySensorDevice(sensor)])
|
add_devices([WinkBinarySensorDevice(sensor)])
|
||||||
|
|
||||||
|
|
||||||
class WinkBinarySensorDevice(BinarySensorDevice, Entity):
|
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
|
||||||
"""Representation of a Wink sensor."""
|
"""Representation of a Wink sensor."""
|
||||||
|
|
||||||
def __init__(self, wink):
|
def __init__(self, wink):
|
||||||
"""Initialize the Wink binary sensor."""
|
"""Initialize the Wink binary sensor."""
|
||||||
self.wink = wink
|
super().__init__(wink)
|
||||||
|
wink = get_component('wink')
|
||||||
self._unit_of_measurement = self.wink.UNIT
|
self._unit_of_measurement = self.wink.UNIT
|
||||||
self._battery = self.wink.battery_level
|
|
||||||
self.capability = self.wink.capability()
|
self.capability = self.wink.capability()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -67,35 +69,3 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity):
|
|||||||
def sensor_class(self):
|
def sensor_class(self):
|
||||||
"""Return the class of this sensor, from SENSOR_CLASSES."""
|
"""Return the class of this sensor, from SENSOR_CLASSES."""
|
||||||
return SENSOR_TYPES.get(self.capability)
|
return SENSOR_TYPES.get(self.capability)
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the ID of this wink sensor."""
|
|
||||||
return "{}.{}".format(self.__class__, self.wink.device_id())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the sensor if any."""
|
|
||||||
return self.wink.name()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
"""True if connection == True."""
|
|
||||||
return self.wink.available
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Update state of the sensor."""
|
|
||||||
self.wink.update_state()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
"""Return the state attributes."""
|
|
||||||
if self._battery:
|
|
||||||
return {
|
|
||||||
ATTR_BATTERY_LEVEL: self._battery_level,
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _battery_level(self):
|
|
||||||
"""Return the battery level."""
|
|
||||||
return self.wink.battery_level * 100
|
|
||||||
|
@ -6,6 +6,7 @@ For more details about this component, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/camera/
|
https://home-assistant.io/components/camera/
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
@ -81,8 +82,6 @@ class Camera(Entity):
|
|||||||
|
|
||||||
def mjpeg_stream(self, response):
|
def mjpeg_stream(self, response):
|
||||||
"""Generate an HTTP MJPEG stream from camera images."""
|
"""Generate an HTTP MJPEG stream from camera images."""
|
||||||
import eventlet
|
|
||||||
|
|
||||||
def stream():
|
def stream():
|
||||||
"""Stream images as mjpeg stream."""
|
"""Stream images as mjpeg stream."""
|
||||||
try:
|
try:
|
||||||
@ -99,7 +98,7 @@ class Camera(Entity):
|
|||||||
|
|
||||||
last_image = img_bytes
|
last_image = img_bytes
|
||||||
|
|
||||||
eventlet.sleep(0.5)
|
time.sleep(0.5)
|
||||||
except GeneratorExit:
|
except GeneratorExit:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
"""Camera platform that has a Raspberry Pi camera."""
|
"""
|
||||||
|
Camera platform that has a Raspberry Pi camera.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/camera.rpi_camera/
|
||||||
|
"""
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import logging
|
import logging
|
||||||
@ -43,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class RaspberryCamera(Camera):
|
class RaspberryCamera(Camera):
|
||||||
"""Raspberry Pi camera."""
|
"""Representation of a Raspberry Pi camera."""
|
||||||
|
|
||||||
def __init__(self, device_info):
|
def __init__(self, device_info):
|
||||||
"""Initialize Raspberry Pi camera component."""
|
"""Initialize Raspberry Pi camera component."""
|
||||||
|
@ -7,9 +7,10 @@ https://home-assistant.io/components/garage_door.wink/
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.garage_door import GarageDoorDevice
|
from homeassistant.components.garage_door import GarageDoorDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL
|
from homeassistant.components.wink import WinkDevice
|
||||||
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.7.7']
|
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@ -31,38 +32,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
pywink.get_garage_doors())
|
pywink.get_garage_doors())
|
||||||
|
|
||||||
|
|
||||||
class WinkGarageDoorDevice(GarageDoorDevice):
|
class WinkGarageDoorDevice(WinkDevice, GarageDoorDevice):
|
||||||
"""Representation of a Wink garage door."""
|
"""Representation of a Wink garage door."""
|
||||||
|
|
||||||
def __init__(self, wink):
|
def __init__(self, wink):
|
||||||
"""Initialize the garage door."""
|
"""Initialize the garage door."""
|
||||||
self.wink = wink
|
WinkDevice.__init__(self, wink)
|
||||||
self._battery = self.wink.battery_level
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the ID of this wink garage door."""
|
|
||||||
return "{}.{}".format(self.__class__, self.wink.device_id())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the garage door if any."""
|
|
||||||
return self.wink.name()
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Update the state of the garage door."""
|
|
||||||
self.wink.update_state()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closed(self):
|
def is_closed(self):
|
||||||
"""Return true if door is closed."""
|
"""Return true if door is closed."""
|
||||||
return self.wink.state() == 0
|
return self.wink.state() == 0
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
"""True if connection == True."""
|
|
||||||
return self.wink.available
|
|
||||||
|
|
||||||
def close_door(self):
|
def close_door(self):
|
||||||
"""Close the door."""
|
"""Close the door."""
|
||||||
self.wink.set_state(0)
|
self.wink.set_state(0)
|
||||||
@ -70,16 +51,3 @@ class WinkGarageDoorDevice(GarageDoorDevice):
|
|||||||
def open_door(self):
|
def open_door(self):
|
||||||
"""Open the door."""
|
"""Open the door."""
|
||||||
self.wink.set_state(1)
|
self.wink.set_state(1)
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
"""Return the state attributes."""
|
|
||||||
if self._battery:
|
|
||||||
return {
|
|
||||||
ATTR_BATTERY_LEVEL: self._battery_level,
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _battery_level(self):
|
|
||||||
"""Return the battery level."""
|
|
||||||
return self.wink.battery_level * 100
|
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
Support for Homematic Devices.
|
Support for Homematic devices.
|
||||||
|
|
||||||
For more details about this component, please refer to the documentation at
|
For more details about this component, please refer to the documentation at
|
||||||
https://home-assistant.io/components/homematic/
|
https://home-assistant.io/components/homematic/
|
||||||
|
|
||||||
Configuration:
|
|
||||||
|
|
||||||
homematic:
|
|
||||||
local_ip: "<IP of device running Home Assistant>"
|
|
||||||
local_port: <Port for connection with Home Assistant>
|
|
||||||
remote_ip: "<IP of Homegear / CCU>"
|
|
||||||
remote_port: <Port of Homegear / CCU XML-RPC Server>
|
|
||||||
autodetect: "<True/False>" (optional, experimental, detect all devices)
|
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
@ -21,11 +12,10 @@ from homeassistant.helpers import discovery
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
DOMAIN = 'homematic'
|
DOMAIN = 'homematic'
|
||||||
REQUIREMENTS = ['pyhomematic==0.1.6']
|
REQUIREMENTS = ['pyhomematic==0.1.8']
|
||||||
|
|
||||||
HOMEMATIC = None
|
HOMEMATIC = None
|
||||||
HOMEMATIC_LINK_DELAY = 0.5
|
HOMEMATIC_LINK_DELAY = 0.5
|
||||||
HOMEMATIC_DEVICES = {}
|
|
||||||
|
|
||||||
DISCOVER_SWITCHES = "homematic.switch"
|
DISCOVER_SWITCHES = "homematic.switch"
|
||||||
DISCOVER_LIGHTS = "homematic.light"
|
DISCOVER_LIGHTS = "homematic.light"
|
||||||
@ -35,18 +25,22 @@ DISCOVER_ROLLERSHUTTER = "homematic.rollershutter"
|
|||||||
DISCOVER_THERMOSTATS = "homematic.thermostat"
|
DISCOVER_THERMOSTATS = "homematic.thermostat"
|
||||||
|
|
||||||
ATTR_DISCOVER_DEVICES = "devices"
|
ATTR_DISCOVER_DEVICES = "devices"
|
||||||
ATTR_DISCOVER_CONFIG = "config"
|
ATTR_PARAM = "param"
|
||||||
|
ATTR_CHANNEL = "channel"
|
||||||
|
ATTR_NAME = "name"
|
||||||
|
ATTR_ADDRESS = "address"
|
||||||
|
|
||||||
|
EVENT_KEYPRESS = "homematic.keypress"
|
||||||
|
|
||||||
HM_DEVICE_TYPES = {
|
HM_DEVICE_TYPES = {
|
||||||
DISCOVER_SWITCHES: ["Switch", "SwitchPowermeter"],
|
DISCOVER_SWITCHES: ["Switch", "SwitchPowermeter"],
|
||||||
DISCOVER_LIGHTS: ["Dimmer"],
|
DISCOVER_LIGHTS: ["Dimmer"],
|
||||||
DISCOVER_SENSORS: ["SwitchPowermeter", "Motion", "MotionV2",
|
DISCOVER_SENSORS: ["SwitchPowermeter", "Motion", "MotionV2",
|
||||||
"RemoteMotion", "ThermostatWall", "AreaThermostat",
|
"RemoteMotion", "ThermostatWall", "AreaThermostat",
|
||||||
"RotaryHandleSensor"],
|
"RotaryHandleSensor", "WaterSensor"],
|
||||||
DISCOVER_THERMOSTATS: ["Thermostat", "ThermostatWall", "MAXThermostat"],
|
DISCOVER_THERMOSTATS: ["Thermostat", "ThermostatWall", "MAXThermostat"],
|
||||||
DISCOVER_BINARY_SENSORS: ["Remote", "ShutterContact", "Smoke", "SmokeV2",
|
DISCOVER_BINARY_SENSORS: ["ShutterContact", "Smoke", "SmokeV2",
|
||||||
"Motion", "MotionV2", "RemoteMotion",
|
"Motion", "MotionV2", "RemoteMotion"],
|
||||||
"GongSensor"],
|
|
||||||
DISCOVER_ROLLERSHUTTER: ["Blind"]
|
DISCOVER_ROLLERSHUTTER: ["Blind"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +60,13 @@ HM_ATTRIBUTE_SUPPORT = {
|
|||||||
"VOLTAGE": ["Voltage", {}]
|
"VOLTAGE": ["Voltage", {}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HM_PRESS_EVENTS = [
|
||||||
|
"PRESS_SHORT",
|
||||||
|
"PRESS_LONG",
|
||||||
|
"PRESS_CONT",
|
||||||
|
"PRESS_LONG_RELEASE"
|
||||||
|
]
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -81,6 +82,8 @@ def setup(hass, config):
|
|||||||
remote_ip = config[DOMAIN].get("remote_ip", None)
|
remote_ip = config[DOMAIN].get("remote_ip", None)
|
||||||
remote_port = config[DOMAIN].get("remote_port", 2001)
|
remote_port = config[DOMAIN].get("remote_port", 2001)
|
||||||
resolvenames = config[DOMAIN].get("resolvenames", False)
|
resolvenames = config[DOMAIN].get("resolvenames", False)
|
||||||
|
username = config[DOMAIN].get("username", "Admin")
|
||||||
|
password = config[DOMAIN].get("password", "")
|
||||||
HOMEMATIC_LINK_DELAY = config[DOMAIN].get("delay", 0.5)
|
HOMEMATIC_LINK_DELAY = config[DOMAIN].get("delay", 0.5)
|
||||||
|
|
||||||
if remote_ip is None or local_ip is None:
|
if remote_ip is None or local_ip is None:
|
||||||
@ -89,12 +92,15 @@ def setup(hass, config):
|
|||||||
|
|
||||||
# Create server thread
|
# Create server thread
|
||||||
bound_system_callback = partial(system_callback_handler, hass, config)
|
bound_system_callback = partial(system_callback_handler, hass, config)
|
||||||
|
# pylint: disable=unexpected-keyword-arg
|
||||||
HOMEMATIC = HMConnection(local=local_ip,
|
HOMEMATIC = HMConnection(local=local_ip,
|
||||||
localport=local_port,
|
localport=local_port,
|
||||||
remote=remote_ip,
|
remote=remote_ip,
|
||||||
remoteport=remote_port,
|
remoteport=remote_port,
|
||||||
systemcallback=bound_system_callback,
|
systemcallback=bound_system_callback,
|
||||||
resolvenames=resolvenames,
|
resolvenames=resolvenames,
|
||||||
|
rpcusername=username,
|
||||||
|
rpcpassword=password,
|
||||||
interface_id="homeassistant")
|
interface_id="homeassistant")
|
||||||
|
|
||||||
# Start server thread, connect to peer, initialize to receive events
|
# Start server thread, connect to peer, initialize to receive events
|
||||||
@ -119,22 +125,23 @@ def system_callback_handler(hass, config, src, *args):
|
|||||||
for dev in dev_descriptions:
|
for dev in dev_descriptions:
|
||||||
key_dict[dev['ADDRESS'].split(':')[0]] = True
|
key_dict[dev['ADDRESS'].split(':')[0]] = True
|
||||||
|
|
||||||
# Connect devices already created in HA to pyhomematic and
|
# Register EVENTS
|
||||||
# add remaining devices to list
|
# Search all device with a EVENTNODE that include data
|
||||||
devices_not_created = []
|
bound_event_callback = partial(_hm_event_handler, hass)
|
||||||
for dev in key_dict:
|
for dev in key_dict:
|
||||||
if dev in HOMEMATIC_DEVICES:
|
if dev not in HOMEMATIC.devices:
|
||||||
for hm_element in HOMEMATIC_DEVICES[dev]:
|
continue
|
||||||
hm_element.link_homematic()
|
|
||||||
else:
|
hmdevice = HOMEMATIC.devices.get(dev)
|
||||||
devices_not_created.append(dev)
|
# have events?
|
||||||
|
if len(hmdevice.EVENTNODE) > 0:
|
||||||
|
_LOGGER.debug("Register Events from %s", dev)
|
||||||
|
hmdevice.setEventCallback(callback=bound_event_callback,
|
||||||
|
bequeath=True)
|
||||||
|
|
||||||
# If configuration allows autodetection of devices,
|
# If configuration allows autodetection of devices,
|
||||||
# all devices not configured are added.
|
# all devices not configured are added.
|
||||||
autodetect = config[DOMAIN].get("autodetect", False)
|
if key_dict:
|
||||||
_LOGGER.debug("Autodetect is %s / unknown device: %s", str(autodetect),
|
|
||||||
str(devices_not_created))
|
|
||||||
if autodetect and devices_not_created:
|
|
||||||
for component_name, discovery_type in (
|
for component_name, discovery_type in (
|
||||||
('switch', DISCOVER_SWITCHES),
|
('switch', DISCOVER_SWITCHES),
|
||||||
('light', DISCOVER_LIGHTS),
|
('light', DISCOVER_LIGHTS),
|
||||||
@ -143,8 +150,7 @@ def system_callback_handler(hass, config, src, *args):
|
|||||||
('sensor', DISCOVER_SENSORS),
|
('sensor', DISCOVER_SENSORS),
|
||||||
('thermostat', DISCOVER_THERMOSTATS)):
|
('thermostat', DISCOVER_THERMOSTATS)):
|
||||||
# Get all devices of a specific type
|
# Get all devices of a specific type
|
||||||
found_devices = _get_devices(discovery_type,
|
found_devices = _get_devices(discovery_type, key_dict)
|
||||||
devices_not_created)
|
|
||||||
|
|
||||||
# When devices of this type are found
|
# When devices of this type are found
|
||||||
# they are setup in HA and an event is fired
|
# they are setup in HA and an event is fired
|
||||||
@ -156,32 +162,25 @@ def system_callback_handler(hass, config, src, *args):
|
|||||||
|
|
||||||
|
|
||||||
def _get_devices(device_type, keys):
|
def _get_devices(device_type, keys):
|
||||||
"""Get devices."""
|
"""Get the Homematic devices."""
|
||||||
from homeassistant.components.binary_sensor.homematic import \
|
|
||||||
SUPPORT_HM_EVENT_AS_BINMOD
|
|
||||||
|
|
||||||
# run
|
# run
|
||||||
device_arr = []
|
device_arr = []
|
||||||
if not keys:
|
|
||||||
keys = HOMEMATIC.devices
|
|
||||||
for key in keys:
|
for key in keys:
|
||||||
device = HOMEMATIC.devices[key]
|
device = HOMEMATIC.devices[key]
|
||||||
if device.__class__.__name__ not in HM_DEVICE_TYPES[device_type]:
|
class_name = device.__class__.__name__
|
||||||
continue
|
|
||||||
metadata = {}
|
metadata = {}
|
||||||
|
|
||||||
|
# is class supported by discovery type
|
||||||
|
if class_name not in HM_DEVICE_TYPES[device_type]:
|
||||||
|
continue
|
||||||
|
|
||||||
# Load metadata if needed to generate a param list
|
# Load metadata if needed to generate a param list
|
||||||
if device_type == DISCOVER_SENSORS:
|
if device_type == DISCOVER_SENSORS:
|
||||||
metadata.update(device.SENSORNODE)
|
metadata.update(device.SENSORNODE)
|
||||||
elif device_type == DISCOVER_BINARY_SENSORS:
|
elif device_type == DISCOVER_BINARY_SENSORS:
|
||||||
metadata.update(device.BINARYNODE)
|
metadata.update(device.BINARYNODE)
|
||||||
|
|
||||||
# Also add supported events as binary type
|
params = _create_params_list(device, metadata, device_type)
|
||||||
for event, channel in device.EVENTNODE.items():
|
|
||||||
if event in SUPPORT_HM_EVENT_AS_BINMOD:
|
|
||||||
metadata.update({event: channel})
|
|
||||||
|
|
||||||
params = _create_params_list(device, metadata)
|
|
||||||
if params:
|
if params:
|
||||||
# Generate options for 1...n elements with 1...n params
|
# Generate options for 1...n elements with 1...n params
|
||||||
for channel in range(1, device.ELEMENT + 1):
|
for channel in range(1, device.ELEMENT + 1):
|
||||||
@ -194,9 +193,9 @@ def _get_devices(device_type, keys):
|
|||||||
device_dict = dict(platform="homematic",
|
device_dict = dict(platform="homematic",
|
||||||
address=key,
|
address=key,
|
||||||
name=name,
|
name=name,
|
||||||
button=channel)
|
channel=channel)
|
||||||
if param is not None:
|
if param is not None:
|
||||||
device_dict["param"] = param
|
device_dict[ATTR_PARAM] = param
|
||||||
|
|
||||||
# Add new device
|
# Add new device
|
||||||
device_arr.append(device_dict)
|
device_arr.append(device_dict)
|
||||||
@ -209,15 +208,22 @@ def _get_devices(device_type, keys):
|
|||||||
return device_arr
|
return device_arr
|
||||||
|
|
||||||
|
|
||||||
def _create_params_list(hmdevice, metadata):
|
def _create_params_list(hmdevice, metadata, device_type):
|
||||||
"""Create a list from HMDevice with all possible parameters in config."""
|
"""Create a list from HMDevice with all possible parameters in config."""
|
||||||
params = {}
|
params = {}
|
||||||
|
merge = False
|
||||||
|
|
||||||
|
# use merge?
|
||||||
|
if device_type == DISCOVER_SENSORS:
|
||||||
|
merge = True
|
||||||
|
elif device_type == DISCOVER_BINARY_SENSORS:
|
||||||
|
merge = True
|
||||||
|
|
||||||
# Search in sensor and binary metadata per elements
|
# Search in sensor and binary metadata per elements
|
||||||
for channel in range(1, hmdevice.ELEMENT + 1):
|
for channel in range(1, hmdevice.ELEMENT + 1):
|
||||||
param_chan = []
|
param_chan = []
|
||||||
try:
|
for node, meta_chan in metadata.items():
|
||||||
for node, meta_chan in metadata.items():
|
try:
|
||||||
# Is this attribute ignored?
|
# Is this attribute ignored?
|
||||||
if node in HM_IGNORE_DISCOVERY_NODE:
|
if node in HM_IGNORE_DISCOVERY_NODE:
|
||||||
continue
|
continue
|
||||||
@ -227,15 +233,17 @@ def _create_params_list(hmdevice, metadata):
|
|||||||
elif channel == 1:
|
elif channel == 1:
|
||||||
# First channel can have other data channel
|
# First channel can have other data channel
|
||||||
param_chan.append(node)
|
param_chan.append(node)
|
||||||
# pylint: disable=broad-except
|
except (TypeError, ValueError):
|
||||||
except Exception as err:
|
_LOGGER.error("Exception generating %s (%s)",
|
||||||
_LOGGER.error("Exception generating %s (%s): %s",
|
hmdevice.ADDRESS, str(metadata))
|
||||||
hmdevice.ADDRESS, str(metadata), str(err))
|
|
||||||
# Default parameter
|
# default parameter is merge is off
|
||||||
if not param_chan:
|
if len(param_chan) == 0 and not merge:
|
||||||
param_chan.append(None)
|
param_chan.append(None)
|
||||||
|
|
||||||
# Add to channel
|
# Add to channel
|
||||||
params.update({channel: param_chan})
|
if len(param_chan) > 0:
|
||||||
|
params.update({channel: param_chan})
|
||||||
|
|
||||||
_LOGGER.debug("Create param list for %s with: %s", hmdevice.ADDRESS,
|
_LOGGER.debug("Create param list for %s with: %s", hmdevice.ADDRESS,
|
||||||
str(params))
|
str(params))
|
||||||
@ -264,55 +272,55 @@ def _create_ha_name(name, channel, param):
|
|||||||
def setup_hmdevice_discovery_helper(hmdevicetype, discovery_info,
|
def setup_hmdevice_discovery_helper(hmdevicetype, discovery_info,
|
||||||
add_callback_devices):
|
add_callback_devices):
|
||||||
"""Helper to setup Homematic devices with discovery info."""
|
"""Helper to setup Homematic devices with discovery info."""
|
||||||
for config in discovery_info["devices"]:
|
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
|
||||||
ret = setup_hmdevice_entity_helper(hmdevicetype, config,
|
_LOGGER.debug("Add device %s from config: %s",
|
||||||
add_callback_devices)
|
str(hmdevicetype), str(config))
|
||||||
if not ret:
|
|
||||||
_LOGGER.error("Setup discovery error with config %s", str(config))
|
# create object and add to HA
|
||||||
|
new_device = hmdevicetype(config)
|
||||||
|
add_callback_devices([new_device])
|
||||||
|
|
||||||
|
# link to HM
|
||||||
|
new_device.link_homematic()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def setup_hmdevice_entity_helper(hmdevicetype, config, add_callback_devices):
|
def _hm_event_handler(hass, device, caller, attribute, value):
|
||||||
"""Helper to setup Homematic devices."""
|
"""Handle all pyhomematic device events."""
|
||||||
if HOMEMATIC is None:
|
channel = device.split(":")[1]
|
||||||
_LOGGER.error('Error setting up HMDevice: Server not configured.')
|
address = device.split(":")[0]
|
||||||
return False
|
hmdevice = HOMEMATIC.devices.get(address)
|
||||||
|
|
||||||
address = config.get('address', None)
|
# is not a event?
|
||||||
if address is None:
|
if attribute not in hmdevice.EVENTNODE:
|
||||||
_LOGGER.error("Error setting up device '%s': " +
|
return
|
||||||
"'address' missing in configuration.", address)
|
|
||||||
return False
|
|
||||||
|
|
||||||
_LOGGER.debug("Add device %s from config: %s",
|
_LOGGER.debug("Event %s for %s channel %s", attribute,
|
||||||
str(hmdevicetype), str(config))
|
hmdevice.NAME, channel)
|
||||||
# Create a new HA homematic object
|
|
||||||
new_device = hmdevicetype(config)
|
|
||||||
if address not in HOMEMATIC_DEVICES:
|
|
||||||
HOMEMATIC_DEVICES[address] = []
|
|
||||||
HOMEMATIC_DEVICES[address].append(new_device)
|
|
||||||
|
|
||||||
# Add to HA
|
# a keypress event
|
||||||
add_callback_devices([new_device])
|
if attribute in HM_PRESS_EVENTS:
|
||||||
|
hass.bus.fire(EVENT_KEYPRESS, {
|
||||||
|
ATTR_NAME: hmdevice.NAME,
|
||||||
|
ATTR_PARAM: attribute,
|
||||||
|
ATTR_CHANNEL: channel
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
# HM is connected
|
_LOGGER.warning("Event is unknown and not forwarded to HA")
|
||||||
if address in HOMEMATIC.devices:
|
|
||||||
return new_device.link_homematic()
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class HMDevice(Entity):
|
class HMDevice(Entity):
|
||||||
"""Homematic device base object."""
|
"""The Homematic device base object."""
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
"""Initialize generic HM device."""
|
"""Initialize a generic Homematic device."""
|
||||||
self._name = config.get("name", None)
|
self._name = config.get(ATTR_NAME, None)
|
||||||
self._address = config.get("address", None)
|
self._address = config.get(ATTR_ADDRESS, None)
|
||||||
self._channel = config.get("button", 1)
|
self._channel = config.get(ATTR_CHANNEL, 1)
|
||||||
self._state = config.get("param", None)
|
self._state = config.get(ATTR_PARAM, None)
|
||||||
self._hidden = config.get("hidden", False)
|
|
||||||
self._data = {}
|
self._data = {}
|
||||||
self._hmdevice = None
|
self._hmdevice = None
|
||||||
self._connected = False
|
self._connected = False
|
||||||
@ -330,7 +338,7 @@ class HMDevice(Entity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""Return False. Homematic states are pushed by the XML RPC Server."""
|
"""Return false. Homematic states are pushed by the XML RPC Server."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -340,24 +348,23 @@ class HMDevice(Entity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def assumed_state(self):
|
def assumed_state(self):
|
||||||
"""Return True if unable to access real state of the device."""
|
"""Return true if unable to access real state of the device."""
|
||||||
return not self._available
|
return not self._available
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
"""Return True if device is available."""
|
"""Return true if device is available."""
|
||||||
return self._available
|
return self._available
|
||||||
|
|
||||||
@property
|
|
||||||
def hidden(self):
|
|
||||||
"""Return True if the entity should be hidden from UIs."""
|
|
||||||
return self._hidden
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return device specific state attributes."""
|
"""Return device specific state attributes."""
|
||||||
attr = {}
|
attr = {}
|
||||||
|
|
||||||
|
# no data available to create
|
||||||
|
if not self.available:
|
||||||
|
return attr
|
||||||
|
|
||||||
# Generate an attributes list
|
# Generate an attributes list
|
||||||
for node, data in HM_ATTRIBUTE_SUPPORT.items():
|
for node, data in HM_ATTRIBUTE_SUPPORT.items():
|
||||||
# Is an attributes and exists for this object
|
# Is an attributes and exists for this object
|
||||||
@ -365,10 +372,13 @@ class HMDevice(Entity):
|
|||||||
value = data[1].get(self._data[node], self._data[node])
|
value = data[1].get(self._data[node], self._data[node])
|
||||||
attr[data[0]] = value
|
attr[data[0]] = value
|
||||||
|
|
||||||
|
# static attributes
|
||||||
|
attr["ID"] = self._hmdevice.ADDRESS
|
||||||
|
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
def link_homematic(self):
|
def link_homematic(self):
|
||||||
"""Connect to homematic."""
|
"""Connect to Homematic."""
|
||||||
# device is already linked
|
# device is already linked
|
||||||
if self._connected:
|
if self._connected:
|
||||||
return True
|
return True
|
||||||
@ -379,7 +389,7 @@ class HMDevice(Entity):
|
|||||||
self._hmdevice = HOMEMATIC.devices[self._address]
|
self._hmdevice = HOMEMATIC.devices[self._address]
|
||||||
self._connected = True
|
self._connected = True
|
||||||
|
|
||||||
# Check if HM class is okay for HA class
|
# Check if Homematic class is okay for HA class
|
||||||
_LOGGER.info("Start linking %s to %s", self._address, self._name)
|
_LOGGER.info("Start linking %s to %s", self._address, self._name)
|
||||||
if self._check_hm_to_ha_object():
|
if self._check_hm_to_ha_object():
|
||||||
try:
|
try:
|
||||||
@ -402,7 +412,7 @@ class HMDevice(Entity):
|
|||||||
_LOGGER.error("Exception while linking %s: %s",
|
_LOGGER.error("Exception while linking %s: %s",
|
||||||
self._address, str(err))
|
self._address, str(err))
|
||||||
else:
|
else:
|
||||||
_LOGGER.critical("Delink %s object from HM!", self._name)
|
_LOGGER.critical("Delink %s object from HM", self._name)
|
||||||
self._connected = False
|
self._connected = False
|
||||||
|
|
||||||
# Update HA
|
# Update HA
|
||||||
@ -429,18 +439,12 @@ class HMDevice(Entity):
|
|||||||
self._available = bool(value)
|
self._available = bool(value)
|
||||||
have_change = True
|
have_change = True
|
||||||
|
|
||||||
# If it has changed, update HA
|
# If it has changed data point, update HA
|
||||||
if have_change:
|
if have_change:
|
||||||
_LOGGER.debug("%s update_ha_state after '%s'", self._name,
|
_LOGGER.debug("%s update_ha_state after '%s'", self._name,
|
||||||
attribute)
|
attribute)
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
# Reset events
|
|
||||||
if attribute in self._hmdevice.EVENTNODE:
|
|
||||||
_LOGGER.debug("%s reset event", self._name)
|
|
||||||
self._data[attribute] = False
|
|
||||||
self.update_ha_state()
|
|
||||||
|
|
||||||
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 = {}
|
||||||
@ -488,24 +492,21 @@ class HMDevice(Entity):
|
|||||||
if node in self._data:
|
if node in self._data:
|
||||||
self._data[node] = funct(name=node, channel=self._channel)
|
self._data[node] = funct(name=node, channel=self._channel)
|
||||||
|
|
||||||
# Set events to False
|
|
||||||
for node in self._hmdevice.EVENTNODE:
|
|
||||||
if node in self._data:
|
|
||||||
self._data[node] = False
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _hm_set_state(self, value):
|
def _hm_set_state(self, value):
|
||||||
|
"""Set data to main datapoint."""
|
||||||
if self._state in self._data:
|
if self._state in self._data:
|
||||||
self._data[self._state] = value
|
self._data[self._state] = value
|
||||||
|
|
||||||
def _hm_get_state(self):
|
def _hm_get_state(self):
|
||||||
|
"""Get data from main datapoint."""
|
||||||
if self._state in self._data:
|
if self._state in self._data:
|
||||||
return self._data[self._state]
|
return self._data[self._state]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _check_hm_to_ha_object(self):
|
def _check_hm_to_ha_object(self):
|
||||||
"""Check if it is possible to use the HM Object as this HA type.
|
"""Check if it is possible to use the Homematic object as this HA type.
|
||||||
|
|
||||||
NEEDS overwrite by inherit!
|
NEEDS overwrite by inherit!
|
||||||
"""
|
"""
|
||||||
@ -521,7 +522,7 @@ class HMDevice(Entity):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _init_data_struct(self):
|
def _init_data_struct(self):
|
||||||
"""Generate a data dict (self._data) from hm metadata.
|
"""Generate a data dict (self._data) from the Homematic metadata.
|
||||||
|
|
||||||
NEEDS overwrite by inherit!
|
NEEDS overwrite by inherit!
|
||||||
"""
|
"""
|
||||||
|
@ -13,19 +13,19 @@ import re
|
|||||||
import ssl
|
import ssl
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.core as ha
|
|
||||||
import homeassistant.remote as rem
|
import homeassistant.remote as rem
|
||||||
from homeassistant import util
|
from homeassistant import util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
SERVER_PORT, HTTP_HEADER_HA_AUTH, HTTP_HEADER_CACHE_CONTROL,
|
SERVER_PORT, HTTP_HEADER_HA_AUTH, HTTP_HEADER_CACHE_CONTROL,
|
||||||
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||||
HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_CORS_HEADERS)
|
HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_CORS_HEADERS,
|
||||||
|
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START)
|
||||||
from homeassistant.helpers.entity import split_entity_id
|
from homeassistant.helpers.entity import split_entity_id
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
DOMAIN = "http"
|
DOMAIN = "http"
|
||||||
REQUIREMENTS = ("eventlet==0.19.0", "static3==0.7.0", "Werkzeug==0.11.5")
|
REQUIREMENTS = ("cherrypy==6.0.2", "static3==0.7.0", "Werkzeug==0.11.10")
|
||||||
|
|
||||||
CONF_API_PASSWORD = "api_password"
|
CONF_API_PASSWORD = "api_password"
|
||||||
CONF_SERVER_HOST = "server_host"
|
CONF_SERVER_HOST = "server_host"
|
||||||
@ -40,7 +40,8 @@ DATA_API_PASSWORD = 'api_password'
|
|||||||
# TLS configuation follows the best-practice guidelines
|
# TLS configuation follows the best-practice guidelines
|
||||||
# specified here: https://wiki.mozilla.org/Security/Server_Side_TLS
|
# specified here: https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||||
# Intermediate guidelines are followed.
|
# Intermediate guidelines are followed.
|
||||||
SSL_VERSION = ssl.PROTOCOL_TLSv1
|
SSL_VERSION = ssl.PROTOCOL_SSLv23
|
||||||
|
SSL_OPTS = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_COMPRESSION
|
||||||
CIPHERS = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:" \
|
CIPHERS = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:" \
|
||||||
"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:" \
|
"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:" \
|
||||||
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:" \
|
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:" \
|
||||||
@ -117,11 +118,17 @@ def setup(hass, config):
|
|||||||
cors_origins=cors_origins
|
cors_origins=cors_origins
|
||||||
)
|
)
|
||||||
|
|
||||||
hass.bus.listen_once(
|
def start_wsgi_server(event):
|
||||||
ha.EVENT_HOMEASSISTANT_START,
|
"""Start the WSGI server."""
|
||||||
lambda event:
|
server.start()
|
||||||
threading.Thread(target=server.start, daemon=True,
|
|
||||||
name='WSGI-server').start())
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_wsgi_server)
|
||||||
|
|
||||||
|
def stop_wsgi_server(event):
|
||||||
|
"""Stop the WSGI server."""
|
||||||
|
server.stop()
|
||||||
|
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_wsgi_server)
|
||||||
|
|
||||||
hass.wsgi = server
|
hass.wsgi = server
|
||||||
hass.config.api = rem.API(server_host if server_host != '0.0.0.0'
|
hass.config.api = rem.API(server_host if server_host != '0.0.0.0'
|
||||||
@ -240,6 +247,7 @@ class HomeAssistantWSGI(object):
|
|||||||
self.server_port = server_port
|
self.server_port = server_port
|
||||||
self.cors_origins = cors_origins
|
self.cors_origins = cors_origins
|
||||||
self.event_forwarder = None
|
self.event_forwarder = None
|
||||||
|
self.server = None
|
||||||
|
|
||||||
def register_view(self, view):
|
def register_view(self, view):
|
||||||
"""Register a view with the WSGI server.
|
"""Register a view with the WSGI server.
|
||||||
@ -307,15 +315,34 @@ class HomeAssistantWSGI(object):
|
|||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start the wsgi server."""
|
"""Start the wsgi server."""
|
||||||
from eventlet import wsgi
|
from cherrypy import wsgiserver
|
||||||
import eventlet
|
from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods,super-init-not-called
|
||||||
|
class ContextSSLAdapter(BuiltinSSLAdapter):
|
||||||
|
"""SSL Adapter that takes in an SSL context."""
|
||||||
|
|
||||||
|
def __init__(self, context):
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
# pylint: disable=no-member
|
||||||
|
self.server = wsgiserver.CherryPyWSGIServer(
|
||||||
|
(self.server_host, self.server_port), self,
|
||||||
|
server_name='Home Assistant')
|
||||||
|
|
||||||
sock = eventlet.listen((self.server_host, self.server_port))
|
|
||||||
if self.ssl_certificate:
|
if self.ssl_certificate:
|
||||||
sock = eventlet.wrap_ssl(sock, certfile=self.ssl_certificate,
|
context = ssl.SSLContext(SSL_VERSION)
|
||||||
keyfile=self.ssl_key, server_side=True,
|
context.options |= SSL_OPTS
|
||||||
ssl_version=SSL_VERSION, ciphers=CIPHERS)
|
context.set_ciphers(CIPHERS)
|
||||||
wsgi.server(sock, self, log=_LOGGER)
|
context.load_cert_chain(self.ssl_certificate, self.ssl_key)
|
||||||
|
self.server.ssl_adapter = ContextSSLAdapter(context)
|
||||||
|
|
||||||
|
threading.Thread(target=self.server.start, daemon=True,
|
||||||
|
name='WSGI-server').start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Stop the wsgi server."""
|
||||||
|
self.server.stop()
|
||||||
|
|
||||||
def dispatch_request(self, request):
|
def dispatch_request(self, request):
|
||||||
"""Handle incoming request."""
|
"""Handle incoming request."""
|
||||||
@ -362,6 +389,10 @@ class HomeAssistantWSGI(object):
|
|||||||
"""Handle a request for base app + extra apps."""
|
"""Handle a request for base app + extra apps."""
|
||||||
from werkzeug.wsgi import DispatcherMiddleware
|
from werkzeug.wsgi import DispatcherMiddleware
|
||||||
|
|
||||||
|
if not self.hass.is_running:
|
||||||
|
from werkzeug.exceptions import BadRequest
|
||||||
|
return BadRequest()(environ, start_response)
|
||||||
|
|
||||||
app = DispatcherMiddleware(self.base_app, self.extra_apps)
|
app = DispatcherMiddleware(self.base_app, self.extra_apps)
|
||||||
# Strip out any cachebusting MD5 fingerprints
|
# Strip out any cachebusting MD5 fingerprints
|
||||||
fingerprinted = _FINGERPRINT.match(environ.get('PATH_INFO', ''))
|
fingerprinted = _FINGERPRINT.match(environ.get('PATH_INFO', ''))
|
||||||
|
@ -23,7 +23,7 @@ DEFAULT_DATABASE = 'home_assistant'
|
|||||||
DEFAULT_SSL = False
|
DEFAULT_SSL = False
|
||||||
DEFAULT_VERIFY_SSL = False
|
DEFAULT_VERIFY_SSL = False
|
||||||
|
|
||||||
REQUIREMENTS = ['influxdb==2.12.0']
|
REQUIREMENTS = ['influxdb==3.0.0']
|
||||||
|
|
||||||
CONF_HOST = 'host'
|
CONF_HOST = 'host'
|
||||||
CONF_PORT = 'port'
|
CONF_PORT = 'port'
|
||||||
|
@ -4,7 +4,6 @@ Support for EnOcean light sources.
|
|||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/light.enocean/
|
https://home-assistant.io/components/light.enocean/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
|
||||||
@ -86,7 +85,7 @@ class EnOceanLight(enocean.EnOceanDevice, Light):
|
|||||||
self._on_state = False
|
self._on_state = False
|
||||||
|
|
||||||
def value_changed(self, val):
|
def value_changed(self, val):
|
||||||
"""Update the internal state of this device in HA."""
|
"""Update the internal state of this device."""
|
||||||
self._brightness = math.floor(val / 100.0 * 256.0)
|
self._brightness = math.floor(val / 100.0 * 256.0)
|
||||||
self._on_state = bool(val != 0)
|
self._on_state = bool(val != 0)
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
@ -1,21 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
The homematic light platform.
|
Support for Homematic lighs.
|
||||||
|
|
||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/light.homematic/
|
https://home-assistant.io/components/light.homematic/
|
||||||
|
|
||||||
Important: For this platform to work the homematic component has to be
|
|
||||||
properly configured.
|
|
||||||
|
|
||||||
Configuration:
|
|
||||||
|
|
||||||
light:
|
|
||||||
- platform: homematic
|
|
||||||
addresss: <Homematic addresss for device> # e.g. "JEQ0XXXXXXX"
|
|
||||||
name: <User defined name> (optional)
|
|
||||||
button: n (integer of channel to map, device-dependent)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from homeassistant.components.light import (ATTR_BRIGHTNESS, Light)
|
from homeassistant.components.light import (ATTR_BRIGHTNESS, Light)
|
||||||
from homeassistant.const import STATE_UNKNOWN
|
from homeassistant.const import STATE_UNKNOWN
|
||||||
@ -23,24 +11,21 @@ import homeassistant.components.homematic as homematic
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# List of component names (string) your component depends upon.
|
|
||||||
DEPENDENCIES = ['homematic']
|
DEPENDENCIES = ['homematic']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||||
"""Setup the platform."""
|
"""Setup the Homematic light platform."""
|
||||||
if discovery_info:
|
if discovery_info is None:
|
||||||
return homematic.setup_hmdevice_discovery_helper(HMLight,
|
return
|
||||||
discovery_info,
|
|
||||||
add_callback_devices)
|
return homematic.setup_hmdevice_discovery_helper(HMLight,
|
||||||
# Manual
|
discovery_info,
|
||||||
return homematic.setup_hmdevice_entity_helper(HMLight,
|
add_callback_devices)
|
||||||
config,
|
|
||||||
add_callback_devices)
|
|
||||||
|
|
||||||
|
|
||||||
class HMLight(homematic.HMDevice, Light):
|
class HMLight(homematic.HMDevice, Light):
|
||||||
"""Represents a Homematic Light in Home Assistant."""
|
"""Representation of a Homematic light."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self):
|
||||||
@ -55,7 +40,7 @@ class HMLight(homematic.HMDevice, Light):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return True if light is on."""
|
"""Return true if light is on."""
|
||||||
try:
|
try:
|
||||||
return self._hm_get_state() > 0
|
return self._hm_get_state() > 0
|
||||||
except TypeError:
|
except TypeError:
|
||||||
@ -78,24 +63,24 @@ class HMLight(homematic.HMDevice, Light):
|
|||||||
self._hmdevice.off(self._channel)
|
self._hmdevice.off(self._channel)
|
||||||
|
|
||||||
def _check_hm_to_ha_object(self):
|
def _check_hm_to_ha_object(self):
|
||||||
"""Check if possible to use the HM Object as this HA type."""
|
"""Check if possible to use the Homematic object as this HA type."""
|
||||||
from pyhomematic.devicetypes.actors import Dimmer, Switch
|
from pyhomematic.devicetypes.actors import Dimmer, Switch
|
||||||
|
|
||||||
# Check compatibility from HMDevice
|
# Check compatibility from HMDevice
|
||||||
if not super()._check_hm_to_ha_object():
|
if not super()._check_hm_to_ha_object():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if the homematic device is correct for this HA device
|
# Check if the Homematic device is correct for this HA device
|
||||||
if isinstance(self._hmdevice, Switch):
|
if isinstance(self._hmdevice, Switch):
|
||||||
return True
|
return True
|
||||||
if isinstance(self._hmdevice, Dimmer):
|
if isinstance(self._hmdevice, Dimmer):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
_LOGGER.critical("This %s can't be use as light!", self._name)
|
_LOGGER.critical("This %s can't be use as light", self._name)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _init_data_struct(self):
|
def _init_data_struct(self):
|
||||||
"""Generate a data dict (self._data) from hm metadata."""
|
"""Generate a data dict (self._data) from the Homematic metadata."""
|
||||||
from pyhomematic.devicetypes.actors import Dimmer, Switch
|
from pyhomematic.devicetypes.actors import Dimmer, Switch
|
||||||
|
|
||||||
super()._init_data_struct()
|
super()._init_data_struct()
|
||||||
|
@ -1,19 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
Support for Osram Lightify.
|
Support for Osram Lightify.
|
||||||
|
|
||||||
Uses: https://github.com/aneumeier/python-lightify for the Osram light
|
For more details about this platform, please refer to the documentation at
|
||||||
interface.
|
https://home-assistant.io/components/light.osramlightify/
|
||||||
|
|
||||||
In order to use the platform just add the following to the configuration.yaml:
|
|
||||||
|
|
||||||
light:
|
|
||||||
platform: osramlightify
|
|
||||||
host: <hostname_or_ip>
|
|
||||||
|
|
||||||
Todo:
|
|
||||||
Add support for Non RGBW lights.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@ -40,7 +30,7 @@ MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
"""Find and return lights."""
|
"""Setup Osram Lightify lights."""
|
||||||
import lightify
|
import lightify
|
||||||
host = config.get(CONF_HOST)
|
host = config.get(CONF_HOST)
|
||||||
if host:
|
if host:
|
||||||
@ -85,7 +75,7 @@ def setup_bridge(bridge, add_devices_callback):
|
|||||||
|
|
||||||
|
|
||||||
class OsramLightifyLight(Light):
|
class OsramLightifyLight(Light):
|
||||||
"""Defines an Osram Lightify Light."""
|
"""Representation of an Osram Lightify Light."""
|
||||||
|
|
||||||
def __init__(self, light_id, light, update_lights):
|
def __init__(self, light_id, light, update_lights):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
|
@ -8,12 +8,13 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \
|
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \
|
||||||
Light, ATTR_RGB_COLOR
|
Light, ATTR_RGB_COLOR
|
||||||
|
from homeassistant.components.wink import WinkDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
from homeassistant.util import color as color_util
|
from homeassistant.util import color as color_util
|
||||||
from homeassistant.util.color import \
|
from homeassistant.util.color import \
|
||||||
color_temperature_mired_to_kelvin as mired_to_kelvin
|
color_temperature_mired_to_kelvin as mired_to_kelvin
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.7.7']
|
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
@ -35,26 +36,12 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
WinkLight(light) for light in pywink.get_bulbs())
|
WinkLight(light) for light in pywink.get_bulbs())
|
||||||
|
|
||||||
|
|
||||||
class WinkLight(Light):
|
class WinkLight(WinkDevice, Light):
|
||||||
"""Representation of a Wink light."""
|
"""Representation of a Wink light."""
|
||||||
|
|
||||||
def __init__(self, wink):
|
def __init__(self, wink):
|
||||||
"""
|
"""Initialize the Wink device."""
|
||||||
Initialize the light.
|
WinkDevice.__init__(self, wink)
|
||||||
|
|
||||||
:type wink: pywink.devices.standard.bulb.WinkBulb
|
|
||||||
"""
|
|
||||||
self.wink = wink
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the ID of this Wink light."""
|
|
||||||
return "{}.{}".format(self.__class__, self.wink.device_id())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the light if any."""
|
|
||||||
return self.wink.name()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
@ -66,11 +53,6 @@ class WinkLight(Light):
|
|||||||
"""Return the brightness of the light."""
|
"""Return the brightness of the light."""
|
||||||
return int(self.wink.brightness() * 255)
|
return int(self.wink.brightness() * 255)
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
"""True if connection == True."""
|
|
||||||
return self.wink.available
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def xy_color(self):
|
def xy_color(self):
|
||||||
"""Current bulb color in CIE 1931 (XY) color space."""
|
"""Current bulb color in CIE 1931 (XY) color space."""
|
||||||
@ -112,7 +94,3 @@ class WinkLight(Light):
|
|||||||
def turn_off(self):
|
def turn_off(self):
|
||||||
"""Turn the switch off."""
|
"""Turn the switch off."""
|
||||||
self.wink.set_state(False)
|
self.wink.set_state(False)
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Update state of the light."""
|
|
||||||
self.wink.update_state(require_desired_state_fulfilled=True)
|
|
||||||
|
@ -4,12 +4,31 @@ Support for Z-Wave lights.
|
|||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/light.zwave/
|
https://home-assistant.io/components/light.zwave/
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
# Because we do not compile openzwave on CI
|
# Because we do not compile openzwave on CI
|
||||||
# pylint: disable=import-error
|
# pylint: disable=import-error
|
||||||
from threading import Timer
|
from threading import Timer
|
||||||
from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN, Light
|
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \
|
||||||
|
ATTR_RGB_COLOR, DOMAIN, Light
|
||||||
from homeassistant.components import zwave
|
from homeassistant.components import zwave
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON
|
from homeassistant.const import STATE_OFF, STATE_ON
|
||||||
|
from homeassistant.util.color import HASS_COLOR_MAX, HASS_COLOR_MIN, \
|
||||||
|
color_temperature_mired_to_kelvin, color_temperature_to_rgb
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
COLOR_CHANNEL_WARM_WHITE = 0x01
|
||||||
|
COLOR_CHANNEL_COLD_WHITE = 0x02
|
||||||
|
COLOR_CHANNEL_RED = 0x04
|
||||||
|
COLOR_CHANNEL_GREEN = 0x08
|
||||||
|
COLOR_CHANNEL_BLUE = 0x10
|
||||||
|
|
||||||
|
# Generate midpoint color temperatures for bulbs that have limited
|
||||||
|
# support for white light colors
|
||||||
|
TEMP_MID_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 2 + HASS_COLOR_MIN
|
||||||
|
TEMP_WARM_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 * 2 + HASS_COLOR_MIN
|
||||||
|
TEMP_COLD_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 + HASS_COLOR_MIN
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@ -28,7 +47,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
return
|
return
|
||||||
|
|
||||||
value.set_change_verified(False)
|
value.set_change_verified(False)
|
||||||
add_devices([ZwaveDimmer(value)])
|
|
||||||
|
if node.has_command_class(zwave.COMMAND_CLASS_COLOR):
|
||||||
|
try:
|
||||||
|
add_devices([ZwaveColorLight(value)])
|
||||||
|
except ValueError as exception:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Error initializing as color bulb: %s "
|
||||||
|
"Initializing as standard dimmer.", exception)
|
||||||
|
add_devices([ZwaveDimmer(value)])
|
||||||
|
else:
|
||||||
|
add_devices([ZwaveDimmer(value)])
|
||||||
|
|
||||||
|
|
||||||
def brightness_state(value):
|
def brightness_state(value):
|
||||||
@ -49,8 +78,9 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
|||||||
from pydispatch import dispatcher
|
from pydispatch import dispatcher
|
||||||
|
|
||||||
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
||||||
|
self._brightness = None
|
||||||
self._brightness, self._state = brightness_state(value)
|
self._state = None
|
||||||
|
self.update_properties()
|
||||||
|
|
||||||
# Used for value change event handling
|
# Used for value change event handling
|
||||||
self._refreshing = False
|
self._refreshing = False
|
||||||
@ -59,6 +89,11 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
|||||||
dispatcher.connect(
|
dispatcher.connect(
|
||||||
self._value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
|
self._value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
|
||||||
|
|
||||||
|
def update_properties(self):
|
||||||
|
"""Update internal properties based on zwave values."""
|
||||||
|
# Brightness
|
||||||
|
self._brightness, self._state = brightness_state(self._value)
|
||||||
|
|
||||||
def _value_changed(self, value):
|
def _value_changed(self, value):
|
||||||
"""Called when a value has changed on the network."""
|
"""Called when a value has changed on the network."""
|
||||||
if self._value.value_id != value.value_id:
|
if self._value.value_id != value.value_id:
|
||||||
@ -66,7 +101,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
|||||||
|
|
||||||
if self._refreshing:
|
if self._refreshing:
|
||||||
self._refreshing = False
|
self._refreshing = False
|
||||||
self._brightness, self._state = brightness_state(value)
|
self.update_properties()
|
||||||
else:
|
else:
|
||||||
def _refresh_value():
|
def _refresh_value():
|
||||||
"""Used timer callback for delayed value refresh."""
|
"""Used timer callback for delayed value refresh."""
|
||||||
@ -107,3 +142,168 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
|||||||
"""Turn the device off."""
|
"""Turn the device off."""
|
||||||
if self._value.node.set_dimmer(self._value.value_id, 0):
|
if self._value.node.set_dimmer(self._value.value_id, 0):
|
||||||
self._state = STATE_OFF
|
self._state = STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
|
def ct_to_rgb(temp):
|
||||||
|
"""Convert color temperature (mireds) to RGB."""
|
||||||
|
colorlist = list(
|
||||||
|
color_temperature_to_rgb(color_temperature_mired_to_kelvin(temp)))
|
||||||
|
return [int(val) for val in colorlist]
|
||||||
|
|
||||||
|
|
||||||
|
class ZwaveColorLight(ZwaveDimmer):
|
||||||
|
"""Representation of a Z-Wave color changing light."""
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
"""Initialize the light."""
|
||||||
|
self._value_color = None
|
||||||
|
self._value_color_channels = None
|
||||||
|
self._color_channels = None
|
||||||
|
self._rgb = None
|
||||||
|
self._ct = None
|
||||||
|
|
||||||
|
# Here we attempt to find a zwave color value with the same instance
|
||||||
|
# id as the dimmer value. Currently zwave nodes that change colors
|
||||||
|
# only include one dimmer and one color command, but this will
|
||||||
|
# hopefully provide some forward compatibility for new devices that
|
||||||
|
# have multiple color changing elements.
|
||||||
|
for value_color in value.node.get_rgbbulbs().values():
|
||||||
|
if value.instance == value_color.instance:
|
||||||
|
self._value_color = value_color
|
||||||
|
|
||||||
|
if self._value_color is None:
|
||||||
|
raise ValueError("No matching color command found.")
|
||||||
|
|
||||||
|
for value_color_channels in value.node.get_values(
|
||||||
|
class_id=zwave.COMMAND_CLASS_COLOR, genre='System',
|
||||||
|
type="Int").values():
|
||||||
|
self._value_color_channels = value_color_channels
|
||||||
|
|
||||||
|
if self._value_color_channels is None:
|
||||||
|
raise ValueError("Color Channels not found.")
|
||||||
|
|
||||||
|
super().__init__(value)
|
||||||
|
|
||||||
|
def update_properties(self):
|
||||||
|
"""Update internal properties based on zwave values."""
|
||||||
|
super().update_properties()
|
||||||
|
|
||||||
|
# Color Channels
|
||||||
|
self._color_channels = self._value_color_channels.data
|
||||||
|
|
||||||
|
# Color Data String
|
||||||
|
data = self._value_color.data
|
||||||
|
|
||||||
|
# RGB is always present in the openzwave color data string.
|
||||||
|
self._rgb = [
|
||||||
|
int(data[1:3], 16),
|
||||||
|
int(data[3:5], 16),
|
||||||
|
int(data[5:7], 16)]
|
||||||
|
|
||||||
|
# Parse remaining color channels. Openzwave appends white channels
|
||||||
|
# that are present.
|
||||||
|
index = 7
|
||||||
|
|
||||||
|
# Warm white
|
||||||
|
if self._color_channels & COLOR_CHANNEL_WARM_WHITE:
|
||||||
|
warm_white = int(data[index:index+2], 16)
|
||||||
|
index += 2
|
||||||
|
else:
|
||||||
|
warm_white = 0
|
||||||
|
|
||||||
|
# Cold white
|
||||||
|
if self._color_channels & COLOR_CHANNEL_COLD_WHITE:
|
||||||
|
cold_white = int(data[index:index+2], 16)
|
||||||
|
index += 2
|
||||||
|
else:
|
||||||
|
cold_white = 0
|
||||||
|
|
||||||
|
# Color temperature. With two white channels, only two color
|
||||||
|
# temperatures are supported for the bulb. The channel values
|
||||||
|
# indicate brightness for warm/cold color temperature.
|
||||||
|
if (self._color_channels & COLOR_CHANNEL_WARM_WHITE and
|
||||||
|
self._color_channels & COLOR_CHANNEL_COLD_WHITE):
|
||||||
|
if warm_white > 0:
|
||||||
|
self._ct = TEMP_WARM_HASS
|
||||||
|
self._rgb = ct_to_rgb(self._ct)
|
||||||
|
elif cold_white > 0:
|
||||||
|
self._ct = TEMP_COLD_HASS
|
||||||
|
self._rgb = ct_to_rgb(self._ct)
|
||||||
|
else:
|
||||||
|
# RGB color is being used. Just report midpoint.
|
||||||
|
self._ct = TEMP_MID_HASS
|
||||||
|
|
||||||
|
# If only warm white is reported 0-255 is color temperature.
|
||||||
|
elif self._color_channels & COLOR_CHANNEL_WARM_WHITE:
|
||||||
|
self._ct = HASS_COLOR_MIN + (HASS_COLOR_MAX - HASS_COLOR_MIN) * (
|
||||||
|
warm_white / 255)
|
||||||
|
self._rgb = ct_to_rgb(self._ct)
|
||||||
|
|
||||||
|
# If only cold white is reported 0-255 is negative color temperature.
|
||||||
|
elif self._color_channels & COLOR_CHANNEL_COLD_WHITE:
|
||||||
|
self._ct = HASS_COLOR_MIN + (HASS_COLOR_MAX - HASS_COLOR_MIN) * (
|
||||||
|
(255 - cold_white) / 255)
|
||||||
|
self._rgb = ct_to_rgb(self._ct)
|
||||||
|
|
||||||
|
# If no rgb channels supported, report None.
|
||||||
|
if not (self._color_channels & COLOR_CHANNEL_RED or
|
||||||
|
self._color_channels & COLOR_CHANNEL_GREEN or
|
||||||
|
self._color_channels & COLOR_CHANNEL_BLUE):
|
||||||
|
self._rgb = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rgb_color(self):
|
||||||
|
"""Return the rgb color."""
|
||||||
|
return self._rgb
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color_temp(self):
|
||||||
|
"""Return the color temperature."""
|
||||||
|
return self._ct
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
"""Turn the device on."""
|
||||||
|
rgbw = None
|
||||||
|
|
||||||
|
if ATTR_COLOR_TEMP in kwargs:
|
||||||
|
# With two white channels, only two color temperatures are
|
||||||
|
# supported for the bulb.
|
||||||
|
if (self._color_channels & COLOR_CHANNEL_WARM_WHITE and
|
||||||
|
self._color_channels & COLOR_CHANNEL_COLD_WHITE):
|
||||||
|
if kwargs[ATTR_COLOR_TEMP] > TEMP_MID_HASS:
|
||||||
|
self._ct = TEMP_WARM_HASS
|
||||||
|
rgbw = b'#000000FF00'
|
||||||
|
else:
|
||||||
|
self._ct = TEMP_COLD_HASS
|
||||||
|
rgbw = b'#00000000FF'
|
||||||
|
|
||||||
|
# If only warm white is reported 0-255 is color temperature
|
||||||
|
elif self._color_channels & COLOR_CHANNEL_WARM_WHITE:
|
||||||
|
rgbw = b'#000000'
|
||||||
|
temp = (
|
||||||
|
(kwargs[ATTR_COLOR_TEMP] - HASS_COLOR_MIN) /
|
||||||
|
(HASS_COLOR_MAX - HASS_COLOR_MIN) * 255)
|
||||||
|
rgbw += format(int(temp)).encode('utf-8')
|
||||||
|
|
||||||
|
# If only cold white is reported 0-255 is negative color temp
|
||||||
|
elif self._color_channels & COLOR_CHANNEL_COLD_WHITE:
|
||||||
|
rgbw = b'#000000'
|
||||||
|
temp = (
|
||||||
|
255 - (kwargs[ATTR_COLOR_TEMP] - HASS_COLOR_MIN) /
|
||||||
|
(HASS_COLOR_MAX - HASS_COLOR_MIN) * 255)
|
||||||
|
rgbw += format(int(temp)).encode('utf-8')
|
||||||
|
|
||||||
|
elif ATTR_RGB_COLOR in kwargs:
|
||||||
|
self._rgb = kwargs[ATTR_RGB_COLOR]
|
||||||
|
|
||||||
|
rgbw = b'#'
|
||||||
|
for colorval in self._rgb:
|
||||||
|
rgbw += format(colorval, '02x').encode('utf-8')
|
||||||
|
rgbw += b'0000'
|
||||||
|
|
||||||
|
if rgbw is None:
|
||||||
|
_LOGGER.warning("rgbw string was not generated for turn_on")
|
||||||
|
else:
|
||||||
|
self._value_color.node.set_rgbw(self._value_color.value_id, rgbw)
|
||||||
|
|
||||||
|
super().turn_on(**kwargs)
|
||||||
|
65
homeassistant/components/lock/vera.py
Normal file
65
homeassistant/components/lock/vera.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
"""
|
||||||
|
Support for Vera locks.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/lock.vera/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.lock import LockDevice
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_BATTERY_LEVEL, STATE_LOCKED, STATE_UNLOCKED)
|
||||||
|
from homeassistant.components.vera import (
|
||||||
|
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
|
||||||
|
|
||||||
|
DEPENDENCIES = ['vera']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
"""Find and return Vera locks."""
|
||||||
|
add_devices_callback(
|
||||||
|
VeraLock(device, VERA_CONTROLLER) for
|
||||||
|
device in VERA_DEVICES['lock'])
|
||||||
|
|
||||||
|
|
||||||
|
class VeraLock(VeraDevice, LockDevice):
|
||||||
|
"""Representation of a Vera lock."""
|
||||||
|
|
||||||
|
def __init__(self, vera_device, controller):
|
||||||
|
"""Initialize the Vera device."""
|
||||||
|
self._state = None
|
||||||
|
VeraDevice.__init__(self, vera_device, controller)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return the state attributes of the device."""
|
||||||
|
attr = {}
|
||||||
|
if self.vera_device.has_battery:
|
||||||
|
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
|
||||||
|
|
||||||
|
attr['Vera Device Id'] = self.vera_device.vera_device_id
|
||||||
|
return attr
|
||||||
|
|
||||||
|
def lock(self, **kwargs):
|
||||||
|
"""Lock the device."""
|
||||||
|
self.vera_device.lock()
|
||||||
|
self._state = STATE_LOCKED
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def unlock(self, **kwargs):
|
||||||
|
"""Unlock the device."""
|
||||||
|
self.vera_device.unlock()
|
||||||
|
self._state = STATE_UNLOCKED
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_locked(self):
|
||||||
|
"""Return true if device is on."""
|
||||||
|
return self._state == STATE_LOCKED
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Called by the Vera device callback to update state."""
|
||||||
|
self._state = (STATE_LOCKED if self.vera_device.is_locked(True)
|
||||||
|
else STATE_UNLOCKED)
|
@ -7,9 +7,10 @@ https://home-assistant.io/components/lock.wink/
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.lock import LockDevice
|
from homeassistant.components.lock import LockDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL
|
from homeassistant.components.wink import WinkDevice
|
||||||
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.7.7']
|
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@ -30,38 +31,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
add_devices(WinkLockDevice(lock) for lock in pywink.get_locks())
|
add_devices(WinkLockDevice(lock) for lock in pywink.get_locks())
|
||||||
|
|
||||||
|
|
||||||
class WinkLockDevice(LockDevice):
|
class WinkLockDevice(WinkDevice, LockDevice):
|
||||||
"""Representation of a Wink lock."""
|
"""Representation of a Wink lock."""
|
||||||
|
|
||||||
def __init__(self, wink):
|
def __init__(self, wink):
|
||||||
"""Initialize the lock."""
|
"""Initialize the lock."""
|
||||||
self.wink = wink
|
WinkDevice.__init__(self, wink)
|
||||||
self._battery = self.wink.battery_level
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the id of this wink lock."""
|
|
||||||
return "{}.{}".format(self.__class__, self.wink.device_id())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the lock if any."""
|
|
||||||
return self.wink.name()
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Update the state of the lock."""
|
|
||||||
self.wink.update_state()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_locked(self):
|
def is_locked(self):
|
||||||
"""Return true if device is locked."""
|
"""Return true if device is locked."""
|
||||||
return self.wink.state()
|
return self.wink.state()
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
"""True if connection == True."""
|
|
||||||
return self.wink.available
|
|
||||||
|
|
||||||
def lock(self, **kwargs):
|
def lock(self, **kwargs):
|
||||||
"""Lock the device."""
|
"""Lock the device."""
|
||||||
self.wink.set_state(True)
|
self.wink.set_state(True)
|
||||||
@ -69,16 +50,3 @@ class WinkLockDevice(LockDevice):
|
|||||||
def unlock(self, **kwargs):
|
def unlock(self, **kwargs):
|
||||||
"""Unlock the device."""
|
"""Unlock the device."""
|
||||||
self.wink.set_state(False)
|
self.wink.set_state(False)
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
"""Return the state attributes."""
|
|
||||||
if self._battery:
|
|
||||||
return {
|
|
||||||
ATTR_BATTERY_LEVEL: self._battery_level,
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _battery_level(self):
|
|
||||||
"""Return the battery level."""
|
|
||||||
return self.wink.battery_level * 100
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
Support for interface with a Sony Bravia TV.
|
Support for interface with a Sony Bravia TV.
|
||||||
|
|
||||||
By Antonio Parraga Navarro
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/media_player.braviatv/
|
||||||
dedicated to Isabel
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@ -38,6 +36,7 @@ SUPPORT_BRAVIA = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
|
|||||||
|
|
||||||
|
|
||||||
def _get_mac_address(ip_address):
|
def _get_mac_address(ip_address):
|
||||||
|
"""Get the MAC address of the device."""
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
pid = Popen(["arp", "-n", ip_address], stdout=PIPE)
|
pid = Popen(["arp", "-n", ip_address], stdout=PIPE)
|
||||||
@ -48,7 +47,7 @@ def _get_mac_address(ip_address):
|
|||||||
|
|
||||||
|
|
||||||
def _config_from_file(filename, config=None):
|
def _config_from_file(filename, config=None):
|
||||||
"""Small configuration file management function."""
|
"""Create the configuration from a file."""
|
||||||
if config:
|
if config:
|
||||||
# We're writing configuration
|
# We're writing configuration
|
||||||
bravia_config = _config_from_file(filename)
|
bravia_config = _config_from_file(filename)
|
||||||
@ -104,7 +103,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
|
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
def setup_bravia(config, pin, hass, add_devices_callback):
|
def setup_bravia(config, pin, hass, add_devices_callback):
|
||||||
"""Setup a sony bravia based on host parameter."""
|
"""Setup a Sony Bravia TV based on host parameter."""
|
||||||
host = config.get(CONF_HOST)
|
host = config.get(CONF_HOST)
|
||||||
name = config.get(CONF_NAME)
|
name = config.get(CONF_NAME)
|
||||||
if name is None:
|
if name is None:
|
||||||
@ -176,7 +175,7 @@ class BraviaTVDevice(MediaPlayerDevice):
|
|||||||
"""Representation of a Sony Bravia TV."""
|
"""Representation of a Sony Bravia TV."""
|
||||||
|
|
||||||
def __init__(self, host, mac, name, pin):
|
def __init__(self, host, mac, name, pin):
|
||||||
"""Initialize the sony bravia device."""
|
"""Initialize the Sony Bravia device."""
|
||||||
from braviarc import braviarc
|
from braviarc import braviarc
|
||||||
|
|
||||||
self._pin = pin
|
self._pin = pin
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
Support for interacting with and controlling the cmus music player.
|
Support for interacting with and controlling the cmus music player.
|
||||||
|
|
||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/media_player.mpd/
|
https://home-assistant.io/components/media_player.cmus/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
@ -17,7 +16,7 @@ from homeassistant.const import (STATE_OFF, STATE_PAUSED, STATE_PLAYING,
|
|||||||
CONF_PORT)
|
CONF_PORT)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['pycmus>=0.1.0']
|
REQUIREMENTS = ['pycmus==0.1.0']
|
||||||
|
|
||||||
SUPPORT_CMUS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
|
SUPPORT_CMUS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
|
||||||
SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
|
SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
|
||||||
@ -25,7 +24,7 @@ SUPPORT_CMUS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discover_info=None):
|
def setup_platform(hass, config, add_devices, discover_info=None):
|
||||||
"""Setup the Cmus platform."""
|
"""Setup the CMUS platform."""
|
||||||
from pycmus import exceptions
|
from pycmus import exceptions
|
||||||
|
|
||||||
host = config.get(CONF_HOST, None)
|
host = config.get(CONF_HOST, None)
|
||||||
@ -44,7 +43,7 @@ def setup_platform(hass, config, add_devices, discover_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class CmusDevice(MediaPlayerDevice):
|
class CmusDevice(MediaPlayerDevice):
|
||||||
"""Representation of a running cmus."""
|
"""Representation of a running CMUS."""
|
||||||
|
|
||||||
# pylint: disable=no-member, too-many-public-methods, abstract-method
|
# pylint: disable=no-member, too-many-public-methods, abstract-method
|
||||||
def __init__(self, server, password, port, name):
|
def __init__(self, server, password, port, name):
|
||||||
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
|||||||
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
|
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['websocket-client==0.35.0']
|
REQUIREMENTS = ['websocket-client==0.37.0']
|
||||||
SUPPORT_GPMDP = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
SUPPORT_GPMDP = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
|||||||
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
|
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['jsonrpc-requests==0.2']
|
REQUIREMENTS = ['jsonrpc-requests==0.3']
|
||||||
|
|
||||||
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \
|
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \
|
||||||
|
@ -4,7 +4,6 @@ Support for the roku media player.
|
|||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/media_player.roku/
|
https://home-assistant.io/components/media_player.roku/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
@ -77,7 +76,8 @@ class RokuDevice(MediaPlayerDevice):
|
|||||||
self.current_app = self.roku.current_app
|
self.current_app = self.roku.current_app
|
||||||
else:
|
else:
|
||||||
self.current_app = None
|
self.current_app = None
|
||||||
except requests.exceptions.ConnectionError:
|
except (requests.exceptions.ConnectionError,
|
||||||
|
requests.exceptions.ReadTimeout):
|
||||||
self.current_app = None
|
self.current_app = None
|
||||||
|
|
||||||
def get_source_list(self):
|
def get_source_list(self):
|
||||||
|
@ -154,12 +154,20 @@ sonos_group_players:
|
|||||||
description: Name(s) of entites that will coordinate the grouping. Platform dependent.
|
description: Name(s) of entites that will coordinate the grouping. Platform dependent.
|
||||||
example: 'media_player.living_room_sonos'
|
example: 'media_player.living_room_sonos'
|
||||||
|
|
||||||
|
sonos_unjoin:
|
||||||
|
description: Unjoin the player from a group.
|
||||||
|
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name(s) of entites that will be unjoined from their group. Platform dependent.
|
||||||
|
example: 'media_player.living_room_sonos'
|
||||||
|
|
||||||
sonos_snapshot:
|
sonos_snapshot:
|
||||||
description: Take a snapshot of the media player.
|
description: Take a snapshot of the media player.
|
||||||
|
|
||||||
fields:
|
fields:
|
||||||
entity_id:
|
entity_id:
|
||||||
description: Name(s) of entites that will coordinate the grouping. Platform dependent.
|
description: Name(s) of entites that will be snapshot. Platform dependent.
|
||||||
example: 'media_player.living_room_sonos'
|
example: 'media_player.living_room_sonos'
|
||||||
|
|
||||||
sonos_restore:
|
sonos_restore:
|
||||||
@ -167,5 +175,5 @@ sonos_restore:
|
|||||||
|
|
||||||
fields:
|
fields:
|
||||||
entity_id:
|
entity_id:
|
||||||
description: Name(s) of entites that will coordinate the grouping. Platform dependent.
|
description: Name(s) of entites that will be restored. Platform dependent.
|
||||||
example: 'media_player.living_room_sonos'
|
example: 'media_player.living_room_sonos'
|
||||||
|
@ -4,7 +4,6 @@ Support for interacting with Snapcast clients.
|
|||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/media_player.snapcast/
|
https://home-assistant.io/components/media_player.snapcast/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
@ -34,11 +34,12 @@ SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
|
|||||||
SUPPORT_SEEK
|
SUPPORT_SEEK
|
||||||
|
|
||||||
SERVICE_GROUP_PLAYERS = 'sonos_group_players'
|
SERVICE_GROUP_PLAYERS = 'sonos_group_players'
|
||||||
|
SERVICE_UNJOIN = 'sonos_unjoin'
|
||||||
SERVICE_SNAPSHOT = 'sonos_snapshot'
|
SERVICE_SNAPSHOT = 'sonos_snapshot'
|
||||||
SERVICE_RESTORE = 'sonos_restore'
|
SERVICE_RESTORE = 'sonos_restore'
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument, too-many-locals
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Setup the Sonos platform."""
|
"""Setup the Sonos platform."""
|
||||||
import soco
|
import soco
|
||||||
@ -72,47 +73,35 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
add_devices(devices)
|
add_devices(devices)
|
||||||
_LOGGER.info('Added %s Sonos speakers', len(players))
|
_LOGGER.info('Added %s Sonos speakers', len(players))
|
||||||
|
|
||||||
|
def _apply_service(service, service_func, *service_func_args):
|
||||||
|
"""Internal func for applying a service."""
|
||||||
|
entity_id = service.data.get('entity_id')
|
||||||
|
|
||||||
|
if entity_id:
|
||||||
|
_devices = [device for device in devices
|
||||||
|
if device.entity_id == entity_id]
|
||||||
|
else:
|
||||||
|
_devices = devices
|
||||||
|
|
||||||
|
for device in _devices:
|
||||||
|
service_func(device, *service_func_args)
|
||||||
|
device.update_ha_state(True)
|
||||||
|
|
||||||
def group_players_service(service):
|
def group_players_service(service):
|
||||||
"""Group media players, use player as coordinator."""
|
"""Group media players, use player as coordinator."""
|
||||||
entity_id = service.data.get('entity_id')
|
_apply_service(service, SonosDevice.group_players)
|
||||||
|
|
||||||
if entity_id:
|
def unjoin_service(service):
|
||||||
_devices = [device for device in devices
|
"""Unjoin the player from a group."""
|
||||||
if device.entity_id == entity_id]
|
_apply_service(service, SonosDevice.unjoin)
|
||||||
else:
|
|
||||||
_devices = devices
|
|
||||||
|
|
||||||
for device in _devices:
|
def snapshot_service(service):
|
||||||
device.group_players()
|
|
||||||
device.update_ha_state(True)
|
|
||||||
|
|
||||||
def snapshot(service):
|
|
||||||
"""Take a snapshot."""
|
"""Take a snapshot."""
|
||||||
entity_id = service.data.get('entity_id')
|
_apply_service(service, SonosDevice.snapshot)
|
||||||
|
|
||||||
if entity_id:
|
def restore_service(service):
|
||||||
_devices = [device for device in devices
|
|
||||||
if device.entity_id == entity_id]
|
|
||||||
else:
|
|
||||||
_devices = devices
|
|
||||||
|
|
||||||
for device in _devices:
|
|
||||||
device.snapshot(service)
|
|
||||||
device.update_ha_state(True)
|
|
||||||
|
|
||||||
def restore(service):
|
|
||||||
"""Restore a snapshot."""
|
"""Restore a snapshot."""
|
||||||
entity_id = service.data.get('entity_id')
|
_apply_service(service, SonosDevice.restore)
|
||||||
|
|
||||||
if entity_id:
|
|
||||||
_devices = [device for device in devices
|
|
||||||
if device.entity_id == entity_id]
|
|
||||||
else:
|
|
||||||
_devices = devices
|
|
||||||
|
|
||||||
for device in _devices:
|
|
||||||
device.restore(service)
|
|
||||||
device.update_ha_state(True)
|
|
||||||
|
|
||||||
descriptions = load_yaml_config_file(
|
descriptions = load_yaml_config_file(
|
||||||
path.join(path.dirname(__file__), 'services.yaml'))
|
path.join(path.dirname(__file__), 'services.yaml'))
|
||||||
@ -121,12 +110,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
group_players_service,
|
group_players_service,
|
||||||
descriptions.get(SERVICE_GROUP_PLAYERS))
|
descriptions.get(SERVICE_GROUP_PLAYERS))
|
||||||
|
|
||||||
|
hass.services.register(DOMAIN, SERVICE_UNJOIN,
|
||||||
|
unjoin_service,
|
||||||
|
descriptions.get(SERVICE_UNJOIN))
|
||||||
|
|
||||||
hass.services.register(DOMAIN, SERVICE_SNAPSHOT,
|
hass.services.register(DOMAIN, SERVICE_SNAPSHOT,
|
||||||
snapshot,
|
snapshot_service,
|
||||||
descriptions.get(SERVICE_SNAPSHOT))
|
descriptions.get(SERVICE_SNAPSHOT))
|
||||||
|
|
||||||
hass.services.register(DOMAIN, SERVICE_RESTORE,
|
hass.services.register(DOMAIN, SERVICE_RESTORE,
|
||||||
restore,
|
restore_service,
|
||||||
descriptions.get(SERVICE_RESTORE))
|
descriptions.get(SERVICE_RESTORE))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -356,12 +349,17 @@ class SonosDevice(MediaPlayerDevice):
|
|||||||
self._player.partymode()
|
self._player.partymode()
|
||||||
|
|
||||||
@only_if_coordinator
|
@only_if_coordinator
|
||||||
def snapshot(self, service):
|
def unjoin(self):
|
||||||
|
"""Unjoin the player from a group."""
|
||||||
|
self._player.unjoin()
|
||||||
|
|
||||||
|
@only_if_coordinator
|
||||||
|
def snapshot(self):
|
||||||
"""Snapshot the player."""
|
"""Snapshot the player."""
|
||||||
self.soco_snapshot.snapshot()
|
self.soco_snapshot.snapshot()
|
||||||
|
|
||||||
@only_if_coordinator
|
@only_if_coordinator
|
||||||
def restore(self, service):
|
def restore(self):
|
||||||
"""Restore snapshot for the player."""
|
"""Restore snapshot for the player."""
|
||||||
self.soco_snapshot.restore(True)
|
self.soco_snapshot.restore(True)
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ Combination of multiple media players into one for a universal controller.
|
|||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/media_player.universal/
|
https://home-assistant.io/components/media_player.universal/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
# pylint: disable=import-error
|
# pylint: disable=import-error
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
@ -6,13 +6,6 @@ https://home-assistant.io/components/rollershutter.homematic/
|
|||||||
|
|
||||||
Important: For this platform to work the homematic component has to be
|
Important: For this platform to work the homematic component has to be
|
||||||
properly configured.
|
properly configured.
|
||||||
|
|
||||||
Configuration:
|
|
||||||
|
|
||||||
rollershutter:
|
|
||||||
- platform: homematic
|
|
||||||
address: "<Homematic address for device>" # e.g. "JEQ0XXXXXXX"
|
|
||||||
name: "<User defined name>" (optional)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -29,14 +22,12 @@ DEPENDENCIES = ['homematic']
|
|||||||
|
|
||||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||||
"""Setup the platform."""
|
"""Setup the platform."""
|
||||||
if discovery_info:
|
if discovery_info is None:
|
||||||
return homematic.setup_hmdevice_discovery_helper(HMRollershutter,
|
return
|
||||||
discovery_info,
|
|
||||||
add_callback_devices)
|
return homematic.setup_hmdevice_discovery_helper(HMRollershutter,
|
||||||
# Manual
|
discovery_info,
|
||||||
return homematic.setup_hmdevice_entity_helper(HMRollershutter,
|
add_callback_devices)
|
||||||
config,
|
|
||||||
add_callback_devices)
|
|
||||||
|
|
||||||
|
|
||||||
class HMRollershutter(homematic.HMDevice, RollershutterDevice):
|
class HMRollershutter(homematic.HMDevice, RollershutterDevice):
|
||||||
|
@ -7,9 +7,10 @@ https://home-assistant.io/components/rollershutter.wink/
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.rollershutter import RollershutterDevice
|
from homeassistant.components.rollershutter import RollershutterDevice
|
||||||
|
from homeassistant.components.wink import WinkDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.7.7']
|
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@ -31,38 +32,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
pywink.get_shades())
|
pywink.get_shades())
|
||||||
|
|
||||||
|
|
||||||
class WinkRollershutterDevice(RollershutterDevice):
|
class WinkRollershutterDevice(WinkDevice, RollershutterDevice):
|
||||||
"""Representation of a Wink rollershutter (shades)."""
|
"""Representation of a Wink rollershutter (shades)."""
|
||||||
|
|
||||||
def __init__(self, wink):
|
def __init__(self, wink):
|
||||||
"""Initialize the rollershutter."""
|
"""Initialize the rollershutter."""
|
||||||
self.wink = wink
|
WinkDevice.__init__(self, wink)
|
||||||
self._battery = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""Wink Shades don't track their position."""
|
"""Wink Shades don't track their position."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the ID of this wink rollershutter."""
|
|
||||||
return "{}.{}".format(self.__class__, self.wink.device_id())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the rollershutter if any."""
|
|
||||||
return self.wink.name()
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Update the state of the rollershutter."""
|
|
||||||
return self.wink.update_state()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
"""True if connection == True."""
|
|
||||||
return self.wink.available
|
|
||||||
|
|
||||||
def move_down(self):
|
def move_down(self):
|
||||||
"""Close the shade."""
|
"""Close the shade."""
|
||||||
self.wink.set_state(0)
|
self.wink.set_state(0)
|
||||||
|
@ -1,38 +1,43 @@
|
|||||||
"""
|
"""
|
||||||
Support for information about the German trans system.
|
Support for information about the German train system.
|
||||||
|
|
||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/sensor.deutsche_bahn/
|
https://home-assistant.io/components/sensor.deutsche_bahn/
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import (CONF_PLATFORM)
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
REQUIREMENTS = ['schiene==0.17']
|
REQUIREMENTS = ['schiene==0.17']
|
||||||
|
|
||||||
|
CONF_START = 'from'
|
||||||
|
CONF_DESTINATION = 'to'
|
||||||
ICON = 'mdi:train'
|
ICON = 'mdi:train'
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_PLATFORM): 'deutsche_bahn',
|
||||||
|
vol.Required(CONF_START): cv.string,
|
||||||
|
vol.Required(CONF_DESTINATION): cv.string,
|
||||||
|
})
|
||||||
|
|
||||||
# Return cached results if last scan was less then this time ago.
|
# Return cached results if last scan was less then this time ago.
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Setup the Deutsche Bahn Sensor."""
|
"""Setup the Deutsche Bahn Sensor."""
|
||||||
start = config.get('from')
|
start = config.get(CONF_START)
|
||||||
goal = config.get('to')
|
destination = config.get(CONF_DESTINATION)
|
||||||
|
|
||||||
if start is None:
|
add_devices([DeutscheBahnSensor(start, destination)])
|
||||||
_LOGGER.error('Missing required variable: "from"')
|
|
||||||
return False
|
|
||||||
|
|
||||||
if goal is None:
|
|
||||||
_LOGGER.error('Missing required variable: "to"')
|
|
||||||
return False
|
|
||||||
|
|
||||||
dev = []
|
|
||||||
dev.append(DeutscheBahnSensor(start, goal))
|
|
||||||
add_devices_callback(dev)
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
@ -63,16 +68,17 @@ class DeutscheBahnSensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
return self.data.connections[0]
|
connections = self.data.connections[0]
|
||||||
|
connections['next'] = self.data.connections[1]['departure']
|
||||||
|
connections['next_on'] = self.data.connections[2]['departure']
|
||||||
|
return connections
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Get the latest delay from bahn.de and updates the state."""
|
"""Get the latest delay from bahn.de and updates the state."""
|
||||||
self.data.update()
|
self.data.update()
|
||||||
self._state = self.data.connections[0].get('departure', 'Unknown')
|
self._state = self.data.connections[0].get('departure', 'Unknown')
|
||||||
if self.data.connections[0]['delay'] != 0:
|
if self.data.connections[0]['delay'] != 0:
|
||||||
self._state += " + {}".format(
|
self._state += " + {}".format(self.data.connections[0]['delay'])
|
||||||
self.data.connections[0]['delay']
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
@ -90,18 +96,15 @@ class SchieneData(object):
|
|||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update the connection data."""
|
"""Update the connection data."""
|
||||||
self.connections = self.schiene.connections(self.start,
|
self.connections = self.schiene.connections(self.start, self.goal)
|
||||||
self.goal,
|
|
||||||
datetime.now())
|
|
||||||
for con in self.connections:
|
for con in self.connections:
|
||||||
# Details info is not useful.
|
# Detail info is not useful. Having a more consistent interface
|
||||||
# Having a more consistent interface simplifies
|
# simplifies usage of template sensors.
|
||||||
# usage of Template sensors later on
|
|
||||||
if 'details' in con:
|
if 'details' in con:
|
||||||
con.pop('details')
|
con.pop('details')
|
||||||
delay = con.get('delay',
|
delay = con.get('delay', {'delay_departure': 0,
|
||||||
{'delay_departure': 0,
|
'delay_arrival': 0})
|
||||||
'delay_arrival': 0})
|
# IMHO only delay_departure is useful
|
||||||
# IMHO only delay_departure is usefull
|
|
||||||
con['delay'] = delay['delay_departure']
|
con['delay'] = delay['delay_departure']
|
||||||
con['ontime'] = con.get('ontime', False)
|
con['ontime'] = con.get('ontime', False)
|
||||||
|
@ -6,14 +6,6 @@ https://home-assistant.io/components/sensor.homematic/
|
|||||||
|
|
||||||
Important: For this platform to work the homematic component has to be
|
Important: For this platform to work the homematic component has to be
|
||||||
properly configured.
|
properly configured.
|
||||||
|
|
||||||
Configuration:
|
|
||||||
|
|
||||||
sensor:
|
|
||||||
- platform: homematic
|
|
||||||
address: <Homematic address for device> # e.g. "JEQ0XXXXXXX"
|
|
||||||
name: <User defined name> (optional)
|
|
||||||
param: <Name of datapoint to us as sensor> (optional)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -41,14 +33,12 @@ HM_UNIT_HA_CAST = {
|
|||||||
|
|
||||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||||
"""Setup the platform."""
|
"""Setup the platform."""
|
||||||
if discovery_info:
|
if discovery_info is None:
|
||||||
return homematic.setup_hmdevice_discovery_helper(HMSensor,
|
return
|
||||||
discovery_info,
|
|
||||||
add_callback_devices)
|
return homematic.setup_hmdevice_discovery_helper(HMSensor,
|
||||||
# Manual
|
discovery_info,
|
||||||
return homematic.setup_hmdevice_entity_helper(HMSensor,
|
add_callback_devices)
|
||||||
config,
|
|
||||||
add_callback_devices)
|
|
||||||
|
|
||||||
|
|
||||||
class HMSensor(homematic.HMDevice):
|
class HMSensor(homematic.HMDevice):
|
||||||
|
@ -12,21 +12,24 @@ from glob import glob
|
|||||||
from homeassistant.const import STATE_UNKNOWN, TEMP_CELSIUS
|
from homeassistant.const import STATE_UNKNOWN, TEMP_CELSIUS
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
BASE_DIR = '/sys/bus/w1/devices/'
|
|
||||||
DEVICE_FOLDERS = glob(os.path.join(BASE_DIR, '28*'))
|
|
||||||
SENSOR_IDS = []
|
|
||||||
DEVICE_FILES = []
|
|
||||||
for device_folder in DEVICE_FOLDERS:
|
|
||||||
SENSOR_IDS.append(os.path.split(device_folder)[1])
|
|
||||||
DEVICE_FILES.append(os.path.join(device_folder, 'w1_slave'))
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# 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):
|
||||||
"""Setup the one wire Sensors."""
|
"""Setup the one wire Sensors."""
|
||||||
if DEVICE_FILES == []:
|
base_dir = config.get('mount_dir', '/sys/bus/w1/devices/')
|
||||||
|
device_folders = glob(os.path.join(base_dir, '[10,22,28,3B,42]*'))
|
||||||
|
sensor_ids = []
|
||||||
|
device_files = []
|
||||||
|
for device_folder in device_folders:
|
||||||
|
sensor_ids.append(os.path.split(device_folder)[1])
|
||||||
|
if base_dir.startswith('/sys/bus/w1/devices'):
|
||||||
|
device_files.append(os.path.join(device_folder, 'w1_slave'))
|
||||||
|
else:
|
||||||
|
device_files.append(os.path.join(device_folder, 'temperature'))
|
||||||
|
|
||||||
|
if device_files == []:
|
||||||
_LOGGER.error('No onewire sensor found.')
|
_LOGGER.error('No onewire sensor found.')
|
||||||
_LOGGER.error('Check if dtoverlay=w1-gpio,gpiopin=4.')
|
_LOGGER.error('Check if dtoverlay=w1-gpio,gpiopin=4.')
|
||||||
_LOGGER.error('is in your /boot/config.txt and')
|
_LOGGER.error('is in your /boot/config.txt and')
|
||||||
@ -34,7 +37,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
return
|
return
|
||||||
|
|
||||||
devs = []
|
devs = []
|
||||||
names = SENSOR_IDS
|
names = sensor_ids
|
||||||
|
|
||||||
for key in config.keys():
|
for key in config.keys():
|
||||||
if key == "names":
|
if key == "names":
|
||||||
@ -47,9 +50,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
# map names to ids.
|
# map names to ids.
|
||||||
elif isinstance(config['names'], dict):
|
elif isinstance(config['names'], dict):
|
||||||
names = []
|
names = []
|
||||||
for sensor_id in SENSOR_IDS:
|
for sensor_id in sensor_ids:
|
||||||
names.append(config['names'].get(sensor_id, sensor_id))
|
names.append(config['names'].get(sensor_id, sensor_id))
|
||||||
for device_file, name in zip(DEVICE_FILES, names):
|
for device_file, name in zip(device_files, names):
|
||||||
devs.append(OneWire(name, device_file))
|
devs.append(OneWire(name, device_file))
|
||||||
add_devices(devs)
|
add_devices(devs)
|
||||||
|
|
||||||
@ -88,14 +91,27 @@ class OneWire(Entity):
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Get the latest data from the device."""
|
"""Get the latest data from the device."""
|
||||||
lines = self._read_temp_raw()
|
temp = -99
|
||||||
while lines[0].strip()[-3:] != 'YES':
|
if self._device_file.startswith('/sys/bus/w1/devices'):
|
||||||
time.sleep(0.2)
|
|
||||||
lines = self._read_temp_raw()
|
lines = self._read_temp_raw()
|
||||||
equals_pos = lines[1].find('t=')
|
while lines[0].strip()[-3:] != 'YES':
|
||||||
if equals_pos != -1:
|
time.sleep(0.2)
|
||||||
temp_string = lines[1][equals_pos+2:]
|
lines = self._read_temp_raw()
|
||||||
temp = round(float(temp_string) / 1000.0, 1)
|
equals_pos = lines[1].find('t=')
|
||||||
if temp < -55 or temp > 125:
|
if equals_pos != -1:
|
||||||
return
|
temp_string = lines[1][equals_pos+2:]
|
||||||
self._state = temp
|
temp = round(float(temp_string) / 1000.0, 1)
|
||||||
|
else:
|
||||||
|
ds_device_file = open(self._device_file, 'r')
|
||||||
|
temp_read = ds_device_file.readlines()
|
||||||
|
ds_device_file.close()
|
||||||
|
if len(temp_read) == 1:
|
||||||
|
try:
|
||||||
|
temp = round(float(temp_read[0]), 1)
|
||||||
|
except ValueError:
|
||||||
|
_LOGGER.warning('Invalid temperature value read from ' +
|
||||||
|
self._device_file)
|
||||||
|
|
||||||
|
if temp < -55 or temp > 125:
|
||||||
|
return
|
||||||
|
self._state = temp
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
"""Support for openexchangerates.org exchange rates service."""
|
"""
|
||||||
|
Support for openexchangerates.org exchange rates service.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/sensor.openexchangerates/
|
||||||
|
"""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
@ -41,7 +46,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class OpenexchangeratesSensor(Entity):
|
class OpenexchangeratesSensor(Entity):
|
||||||
"""Implementing the Openexchangerates sensor."""
|
"""Representation of an Openexchangerates sensor."""
|
||||||
|
|
||||||
def __init__(self, rest, name, quote):
|
def __init__(self, rest, name, quote):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
@ -87,7 +92,7 @@ class OpenexchangeratesData(object):
|
|||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Get the latest data from openexchangerates."""
|
"""Get the latest data from openexchangerates.org."""
|
||||||
try:
|
try:
|
||||||
result = requests.get(self._resource, params={'base': self._base,
|
result = requests.get(self._resource, params={'base': self._base,
|
||||||
'app_id':
|
'app_id':
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
"""Support for ThinkingCleaner."""
|
"""
|
||||||
|
Support for ThinkingCleaner.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/sensor.thinkingcleaner/
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
@ -7,11 +7,12 @@ at https://home-assistant.io/components/sensor.wink/
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.const import (CONF_ACCESS_TOKEN, STATE_CLOSED,
|
from homeassistant.const import (CONF_ACCESS_TOKEN, STATE_CLOSED,
|
||||||
STATE_OPEN, TEMP_CELSIUS,
|
STATE_OPEN, TEMP_CELSIUS)
|
||||||
ATTR_BATTERY_LEVEL)
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.components.wink import WinkDevice
|
||||||
|
from homeassistant.loader import get_component
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.7.7']
|
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
|
||||||
|
|
||||||
SENSOR_TYPES = ['temperature', 'humidity']
|
SENSOR_TYPES = ['temperature', 'humidity']
|
||||||
|
|
||||||
@ -38,14 +39,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
add_devices(WinkEggMinder(eggtray) for eggtray in pywink.get_eggtrays())
|
add_devices(WinkEggMinder(eggtray) for eggtray in pywink.get_eggtrays())
|
||||||
|
|
||||||
|
|
||||||
class WinkSensorDevice(Entity):
|
class WinkSensorDevice(WinkDevice, Entity):
|
||||||
"""Representation of a Wink sensor."""
|
"""Representation of a Wink sensor."""
|
||||||
|
|
||||||
def __init__(self, wink):
|
def __init__(self, wink):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the Wink device."""
|
||||||
self.wink = wink
|
super().__init__(wink)
|
||||||
|
wink = get_component('wink')
|
||||||
self.capability = self.wink.capability()
|
self.capability = self.wink.capability()
|
||||||
self._battery = self.wink.battery_level
|
|
||||||
if self.wink.UNIT == "°":
|
if self.wink.UNIT == "°":
|
||||||
self._unit_of_measurement = TEMP_CELSIUS
|
self._unit_of_measurement = TEMP_CELSIUS
|
||||||
else:
|
else:
|
||||||
@ -55,9 +56,9 @@ class WinkSensorDevice(Entity):
|
|||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
if self.capability == "humidity":
|
if self.capability == "humidity":
|
||||||
return self.wink.humidity_percentage()
|
return round(self.wink.humidity_percentage())
|
||||||
elif self.capability == "temperature":
|
elif self.capability == "temperature":
|
||||||
return self.wink.temperature_float()
|
return round(self.wink.temperature_float(), 1)
|
||||||
else:
|
else:
|
||||||
return STATE_OPEN if self.is_open else STATE_CLOSED
|
return STATE_OPEN if self.is_open else STATE_CLOSED
|
||||||
|
|
||||||
@ -66,80 +67,20 @@ class WinkSensorDevice(Entity):
|
|||||||
"""Return the unit of measurement of this entity, if any."""
|
"""Return the unit of measurement of this entity, if any."""
|
||||||
return self._unit_of_measurement
|
return self._unit_of_measurement
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the ID of this wink sensor."""
|
|
||||||
return "{}.{}".format(self.__class__, self.wink.device_id())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the sensor if any."""
|
|
||||||
return self.wink.name()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
"""True if connection == True."""
|
|
||||||
return self.wink.available
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Update state of the sensor."""
|
|
||||||
self.wink.update_state()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_open(self):
|
def is_open(self):
|
||||||
"""Return true if door is open."""
|
"""Return true if door is open."""
|
||||||
return self.wink.state()
|
return self.wink.state()
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
"""Return the state attributes."""
|
|
||||||
if self._battery:
|
|
||||||
return {
|
|
||||||
ATTR_BATTERY_LEVEL: self._battery_level,
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
class WinkEggMinder(WinkDevice, Entity):
|
||||||
def _battery_level(self):
|
|
||||||
"""Return the battery level."""
|
|
||||||
return self.wink.battery_level * 100
|
|
||||||
|
|
||||||
|
|
||||||
class WinkEggMinder(Entity):
|
|
||||||
"""Representation of a Wink Egg Minder."""
|
"""Representation of a Wink Egg Minder."""
|
||||||
|
|
||||||
def __init__(self, wink):
|
def __init__(self, wink):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self.wink = wink
|
WinkDevice.__init__(self, wink)
|
||||||
self._battery = self.wink.battery_level
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
return self.wink.state()
|
return self.wink.state()
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the id of this wink Egg Minder."""
|
|
||||||
return "{}.{}".format(self.__class__, self.wink.device_id())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the Egg Minder if any."""
|
|
||||||
return self.wink.name()
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Update state of the Egg Minder."""
|
|
||||||
self.wink.update_state()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
"""Return the state attributes."""
|
|
||||||
if self._battery:
|
|
||||||
return {
|
|
||||||
ATTR_BATTERY_LEVEL: self._battery_level,
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _battery_level(self):
|
|
||||||
"""Return the battery level."""
|
|
||||||
return self.wink.battery_level * 100
|
|
||||||
|
@ -15,7 +15,6 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
from homeassistant.util import location
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -54,16 +53,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
"""Setup the Yr.no sensor."""
|
"""Setup the Yr.no sensor."""
|
||||||
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
||||||
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
||||||
elevation = config.get(CONF_ELEVATION)
|
elevation = config.get(CONF_ELEVATION, hass.config.elevation or 0)
|
||||||
|
|
||||||
if None in (latitude, longitude):
|
if None in (latitude, longitude):
|
||||||
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if elevation is None:
|
|
||||||
elevation = location.elevation(latitude,
|
|
||||||
longitude)
|
|
||||||
|
|
||||||
coordinates = dict(lat=latitude,
|
coordinates = dict(lat=latitude,
|
||||||
lon=longitude,
|
lon=longitude,
|
||||||
msl=elevation)
|
msl=elevation)
|
||||||
|
@ -12,7 +12,6 @@ from homeassistant.helpers.entity import Entity
|
|||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
track_point_in_utc_time, track_utc_time_change)
|
track_point_in_utc_time, track_utc_time_change)
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
from homeassistant.util import location as location_util
|
|
||||||
from homeassistant.const import CONF_ELEVATION
|
from homeassistant.const import CONF_ELEVATION
|
||||||
|
|
||||||
REQUIREMENTS = ['astral==1.2']
|
REQUIREMENTS = ['astral==1.2']
|
||||||
@ -108,7 +107,7 @@ def setup(hass, config):
|
|||||||
|
|
||||||
elevation = platform_config.get(CONF_ELEVATION)
|
elevation = platform_config.get(CONF_ELEVATION)
|
||||||
if elevation is None:
|
if elevation is None:
|
||||||
elevation = location_util.elevation(latitude, longitude)
|
elevation = hass.config.elevation or 0
|
||||||
|
|
||||||
from astral import Location
|
from astral import Location
|
||||||
|
|
||||||
|
@ -1,21 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
The homematic switch platform.
|
Support for Homematic switches.
|
||||||
|
|
||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/switch.homematic/
|
https://home-assistant.io/components/switch.homematic/
|
||||||
|
|
||||||
Important: For this platform to work the homematic component has to be
|
|
||||||
properly configured.
|
|
||||||
|
|
||||||
Configuration:
|
|
||||||
|
|
||||||
switch:
|
|
||||||
- platform: homematic
|
|
||||||
address: <Homematic address for device> # e.g. "JEQ0XXXXXXX"
|
|
||||||
name: <User defined name> (optional)
|
|
||||||
button: n (integer of channel to map, device-dependent) (optional)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchDevice
|
||||||
from homeassistant.const import STATE_UNKNOWN
|
from homeassistant.const import STATE_UNKNOWN
|
||||||
@ -27,19 +15,17 @@ DEPENDENCIES = ['homematic']
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||||
"""Setup the platform."""
|
"""Setup the Homematic switch platform."""
|
||||||
if discovery_info:
|
if discovery_info is None:
|
||||||
return homematic.setup_hmdevice_discovery_helper(HMSwitch,
|
return
|
||||||
discovery_info,
|
|
||||||
add_callback_devices)
|
return homematic.setup_hmdevice_discovery_helper(HMSwitch,
|
||||||
# Manual
|
discovery_info,
|
||||||
return homematic.setup_hmdevice_entity_helper(HMSwitch,
|
add_callback_devices)
|
||||||
config,
|
|
||||||
add_callback_devices)
|
|
||||||
|
|
||||||
|
|
||||||
class HMSwitch(homematic.HMDevice, SwitchDevice):
|
class HMSwitch(homematic.HMDevice, SwitchDevice):
|
||||||
"""Represents a Homematic Switch in Home Assistant."""
|
"""Representation of a Homematic switch."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
@ -71,24 +57,24 @@ class HMSwitch(homematic.HMDevice, SwitchDevice):
|
|||||||
self._hmdevice.off(self._channel)
|
self._hmdevice.off(self._channel)
|
||||||
|
|
||||||
def _check_hm_to_ha_object(self):
|
def _check_hm_to_ha_object(self):
|
||||||
"""Check if possible to use the HM Object as this HA type."""
|
"""Check if possible to use the Homematic object as this HA type."""
|
||||||
from pyhomematic.devicetypes.actors import Dimmer, Switch
|
from pyhomematic.devicetypes.actors import Dimmer, Switch
|
||||||
|
|
||||||
# Check compatibility from HMDevice
|
# Check compatibility from HMDevice
|
||||||
if not super()._check_hm_to_ha_object():
|
if not super()._check_hm_to_ha_object():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if the homematic device is correct for this HA device
|
# Check if the Homematic device is correct for this HA device
|
||||||
if isinstance(self._hmdevice, Switch):
|
if isinstance(self._hmdevice, Switch):
|
||||||
return True
|
return True
|
||||||
if isinstance(self._hmdevice, Dimmer):
|
if isinstance(self._hmdevice, Dimmer):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
_LOGGER.critical("This %s can't be use as switch!", self._name)
|
_LOGGER.critical("This %s can't be use as switch", self._name)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _init_data_struct(self):
|
def _init_data_struct(self):
|
||||||
"""Generate a data dict (self._data) from hm metadata."""
|
"""Generate a data dict (self._data) from the Homematic metadata."""
|
||||||
from pyhomematic.devicetypes.actors import Dimmer,\
|
from pyhomematic.devicetypes.actors import Dimmer,\
|
||||||
Switch, SwitchPowermeter
|
Switch, SwitchPowermeter
|
||||||
|
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
"""Support for ThinkingCleaner."""
|
"""
|
||||||
|
Support for ThinkingCleaner.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/switch.thinkingcleaner/
|
||||||
|
"""
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
@ -52,7 +52,7 @@ class WOLSwitch(SwitchDevice):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""True if switch is on."""
|
"""Return true if switch is on."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -6,10 +6,11 @@ https://home-assistant.io/components/switch.wink/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.wink import WinkToggleDevice
|
from homeassistant.components.wink import WinkDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
|
|
||||||
REQUIREMENTS = ['python-wink==0.7.7']
|
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@ -31,3 +32,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
add_devices(WinkToggleDevice(switch) for switch in
|
add_devices(WinkToggleDevice(switch) for switch in
|
||||||
pywink.get_powerstrip_outlets())
|
pywink.get_powerstrip_outlets())
|
||||||
add_devices(WinkToggleDevice(switch) for switch in pywink.get_sirens())
|
add_devices(WinkToggleDevice(switch) for switch in pywink.get_sirens())
|
||||||
|
|
||||||
|
|
||||||
|
class WinkToggleDevice(WinkDevice, ToggleEntity):
|
||||||
|
"""Represents a Wink toggle (switch) device."""
|
||||||
|
|
||||||
|
def __init__(self, wink):
|
||||||
|
"""Initialize the Wink device."""
|
||||||
|
WinkDevice.__init__(self, wink)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return true if device is on."""
|
||||||
|
return self.wink.state()
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
"""Turn the device on."""
|
||||||
|
self.wink.set_state(True)
|
||||||
|
|
||||||
|
def turn_off(self):
|
||||||
|
"""Turn the device off."""
|
||||||
|
self.wink.set_state(False)
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
"""
|
"""
|
||||||
Support for eq3 Bluetooth Smart thermostats.
|
Support for eq3 Bluetooth Smart thermostats.
|
||||||
|
|
||||||
Uses bluepy_devices library.
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/thermostat.eq3btsmart/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.thermostat import ThermostatDevice
|
from homeassistant.components.thermostat import ThermostatDevice
|
||||||
from homeassistant.const import TEMP_CELCIUS
|
from homeassistant.const import TEMP_CELCIUS
|
||||||
from homeassistant.helpers.temperature import convert
|
from homeassistant.helpers.temperature import convert
|
||||||
|
|
||||||
REQUIREMENTS = ['bluepy_devices>=0.2.0']
|
REQUIREMENTS = ['bluepy_devices==0.2.0']
|
||||||
|
|
||||||
CONF_MAC = 'mac'
|
CONF_MAC = 'mac'
|
||||||
CONF_DEVICES = 'devices'
|
CONF_DEVICES = 'devices'
|
||||||
|
@ -1,20 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
The Homematic thermostat platform.
|
Support for Homematic thermostats.
|
||||||
|
|
||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/thermostat.homematic/
|
https://home-assistant.io/components/thermostat.homematic/
|
||||||
|
|
||||||
Important: For this platform to work the homematic component has to be
|
|
||||||
properly configured.
|
|
||||||
|
|
||||||
Configuration:
|
|
||||||
|
|
||||||
thermostat:
|
|
||||||
- platform: homematic
|
|
||||||
address: "<Homematic address for device>" # e.g. "JEQ0XXXXXXX"
|
|
||||||
name: "<User defined name>" (optional)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import homeassistant.components.homematic as homematic
|
import homeassistant.components.homematic as homematic
|
||||||
from homeassistant.components.thermostat import ThermostatDevice
|
from homeassistant.components.thermostat import ThermostatDevice
|
||||||
@ -27,20 +16,18 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||||
"""Setup the platform."""
|
"""Setup the Homematic thermostat platform."""
|
||||||
if discovery_info:
|
if discovery_info is None:
|
||||||
return homematic.setup_hmdevice_discovery_helper(HMThermostat,
|
return
|
||||||
discovery_info,
|
|
||||||
add_callback_devices)
|
return homematic.setup_hmdevice_discovery_helper(HMThermostat,
|
||||||
# Manual
|
discovery_info,
|
||||||
return homematic.setup_hmdevice_entity_helper(HMThermostat,
|
add_callback_devices)
|
||||||
config,
|
|
||||||
add_callback_devices)
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=abstract-method
|
# pylint: disable=abstract-method
|
||||||
class HMThermostat(homematic.HMDevice, ThermostatDevice):
|
class HMThermostat(homematic.HMDevice, ThermostatDevice):
|
||||||
"""Represents a Homematic Thermostat in Home Assistant."""
|
"""Representation of a Homematic thermostat."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
@ -78,7 +65,7 @@ class HMThermostat(homematic.HMDevice, ThermostatDevice):
|
|||||||
return convert(30.5, TEMP_CELSIUS, self.unit_of_measurement)
|
return convert(30.5, TEMP_CELSIUS, self.unit_of_measurement)
|
||||||
|
|
||||||
def _check_hm_to_ha_object(self):
|
def _check_hm_to_ha_object(self):
|
||||||
"""Check if possible to use the HM Object as this HA type."""
|
"""Check if possible to use the Homematic object as this HA type."""
|
||||||
from pyhomematic.devicetypes.thermostats import HMThermostat\
|
from pyhomematic.devicetypes.thermostats import HMThermostat\
|
||||||
as pyHMThermostat
|
as pyHMThermostat
|
||||||
|
|
||||||
@ -86,7 +73,7 @@ class HMThermostat(homematic.HMDevice, ThermostatDevice):
|
|||||||
if not super()._check_hm_to_ha_object():
|
if not super()._check_hm_to_ha_object():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if the homematic device correct for this HA device
|
# Check if the Homematic device correct for this HA device
|
||||||
if isinstance(self._hmdevice, pyHMThermostat):
|
if isinstance(self._hmdevice, pyHMThermostat):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -94,7 +81,7 @@ class HMThermostat(homematic.HMDevice, ThermostatDevice):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def _init_data_struct(self):
|
def _init_data_struct(self):
|
||||||
"""Generate a data dict (self._data) from hm metadata."""
|
"""Generate a data dict (self._data) from the Homematic metadata."""
|
||||||
super()._init_data_struct()
|
super()._init_data_struct()
|
||||||
|
|
||||||
# Add state to data dict
|
# Add state to data dict
|
||||||
|
@ -13,12 +13,13 @@ from homeassistant.helpers import discovery
|
|||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
REQUIREMENTS = ['pyvera==0.2.10']
|
REQUIREMENTS = ['pyvera==0.2.12']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = 'vera'
|
DOMAIN = 'vera'
|
||||||
|
|
||||||
|
|
||||||
VERA_CONTROLLER = None
|
VERA_CONTROLLER = None
|
||||||
|
|
||||||
CONF_EXCLUDE = 'exclude'
|
CONF_EXCLUDE = 'exclude'
|
||||||
@ -33,6 +34,7 @@ DEVICE_CATEGORIES = {
|
|||||||
'Switch': 'switch',
|
'Switch': 'switch',
|
||||||
'Armable Sensor': 'switch',
|
'Armable Sensor': 'switch',
|
||||||
'On/Off Switch': 'switch',
|
'On/Off Switch': 'switch',
|
||||||
|
'Doorlock': 'lock',
|
||||||
# 'Window Covering': NOT SUPPORTED YET
|
# 'Window Covering': NOT SUPPORTED YET
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +93,7 @@ def setup(hass, base_config):
|
|||||||
dev_type = 'light'
|
dev_type = 'light'
|
||||||
VERA_DEVICES[dev_type].append(device)
|
VERA_DEVICES[dev_type].append(device)
|
||||||
|
|
||||||
for component in 'binary_sensor', 'sensor', 'light', 'switch':
|
for component in 'binary_sensor', 'sensor', 'light', 'switch', 'lock':
|
||||||
discovery.load_platform(hass, component, DOMAIN, {}, base_config)
|
discovery.load_platform(hass, component, DOMAIN, {}, base_config)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -5,13 +5,17 @@ For more details about this component, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/wink/
|
https://home-assistant.io/components/wink/
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
|
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL
|
|
||||||
from homeassistant.helpers import validate_config, discovery
|
from homeassistant.helpers import validate_config, discovery
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.const import CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
DOMAIN = "wink"
|
DOMAIN = "wink"
|
||||||
REQUIREMENTS = ['python-wink==0.7.7']
|
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
|
||||||
|
|
||||||
|
SUBSCRIPTION_HANDLER = None
|
||||||
|
CHANNELS = []
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
@ -22,7 +26,11 @@ def setup(hass, config):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
import pywink
|
import pywink
|
||||||
|
from pubnub import Pubnub
|
||||||
pywink.set_bearer_token(config[DOMAIN][CONF_ACCESS_TOKEN])
|
pywink.set_bearer_token(config[DOMAIN][CONF_ACCESS_TOKEN])
|
||||||
|
global SUBSCRIPTION_HANDLER
|
||||||
|
SUBSCRIPTION_HANDLER = Pubnub("N/A", pywink.get_subscription_key())
|
||||||
|
SUBSCRIPTION_HANDLER.set_heartbeat(120)
|
||||||
|
|
||||||
# Load components for the devices in the Wink that we support
|
# Load components for the devices in the Wink that we support
|
||||||
for component_name, func_exists in (
|
for component_name, func_exists in (
|
||||||
@ -41,13 +49,33 @@ def setup(hass, config):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class WinkToggleDevice(ToggleEntity):
|
class WinkDevice(Entity):
|
||||||
"""Represents a Wink toggle (switch) device."""
|
"""Represents a base Wink device."""
|
||||||
|
|
||||||
def __init__(self, wink):
|
def __init__(self, wink):
|
||||||
"""Initialize the Wink device."""
|
"""Initialize the Wink device."""
|
||||||
|
from pubnub import Pubnub
|
||||||
self.wink = wink
|
self.wink = wink
|
||||||
self._battery = self.wink.battery_level
|
self._battery = self.wink.battery_level
|
||||||
|
if self.wink.pubnub_channel in CHANNELS:
|
||||||
|
pubnub = Pubnub("N/A", self.wink.pubnub_key)
|
||||||
|
pubnub.set_heartbeat(120)
|
||||||
|
pubnub.subscribe(self.wink.pubnub_channel,
|
||||||
|
self._pubnub_update,
|
||||||
|
error=self._pubnub_error)
|
||||||
|
else:
|
||||||
|
CHANNELS.append(self.wink.pubnub_channel)
|
||||||
|
SUBSCRIPTION_HANDLER.subscribe(self.wink.pubnub_channel,
|
||||||
|
self._pubnub_update,
|
||||||
|
error=self._pubnub_error)
|
||||||
|
|
||||||
|
def _pubnub_update(self, message, channel):
|
||||||
|
self.wink.pubnub_update(json.loads(message))
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def _pubnub_error(self, message):
|
||||||
|
logging.getLogger(__name__).error(
|
||||||
|
"Error on pubnub update for " + self.wink.name())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
@ -59,28 +87,20 @@ class WinkToggleDevice(ToggleEntity):
|
|||||||
"""Return the name of the device."""
|
"""Return the name of the device."""
|
||||||
return self.wink.name()
|
return self.wink.name()
|
||||||
|
|
||||||
@property
|
|
||||||
def is_on(self):
|
|
||||||
"""Return true if device is on."""
|
|
||||||
return self.wink.state()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
"""True if connection == True."""
|
"""True if connection == True."""
|
||||||
return self.wink.available
|
return self.wink.available
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
|
||||||
"""Turn the device on."""
|
|
||||||
self.wink.set_state(True)
|
|
||||||
|
|
||||||
def turn_off(self):
|
|
||||||
"""Turn the device off."""
|
|
||||||
self.wink.set_state(False)
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update state of the device."""
|
"""Update state of the device."""
|
||||||
self.wink.update_state()
|
self.wink.update_state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Only poll if we are not subscribed to pubnub."""
|
||||||
|
return self.wink.pubnub_channel is None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
|
@ -41,6 +41,7 @@ EVENT_SCENE_ACTIVATED = "zwave.scene_activated"
|
|||||||
|
|
||||||
COMMAND_CLASS_WHATEVER = None
|
COMMAND_CLASS_WHATEVER = None
|
||||||
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
|
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
|
||||||
|
COMMAND_CLASS_COLOR = 51
|
||||||
COMMAND_CLASS_METER = 50
|
COMMAND_CLASS_METER = 50
|
||||||
COMMAND_CLASS_ALARM = 113
|
COMMAND_CLASS_ALARM = 113
|
||||||
COMMAND_CLASS_SWITCH_BINARY = 37
|
COMMAND_CLASS_SWITCH_BINARY = 37
|
||||||
|
@ -1,31 +1,35 @@
|
|||||||
"""Module to help with parsing and generating configuration files."""
|
"""Module to help with parsing and generating configuration files."""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.util.location as loc_util
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_TEMPERATURE_UNIT,
|
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_TEMPERATURE_UNIT,
|
||||||
CONF_TIME_ZONE, CONF_CUSTOMIZE)
|
CONF_TIME_ZONE, CONF_CUSTOMIZE, CONF_ELEVATION, TEMP_FAHRENHEIT,
|
||||||
|
TEMP_CELSIUS, __version__)
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.util.yaml import load_yaml
|
from homeassistant.util.yaml import load_yaml
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import valid_entity_id
|
from homeassistant.helpers.entity import valid_entity_id, set_customize
|
||||||
|
from homeassistant.util import dt as date_util, location as loc_util
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
YAML_CONFIG_FILE = 'configuration.yaml'
|
YAML_CONFIG_FILE = 'configuration.yaml'
|
||||||
|
VERSION_FILE = '.HA_VERSION'
|
||||||
CONFIG_DIR_NAME = '.homeassistant'
|
CONFIG_DIR_NAME = '.homeassistant'
|
||||||
|
|
||||||
DEFAULT_CONFIG = (
|
DEFAULT_CONFIG = (
|
||||||
# Tuples (attribute, default, auto detect property, description)
|
# Tuples (attribute, default, auto detect property, description)
|
||||||
(CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is '
|
(CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is '
|
||||||
'running'),
|
'running'),
|
||||||
(CONF_LATITUDE, None, 'latitude', 'Location required to calculate the time'
|
(CONF_LATITUDE, 0, 'latitude', 'Location required to calculate the time'
|
||||||
' the sun rises and sets'),
|
' the sun rises and sets'),
|
||||||
(CONF_LONGITUDE, None, 'longitude', None),
|
(CONF_LONGITUDE, 0, 'longitude', None),
|
||||||
|
(CONF_ELEVATION, 0, None, 'Impacts weather/sunrise data'),
|
||||||
(CONF_TEMPERATURE_UNIT, 'C', None, 'C for Celsius, F for Fahrenheit'),
|
(CONF_TEMPERATURE_UNIT, 'C', None, 'C for Celsius, F for Fahrenheit'),
|
||||||
(CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki'
|
(CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki'
|
||||||
'pedia.org/wiki/List_of_tz_database_time_zones'),
|
'pedia.org/wiki/List_of_tz_database_time_zones'),
|
||||||
@ -39,7 +43,7 @@ DEFAULT_COMPONENTS = {
|
|||||||
'history:': 'Enables support for tracking state changes over time.',
|
'history:': 'Enables support for tracking state changes over time.',
|
||||||
'logbook:': 'View all events in a logbook',
|
'logbook:': 'View all events in a logbook',
|
||||||
'sun:': 'Track the sun',
|
'sun:': 'Track the sun',
|
||||||
'sensor:\n platform: yr': 'Prediction of weather',
|
'sensor:\n platform: yr': 'Weather Prediction',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -61,6 +65,7 @@ CORE_CONFIG_SCHEMA = vol.Schema({
|
|||||||
CONF_NAME: vol.Coerce(str),
|
CONF_NAME: vol.Coerce(str),
|
||||||
CONF_LATITUDE: cv.latitude,
|
CONF_LATITUDE: cv.latitude,
|
||||||
CONF_LONGITUDE: cv.longitude,
|
CONF_LONGITUDE: cv.longitude,
|
||||||
|
CONF_ELEVATION: vol.Coerce(float),
|
||||||
CONF_TEMPERATURE_UNIT: cv.temperature_unit,
|
CONF_TEMPERATURE_UNIT: cv.temperature_unit,
|
||||||
CONF_TIME_ZONE: cv.time_zone,
|
CONF_TIME_ZONE: cv.time_zone,
|
||||||
vol.Required(CONF_CUSTOMIZE,
|
vol.Required(CONF_CUSTOMIZE,
|
||||||
@ -97,6 +102,7 @@ def create_default_config(config_dir, detect_location=True):
|
|||||||
Return path to new config file if success, None if failed.
|
Return path to new config file if success, None if failed.
|
||||||
"""
|
"""
|
||||||
config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
|
config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
|
||||||
|
version_path = os.path.join(config_dir, VERSION_FILE)
|
||||||
|
|
||||||
info = {attr: default for attr, default, _, _ in DEFAULT_CONFIG}
|
info = {attr: default for attr, default, _, _ in DEFAULT_CONFIG}
|
||||||
|
|
||||||
@ -111,6 +117,10 @@ def create_default_config(config_dir, detect_location=True):
|
|||||||
continue
|
continue
|
||||||
info[attr] = getattr(location_info, prop) or default
|
info[attr] = getattr(location_info, prop) or default
|
||||||
|
|
||||||
|
if location_info.latitude and location_info.longitude:
|
||||||
|
info[CONF_ELEVATION] = loc_util.elevation(location_info.latitude,
|
||||||
|
location_info.longitude)
|
||||||
|
|
||||||
# Writing files with YAML does not create the most human readable results
|
# Writing files with YAML does not create the most human readable results
|
||||||
# So we're hard coding a YAML template.
|
# So we're hard coding a YAML template.
|
||||||
try:
|
try:
|
||||||
@ -130,6 +140,9 @@ def create_default_config(config_dir, detect_location=True):
|
|||||||
config_file.write("# {}\n".format(description))
|
config_file.write("# {}\n".format(description))
|
||||||
config_file.write("{}\n\n".format(component))
|
config_file.write("{}\n\n".format(component))
|
||||||
|
|
||||||
|
with open(version_path, 'wt') as version_file:
|
||||||
|
version_file.write(__version__)
|
||||||
|
|
||||||
return config_path
|
return config_path
|
||||||
|
|
||||||
except IOError:
|
except IOError:
|
||||||
@ -155,3 +168,112 @@ def load_yaml_config_file(config_path):
|
|||||||
raise HomeAssistantError(msg)
|
raise HomeAssistantError(msg)
|
||||||
|
|
||||||
return conf_dict
|
return conf_dict
|
||||||
|
|
||||||
|
|
||||||
|
def process_ha_config_upgrade(hass):
|
||||||
|
"""Upgrade config if necessary."""
|
||||||
|
version_path = hass.config.path(VERSION_FILE)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(version_path, 'rt') as inp:
|
||||||
|
conf_version = inp.readline().strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
# Last version to not have this file
|
||||||
|
conf_version = '0.7.7'
|
||||||
|
|
||||||
|
if conf_version == __version__:
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.info('Upgrading config directory from %s to %s', conf_version,
|
||||||
|
__version__)
|
||||||
|
|
||||||
|
lib_path = hass.config.path('deps')
|
||||||
|
if os.path.isdir(lib_path):
|
||||||
|
shutil.rmtree(lib_path)
|
||||||
|
|
||||||
|
with open(version_path, 'wt') as outp:
|
||||||
|
outp.write(__version__)
|
||||||
|
|
||||||
|
|
||||||
|
def process_ha_core_config(hass, config):
|
||||||
|
"""Process the [homeassistant] section from the config."""
|
||||||
|
# pylint: disable=too-many-branches
|
||||||
|
config = CORE_CONFIG_SCHEMA(config)
|
||||||
|
hac = hass.config
|
||||||
|
|
||||||
|
def set_time_zone(time_zone_str):
|
||||||
|
"""Helper method to set time zone."""
|
||||||
|
if time_zone_str is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
time_zone = date_util.get_time_zone(time_zone_str)
|
||||||
|
|
||||||
|
if time_zone:
|
||||||
|
hac.time_zone = time_zone
|
||||||
|
date_util.set_default_time_zone(time_zone)
|
||||||
|
else:
|
||||||
|
_LOGGER.error('Received invalid time zone %s', time_zone_str)
|
||||||
|
|
||||||
|
for key, attr in ((CONF_LATITUDE, 'latitude'),
|
||||||
|
(CONF_LONGITUDE, 'longitude'),
|
||||||
|
(CONF_NAME, 'location_name'),
|
||||||
|
(CONF_ELEVATION, 'elevation')):
|
||||||
|
if key in config:
|
||||||
|
setattr(hac, attr, config[key])
|
||||||
|
|
||||||
|
if CONF_TIME_ZONE in config:
|
||||||
|
set_time_zone(config.get(CONF_TIME_ZONE))
|
||||||
|
|
||||||
|
set_customize(config.get(CONF_CUSTOMIZE) or {})
|
||||||
|
|
||||||
|
if CONF_TEMPERATURE_UNIT in config:
|
||||||
|
hac.temperature_unit = config[CONF_TEMPERATURE_UNIT]
|
||||||
|
|
||||||
|
# Shortcut if no auto-detection necessary
|
||||||
|
if None not in (hac.latitude, hac.longitude, hac.temperature_unit,
|
||||||
|
hac.time_zone, hac.elevation):
|
||||||
|
return
|
||||||
|
|
||||||
|
discovered = []
|
||||||
|
|
||||||
|
# If we miss some of the needed values, auto detect them
|
||||||
|
if None in (hac.latitude, hac.longitude, hac.temperature_unit,
|
||||||
|
hac.time_zone):
|
||||||
|
info = loc_util.detect_location_info()
|
||||||
|
|
||||||
|
if info is None:
|
||||||
|
_LOGGER.error('Could not detect location information')
|
||||||
|
return
|
||||||
|
|
||||||
|
if hac.latitude is None and hac.longitude is None:
|
||||||
|
hac.latitude = info.latitude
|
||||||
|
hac.longitude = info.longitude
|
||||||
|
discovered.append(('latitude', hac.latitude))
|
||||||
|
discovered.append(('longitude', hac.longitude))
|
||||||
|
|
||||||
|
if hac.temperature_unit is None:
|
||||||
|
if info.use_fahrenheit:
|
||||||
|
hac.temperature_unit = TEMP_FAHRENHEIT
|
||||||
|
discovered.append(('temperature_unit', 'F'))
|
||||||
|
else:
|
||||||
|
hac.temperature_unit = TEMP_CELSIUS
|
||||||
|
discovered.append(('temperature_unit', 'C'))
|
||||||
|
|
||||||
|
if hac.location_name is None:
|
||||||
|
hac.location_name = info.city
|
||||||
|
discovered.append(('name', info.city))
|
||||||
|
|
||||||
|
if hac.time_zone is None:
|
||||||
|
set_time_zone(info.time_zone)
|
||||||
|
discovered.append(('time_zone', info.time_zone))
|
||||||
|
|
||||||
|
if hac.elevation is None and hac.latitude is not None and \
|
||||||
|
hac.longitude is not None:
|
||||||
|
elevation = loc_util.elevation(hac.latitude, hac.longitude)
|
||||||
|
hac.elevation = elevation
|
||||||
|
discovered.append(('elevation', elevation))
|
||||||
|
|
||||||
|
if discovered:
|
||||||
|
_LOGGER.warning(
|
||||||
|
'Incomplete core config. Auto detected %s',
|
||||||
|
', '.join('{}: {}'.format(key, val) for key, val in discovered))
|
||||||
|
@ -49,6 +49,19 @@ MIN_WORKER_THREAD = 2
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CoreState(enum.Enum):
|
||||||
|
"""Represent the current state of Home Assistant."""
|
||||||
|
|
||||||
|
not_running = "NOT_RUNNING"
|
||||||
|
starting = "STARTING"
|
||||||
|
running = "RUNNING"
|
||||||
|
stopping = "STOPPING"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Return the event."""
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistant(object):
|
class HomeAssistant(object):
|
||||||
"""Root object of the Home Assistant home automation."""
|
"""Root object of the Home Assistant home automation."""
|
||||||
|
|
||||||
@ -59,14 +72,23 @@ class HomeAssistant(object):
|
|||||||
self.services = ServiceRegistry(self.bus, pool)
|
self.services = ServiceRegistry(self.bus, pool)
|
||||||
self.states = StateMachine(self.bus)
|
self.states = StateMachine(self.bus)
|
||||||
self.config = Config()
|
self.config = Config()
|
||||||
|
self.state = CoreState.not_running
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_running(self):
|
||||||
|
"""Return if Home Assistant is running."""
|
||||||
|
return self.state == CoreState.running
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start home assistant."""
|
"""Start home assistant."""
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Starting Home Assistant (%d threads)", self.pool.worker_count)
|
"Starting Home Assistant (%d threads)", self.pool.worker_count)
|
||||||
|
self.state = CoreState.starting
|
||||||
|
|
||||||
create_timer(self)
|
create_timer(self)
|
||||||
self.bus.fire(EVENT_HOMEASSISTANT_START)
|
self.bus.fire(EVENT_HOMEASSISTANT_START)
|
||||||
|
self.pool.block_till_done()
|
||||||
|
self.state = CoreState.running
|
||||||
|
|
||||||
def block_till_stopped(self):
|
def block_till_stopped(self):
|
||||||
"""Register service homeassistant/stop and will block until called."""
|
"""Register service homeassistant/stop and will block until called."""
|
||||||
@ -113,8 +135,10 @@ class HomeAssistant(object):
|
|||||||
def stop(self):
|
def stop(self):
|
||||||
"""Stop Home Assistant and shuts down all threads."""
|
"""Stop Home Assistant and shuts down all threads."""
|
||||||
_LOGGER.info("Stopping")
|
_LOGGER.info("Stopping")
|
||||||
|
self.state = CoreState.stopping
|
||||||
self.bus.fire(EVENT_HOMEASSISTANT_STOP)
|
self.bus.fire(EVENT_HOMEASSISTANT_STOP)
|
||||||
self.pool.stop()
|
self.pool.stop()
|
||||||
|
self.state = CoreState.not_running
|
||||||
|
|
||||||
|
|
||||||
class JobPriority(util.OrderedEnum):
|
class JobPriority(util.OrderedEnum):
|
||||||
@ -681,6 +705,7 @@ class Config(object):
|
|||||||
"""Initialize a new config object."""
|
"""Initialize a new config object."""
|
||||||
self.latitude = None
|
self.latitude = None
|
||||||
self.longitude = None
|
self.longitude = None
|
||||||
|
self.elevation = None
|
||||||
self.temperature_unit = None
|
self.temperature_unit = None
|
||||||
self.location_name = None
|
self.location_name = None
|
||||||
self.time_zone = None
|
self.time_zone = None
|
||||||
|
@ -11,6 +11,7 @@ from datetime import datetime
|
|||||||
import enum
|
import enum
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
import threading
|
import threading
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
@ -123,6 +124,7 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
self.services = ha.ServiceRegistry(self.bus, pool)
|
self.services = ha.ServiceRegistry(self.bus, pool)
|
||||||
self.states = StateMachine(self.bus, self.remote_api)
|
self.states = StateMachine(self.bus, self.remote_api)
|
||||||
self.config = ha.Config()
|
self.config = ha.Config()
|
||||||
|
self.state = ha.CoreState.not_running
|
||||||
|
|
||||||
self.config.api = local_api
|
self.config.api = local_api
|
||||||
|
|
||||||
@ -134,17 +136,20 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
'Unable to setup local API to receive events')
|
'Unable to setup local API to receive events')
|
||||||
|
|
||||||
|
self.state = ha.CoreState.starting
|
||||||
ha.create_timer(self)
|
ha.create_timer(self)
|
||||||
|
|
||||||
self.bus.fire(ha.EVENT_HOMEASSISTANT_START,
|
self.bus.fire(ha.EVENT_HOMEASSISTANT_START,
|
||||||
origin=ha.EventOrigin.remote)
|
origin=ha.EventOrigin.remote)
|
||||||
|
|
||||||
# Give eventlet time to startup
|
# Ensure local HTTP is started
|
||||||
import eventlet
|
self.pool.block_till_done()
|
||||||
eventlet.sleep(0.1)
|
self.state = ha.CoreState.running
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
# Setup that events from remote_api get forwarded to local_api
|
# Setup that events from remote_api get forwarded to local_api
|
||||||
# Do this after we fire START, otherwise HTTP is not started
|
# Do this after we are running, otherwise HTTP is not started
|
||||||
|
# or requests are blocked
|
||||||
if not connect_remote_events(self.remote_api, self.config.api):
|
if not connect_remote_events(self.remote_api, self.config.api):
|
||||||
raise HomeAssistantError((
|
raise HomeAssistantError((
|
||||||
'Could not setup event forwarding from api {} to '
|
'Could not setup event forwarding from api {} to '
|
||||||
@ -153,6 +158,7 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
def stop(self):
|
def stop(self):
|
||||||
"""Stop Home Assistant and shuts down all threads."""
|
"""Stop Home Assistant and shuts down all threads."""
|
||||||
_LOGGER.info("Stopping")
|
_LOGGER.info("Stopping")
|
||||||
|
self.state = ha.CoreState.stopping
|
||||||
|
|
||||||
self.bus.fire(ha.EVENT_HOMEASSISTANT_STOP,
|
self.bus.fire(ha.EVENT_HOMEASSISTANT_STOP,
|
||||||
origin=ha.EventOrigin.remote)
|
origin=ha.EventOrigin.remote)
|
||||||
@ -161,6 +167,7 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
|
|
||||||
# Disconnect master event forwarding
|
# Disconnect master event forwarding
|
||||||
disconnect_remote_events(self.remote_api, self.config.api)
|
disconnect_remote_events(self.remote_api, self.config.api)
|
||||||
|
self.state = ha.CoreState.not_running
|
||||||
|
|
||||||
|
|
||||||
class EventBus(ha.EventBus):
|
class EventBus(ha.EventBus):
|
||||||
|
@ -8,7 +8,8 @@ import math
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
ELEVATION_URL = 'http://maps.googleapis.com/maps/api/elevation/json'
|
ELEVATION_URL = 'http://maps.googleapis.com/maps/api/elevation/json'
|
||||||
DATA_SOURCE = ['https://freegeoip.io/json/', 'http://ip-api.com/json']
|
FREEGEO_API = 'https://freegeoip.io/json/'
|
||||||
|
IP_API = 'http://ip-api.com/json'
|
||||||
|
|
||||||
# Constants from https://github.com/maurycyp/vincenty
|
# Constants from https://github.com/maurycyp/vincenty
|
||||||
# Earth ellipsoid according to WGS 84
|
# Earth ellipsoid according to WGS 84
|
||||||
@ -32,30 +33,13 @@ LocationInfo = collections.namedtuple(
|
|||||||
|
|
||||||
def detect_location_info():
|
def detect_location_info():
|
||||||
"""Detect location information."""
|
"""Detect location information."""
|
||||||
success = None
|
data = _get_freegeoip()
|
||||||
|
|
||||||
for source in DATA_SOURCE:
|
if data is None:
|
||||||
try:
|
data = _get_ip_api()
|
||||||
raw_info = requests.get(source, timeout=5).json()
|
|
||||||
success = source
|
|
||||||
break
|
|
||||||
except (requests.RequestException, ValueError):
|
|
||||||
success = False
|
|
||||||
|
|
||||||
if success is False:
|
if data is None:
|
||||||
return None
|
return None
|
||||||
else:
|
|
||||||
data = {key: raw_info.get(key) for key in LocationInfo._fields}
|
|
||||||
if success is DATA_SOURCE[1]:
|
|
||||||
data['ip'] = raw_info.get('query')
|
|
||||||
data['country_code'] = raw_info.get('countryCode')
|
|
||||||
data['country_name'] = raw_info.get('country')
|
|
||||||
data['region_code'] = raw_info.get('region')
|
|
||||||
data['region_name'] = raw_info.get('regionName')
|
|
||||||
data['zip_code'] = raw_info.get('zip')
|
|
||||||
data['time_zone'] = raw_info.get('timezone')
|
|
||||||
data['latitude'] = raw_info.get('lat')
|
|
||||||
data['longitude'] = raw_info.get('lon')
|
|
||||||
|
|
||||||
# From Wikipedia: Fahrenheit is used in the Bahamas, Belize,
|
# From Wikipedia: Fahrenheit is used in the Bahamas, Belize,
|
||||||
# the Cayman Islands, Palau, and the United States and associated
|
# the Cayman Islands, Palau, and the United States and associated
|
||||||
@ -73,11 +57,16 @@ def distance(lat1, lon1, lat2, lon2):
|
|||||||
|
|
||||||
def elevation(latitude, longitude):
|
def elevation(latitude, longitude):
|
||||||
"""Return elevation for given latitude and longitude."""
|
"""Return elevation for given latitude and longitude."""
|
||||||
req = requests.get(ELEVATION_URL,
|
try:
|
||||||
params={'locations': '{},{}'.format(latitude,
|
req = requests.get(
|
||||||
longitude),
|
ELEVATION_URL,
|
||||||
'sensor': 'false'},
|
params={
|
||||||
timeout=10)
|
'locations': '{},{}'.format(latitude, longitude),
|
||||||
|
'sensor': 'false',
|
||||||
|
},
|
||||||
|
timeout=10)
|
||||||
|
except requests.RequestException:
|
||||||
|
return 0
|
||||||
|
|
||||||
if req.status_code != 200:
|
if req.status_code != 200:
|
||||||
return 0
|
return 0
|
||||||
@ -157,3 +146,45 @@ def vincenty(point1, point2, miles=False):
|
|||||||
s *= MILES_PER_KILOMETER # kilometers to miles
|
s *= MILES_PER_KILOMETER # kilometers to miles
|
||||||
|
|
||||||
return round(s, 6)
|
return round(s, 6)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_freegeoip():
|
||||||
|
"""Query freegeoip.io for location data."""
|
||||||
|
try:
|
||||||
|
raw_info = requests.get(FREEGEO_API, timeout=5).json()
|
||||||
|
except (requests.RequestException, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return {
|
||||||
|
'ip': raw_info.get('ip'),
|
||||||
|
'country_code': raw_info.get('country_code'),
|
||||||
|
'country_name': raw_info.get('country_name'),
|
||||||
|
'region_code': raw_info.get('region_code'),
|
||||||
|
'region_name': raw_info.get('region_name'),
|
||||||
|
'city': raw_info.get('city'),
|
||||||
|
'zip_code': raw_info.get('zip_code'),
|
||||||
|
'time_zone': raw_info.get('time_zone'),
|
||||||
|
'latitude': raw_info.get('latitude'),
|
||||||
|
'longitude': raw_info.get('longitude'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_ip_api():
|
||||||
|
"""Query ip-api.com for location data."""
|
||||||
|
try:
|
||||||
|
raw_info = requests.get(IP_API, timeout=5).json()
|
||||||
|
except (requests.RequestException, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return {
|
||||||
|
'ip': raw_info.get('query'),
|
||||||
|
'country_code': raw_info.get('countryCode'),
|
||||||
|
'country_name': raw_info.get('country'),
|
||||||
|
'region_code': raw_info.get('region'),
|
||||||
|
'region_name': raw_info.get('regionName'),
|
||||||
|
'city': raw_info.get('city'),
|
||||||
|
'zip_code': raw_info.get('zip'),
|
||||||
|
'time_zone': raw_info.get('timezone'),
|
||||||
|
'latitude': raw_info.get('lat'),
|
||||||
|
'longitude': raw_info.get('lon'),
|
||||||
|
}
|
||||||
|
@ -5,7 +5,6 @@ pytz>=2016.4
|
|||||||
pip>=7.0.0
|
pip>=7.0.0
|
||||||
jinja2>=2.8
|
jinja2>=2.8
|
||||||
voluptuous==0.8.9
|
voluptuous==0.8.9
|
||||||
eventlet==0.19.0
|
|
||||||
|
|
||||||
# homeassistant.components.isy994
|
# homeassistant.components.isy994
|
||||||
PyISY==1.0.6
|
PyISY==1.0.6
|
||||||
@ -23,7 +22,7 @@ SoCo==0.11.1
|
|||||||
TwitterAPI==2.4.1
|
TwitterAPI==2.4.1
|
||||||
|
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
Werkzeug==0.11.5
|
Werkzeug==0.11.10
|
||||||
|
|
||||||
# homeassistant.components.apcupsd
|
# homeassistant.components.apcupsd
|
||||||
apcaccess==0.0.4
|
apcaccess==0.0.4
|
||||||
@ -41,13 +40,16 @@ blinkstick==1.1.7
|
|||||||
blockchain==1.3.3
|
blockchain==1.3.3
|
||||||
|
|
||||||
# homeassistant.components.thermostat.eq3btsmart
|
# homeassistant.components.thermostat.eq3btsmart
|
||||||
# bluepy_devices>=0.2.0
|
# bluepy_devices==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.notify.aws_lambda
|
# homeassistant.components.notify.aws_lambda
|
||||||
# homeassistant.components.notify.aws_sns
|
# homeassistant.components.notify.aws_sns
|
||||||
# homeassistant.components.notify.aws_sqs
|
# homeassistant.components.notify.aws_sqs
|
||||||
boto3==1.3.1
|
boto3==1.3.1
|
||||||
|
|
||||||
|
# homeassistant.components.http
|
||||||
|
cherrypy==6.0.2
|
||||||
|
|
||||||
# homeassistant.components.notify.xmpp
|
# homeassistant.components.notify.xmpp
|
||||||
dnspython3==1.12.0
|
dnspython3==1.12.0
|
||||||
|
|
||||||
@ -61,9 +63,6 @@ eliqonline==1.0.12
|
|||||||
# homeassistant.components.enocean
|
# homeassistant.components.enocean
|
||||||
enocean==0.31
|
enocean==0.31
|
||||||
|
|
||||||
# homeassistant.components.http
|
|
||||||
eventlet==0.19.0
|
|
||||||
|
|
||||||
# homeassistant.components.thermostat.honeywell
|
# homeassistant.components.thermostat.honeywell
|
||||||
evohomeclient==0.2.5
|
evohomeclient==0.2.5
|
||||||
|
|
||||||
@ -163,13 +162,13 @@ https://github.com/w1ll1am23/pygooglevoice-sms/archive/7c5ee9969b97a7992fc86a753
|
|||||||
https://github.com/wokar/pylgnetcast/archive/v0.2.0.zip#pylgnetcast==0.2.0
|
https://github.com/wokar/pylgnetcast/archive/v0.2.0.zip#pylgnetcast==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.influxdb
|
# homeassistant.components.influxdb
|
||||||
influxdb==2.12.0
|
influxdb==3.0.0
|
||||||
|
|
||||||
# homeassistant.components.insteon_hub
|
# homeassistant.components.insteon_hub
|
||||||
insteon_hub==0.4.5
|
insteon_hub==0.4.5
|
||||||
|
|
||||||
# homeassistant.components.media_player.kodi
|
# homeassistant.components.media_player.kodi
|
||||||
jsonrpc-requests==0.2
|
jsonrpc-requests==0.3
|
||||||
|
|
||||||
# homeassistant.components.light.lifx
|
# homeassistant.components.light.lifx
|
||||||
liffylights==0.9.4
|
liffylights==0.9.4
|
||||||
@ -220,6 +219,16 @@ proliphix==0.1.0
|
|||||||
# homeassistant.components.sensor.systemmonitor
|
# homeassistant.components.sensor.systemmonitor
|
||||||
psutil==4.3.0
|
psutil==4.3.0
|
||||||
|
|
||||||
|
# homeassistant.components.wink
|
||||||
|
# homeassistant.components.binary_sensor.wink
|
||||||
|
# homeassistant.components.garage_door.wink
|
||||||
|
# homeassistant.components.light.wink
|
||||||
|
# homeassistant.components.lock.wink
|
||||||
|
# homeassistant.components.rollershutter.wink
|
||||||
|
# homeassistant.components.sensor.wink
|
||||||
|
# homeassistant.components.switch.wink
|
||||||
|
pubnub==3.7.8
|
||||||
|
|
||||||
# homeassistant.components.notify.pushbullet
|
# homeassistant.components.notify.pushbullet
|
||||||
pushbullet.py==0.10.0
|
pushbullet.py==0.10.0
|
||||||
|
|
||||||
@ -245,7 +254,7 @@ pyasn1==0.1.9
|
|||||||
pychromecast==0.7.2
|
pychromecast==0.7.2
|
||||||
|
|
||||||
# homeassistant.components.media_player.cmus
|
# homeassistant.components.media_player.cmus
|
||||||
pycmus>=0.1.0
|
pycmus==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.envisalink
|
# homeassistant.components.envisalink
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
@ -258,7 +267,7 @@ pyenvisalink==1.0
|
|||||||
pyfttt==0.3
|
pyfttt==0.3
|
||||||
|
|
||||||
# homeassistant.components.homematic
|
# homeassistant.components.homematic
|
||||||
pyhomematic==0.1.6
|
pyhomematic==0.1.8
|
||||||
|
|
||||||
# homeassistant.components.device_tracker.icloud
|
# homeassistant.components.device_tracker.icloud
|
||||||
pyicloud==0.8.3
|
pyicloud==0.8.3
|
||||||
@ -324,13 +333,13 @@ python-twitch==1.2.0
|
|||||||
# homeassistant.components.rollershutter.wink
|
# homeassistant.components.rollershutter.wink
|
||||||
# homeassistant.components.sensor.wink
|
# homeassistant.components.sensor.wink
|
||||||
# homeassistant.components.switch.wink
|
# homeassistant.components.switch.wink
|
||||||
python-wink==0.7.7
|
python-wink==0.7.8
|
||||||
|
|
||||||
# homeassistant.components.keyboard
|
# homeassistant.components.keyboard
|
||||||
pyuserinput==0.1.9
|
pyuserinput==0.1.9
|
||||||
|
|
||||||
# homeassistant.components.vera
|
# homeassistant.components.vera
|
||||||
pyvera==0.2.10
|
pyvera==0.2.12
|
||||||
|
|
||||||
# homeassistant.components.wemo
|
# homeassistant.components.wemo
|
||||||
pywemo==0.4.3
|
pywemo==0.4.3
|
||||||
@ -410,7 +419,7 @@ vsure==0.8.1
|
|||||||
wakeonlan==0.2.2
|
wakeonlan==0.2.2
|
||||||
|
|
||||||
# homeassistant.components.media_player.gpmdp
|
# homeassistant.components.media_player.gpmdp
|
||||||
websocket-client==0.35.0
|
websocket-client==0.37.0
|
||||||
|
|
||||||
# homeassistant.components.zigbee
|
# homeassistant.components.zigbee
|
||||||
xbee-helper==0.0.7
|
xbee-helper==0.0.7
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
flake8>=2.5.4
|
flake8>=2.6.0
|
||||||
pylint>=1.5.5
|
pylint>=1.5.6
|
||||||
coveralls>=1.1
|
coveralls>=1.1
|
||||||
pytest>=2.9.1
|
pytest>=2.9.2
|
||||||
pytest-cov>=2.2.0
|
pytest-cov>=2.2.1
|
||||||
pytest-timeout>=1.0.0
|
pytest-timeout>=1.0.0
|
||||||
pytest-capturelog>=0.7
|
pytest-capturelog>=0.7
|
||||||
betamax==0.7.0
|
|
||||||
pydocstyle>=1.0.0
|
pydocstyle>=1.0.0
|
||||||
httpretty==0.8.14
|
requests_mock>=1.0
|
||||||
|
1
setup.py
1
setup.py
@ -17,7 +17,6 @@ REQUIRES = [
|
|||||||
'pip>=7.0.0',
|
'pip>=7.0.0',
|
||||||
'jinja2>=2.8',
|
'jinja2>=2.8',
|
||||||
'voluptuous==0.8.9',
|
'voluptuous==0.8.9',
|
||||||
'eventlet==0.19.0',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
@ -1,27 +1,25 @@
|
|||||||
"""Test the initialization."""
|
"""Setup some common test helper things."""
|
||||||
import betamax
|
import functools
|
||||||
|
|
||||||
from homeassistant import util
|
from homeassistant import util
|
||||||
from homeassistant.util import location
|
from homeassistant.util import location
|
||||||
|
|
||||||
with betamax.Betamax.configure() as config:
|
|
||||||
config.cassette_library_dir = 'tests/cassettes'
|
|
||||||
|
|
||||||
# Automatically called during different setups. Too often forgotten
|
def test_real(func):
|
||||||
# so mocked by default.
|
"""Force a function to require a keyword _test_real to be passed in."""
|
||||||
location.detect_location_info = lambda: location.LocationInfo(
|
@functools.wraps(func)
|
||||||
ip='1.1.1.1',
|
def guard_func(*args, **kwargs):
|
||||||
country_code='US',
|
real = kwargs.pop('_test_real', None)
|
||||||
country_name='United States',
|
|
||||||
region_code='CA',
|
|
||||||
region_name='California',
|
|
||||||
city='San Diego',
|
|
||||||
zip_code='92122',
|
|
||||||
time_zone='America/Los_Angeles',
|
|
||||||
latitude='2.0',
|
|
||||||
longitude='1.0',
|
|
||||||
use_fahrenheit=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
location.elevation = lambda latitude, longitude: 0
|
if not real:
|
||||||
|
raise Exception('Forgot to mock or pass "_test_real=True" to %s',
|
||||||
|
func.__name__)
|
||||||
|
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return guard_func
|
||||||
|
|
||||||
|
# Guard a few functions that would make network connections
|
||||||
|
location.detect_location_info = test_real(location.detect_location_info)
|
||||||
|
location.elevation = test_real(location.elevation)
|
||||||
util.get_local_ip = lambda: '127.0.0.1'
|
util.get_local_ip = lambda: '127.0.0.1'
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -35,6 +35,7 @@ def get_test_home_assistant(num_threads=None):
|
|||||||
hass.config.config_dir = get_test_config_dir()
|
hass.config.config_dir = get_test_config_dir()
|
||||||
hass.config.latitude = 32.87336
|
hass.config.latitude = 32.87336
|
||||||
hass.config.longitude = -117.22743
|
hass.config.longitude = -117.22743
|
||||||
|
hass.config.elevation = 0
|
||||||
hass.config.time_zone = date_util.get_time_zone('US/Pacific')
|
hass.config.time_zone = date_util.get_time_zone('US/Pacific')
|
||||||
hass.config.temperature_unit = TEMP_CELSIUS
|
hass.config.temperature_unit = TEMP_CELSIUS
|
||||||
|
|
||||||
@ -105,6 +106,13 @@ def ensure_sun_set(hass):
|
|||||||
fire_time_changed(hass, sun.next_setting_utc(hass) + timedelta(seconds=10))
|
fire_time_changed(hass, sun.next_setting_utc(hass) + timedelta(seconds=10))
|
||||||
|
|
||||||
|
|
||||||
|
def load_fixture(filename):
|
||||||
|
"""Helper to load a fixture."""
|
||||||
|
path = os.path.join(os.path.dirname(__file__), 'fixtures', filename)
|
||||||
|
with open(path) as fp:
|
||||||
|
return fp.read()
|
||||||
|
|
||||||
|
|
||||||
def mock_state_change_event(hass, new_state, old_state=None):
|
def mock_state_change_event(hass, new_state, old_state=None):
|
||||||
"""Mock state change envent."""
|
"""Mock state change envent."""
|
||||||
event_data = {
|
event_data = {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""The tests the for Locative device tracker platform."""
|
"""The tests the for Locative device tracker platform."""
|
||||||
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import eventlet
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from homeassistant import bootstrap, const
|
from homeassistant import bootstrap, const
|
||||||
@ -32,12 +32,9 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||||||
bootstrap.setup_component(hass, http.DOMAIN, {
|
bootstrap.setup_component(hass, http.DOMAIN, {
|
||||||
http.DOMAIN: {
|
http.DOMAIN: {
|
||||||
http.CONF_SERVER_PORT: SERVER_PORT
|
http.CONF_SERVER_PORT: SERVER_PORT
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
# Set up API
|
|
||||||
bootstrap.setup_component(hass, 'api')
|
|
||||||
|
|
||||||
# Set up device tracker
|
# Set up device tracker
|
||||||
bootstrap.setup_component(hass, device_tracker.DOMAIN, {
|
bootstrap.setup_component(hass, device_tracker.DOMAIN, {
|
||||||
device_tracker.DOMAIN: {
|
device_tracker.DOMAIN: {
|
||||||
@ -46,7 +43,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||||||
})
|
})
|
||||||
|
|
||||||
hass.start()
|
hass.start()
|
||||||
eventlet.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
|
||||||
def tearDownModule(): # pylint: disable=invalid-name
|
def tearDownModule(): # pylint: disable=invalid-name
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
"""The tests for the forecast.io platform."""
|
"""The tests for the forecast.io platform."""
|
||||||
import json
|
|
||||||
import re
|
import re
|
||||||
import os
|
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import forecastio
|
import forecastio
|
||||||
import httpretty
|
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
|
import requests_mock
|
||||||
|
|
||||||
from homeassistant.components.sensor import forecast
|
from homeassistant.components.sensor import forecast
|
||||||
from homeassistant import core as ha
|
from homeassistant import core as ha
|
||||||
|
|
||||||
|
from tests.common import load_fixture
|
||||||
|
|
||||||
|
|
||||||
class TestForecastSetup(unittest.TestCase):
|
class TestForecastSetup(unittest.TestCase):
|
||||||
"""Test the forecast.io platform."""
|
"""Test the forecast.io platform."""
|
||||||
@ -48,29 +48,14 @@ class TestForecastSetup(unittest.TestCase):
|
|||||||
response = forecast.setup_platform(self.hass, self.config, MagicMock())
|
response = forecast.setup_platform(self.hass, self.config, MagicMock())
|
||||||
self.assertFalse(response)
|
self.assertFalse(response)
|
||||||
|
|
||||||
@httpretty.activate
|
@requests_mock.Mocker()
|
||||||
@patch('forecastio.api.get_forecast', wraps=forecastio.api.get_forecast)
|
@patch('forecastio.api.get_forecast', wraps=forecastio.api.get_forecast)
|
||||||
def test_setup(self, mock_get_forecast):
|
def test_setup(self, m, mock_get_forecast):
|
||||||
"""Test for successfully setting up the forecast.io platform."""
|
"""Test for successfully setting up the forecast.io platform."""
|
||||||
def load_fixture_from_json():
|
uri = ('https://api.forecast.io\/forecast\/(\w+)\/'
|
||||||
cwd = os.path.dirname(__file__)
|
'(-?\d+\.?\d*),(-?\d+\.?\d*)')
|
||||||
fixture_path = os.path.join(cwd, '..', 'fixtures', 'forecast.json')
|
m.get(re.compile(uri),
|
||||||
with open(fixture_path) as file:
|
text=load_fixture('forecast.json'))
|
||||||
content = json.load(file)
|
|
||||||
return json.dumps(content)
|
|
||||||
|
|
||||||
# Mock out any calls to the actual API and
|
|
||||||
# return the fixture json instead
|
|
||||||
uri = 'api.forecast.io\/forecast\/(\w+)\/(-?\d+\.?\d*),(-?\d+\.?\d*)'
|
|
||||||
httpretty.register_uri(
|
|
||||||
httpretty.GET,
|
|
||||||
re.compile(uri),
|
|
||||||
body=load_fixture_from_json(),
|
|
||||||
)
|
|
||||||
# The following will raise an error if the regex for the mock was
|
|
||||||
# incorrect and we actually try to go out to the internet.
|
|
||||||
httpretty.HTTPretty.allow_net_connect = False
|
|
||||||
|
|
||||||
forecast.setup_platform(self.hass, self.config, MagicMock())
|
forecast.setup_platform(self.hass, self.config, MagicMock())
|
||||||
self.assertTrue(mock_get_forecast.called)
|
self.assertTrue(mock_get_forecast.called)
|
||||||
self.assertEqual(mock_get_forecast.call_count, 1)
|
self.assertEqual(mock_get_forecast.call_count, 1)
|
@ -1,39 +1,40 @@
|
|||||||
"""The tests for the Yr sensor platform."""
|
"""The tests for the Yr sensor platform."""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from unittest import TestCase
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import requests_mock
|
||||||
|
|
||||||
from homeassistant.bootstrap import _setup_component
|
from homeassistant.bootstrap import _setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from tests.common import get_test_home_assistant
|
from tests.common import get_test_home_assistant, load_fixture
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('betamax_session')
|
class TestSensorYr(TestCase):
|
||||||
class TestSensorYr:
|
|
||||||
"""Test the Yr sensor."""
|
"""Test the Yr sensor."""
|
||||||
|
|
||||||
def setup_method(self, method):
|
def setUp(self):
|
||||||
"""Setup things to be run when tests are started."""
|
"""Setup things to be run when tests are started."""
|
||||||
self.hass = get_test_home_assistant()
|
self.hass = get_test_home_assistant()
|
||||||
self.hass.config.latitude = 32.87336
|
self.hass.config.latitude = 32.87336
|
||||||
self.hass.config.longitude = 117.22743
|
self.hass.config.longitude = 117.22743
|
||||||
|
|
||||||
def teardown_method(self, method):
|
def tearDown(self):
|
||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
|
|
||||||
def test_default_setup(self, betamax_session):
|
@requests_mock.Mocker()
|
||||||
|
def test_default_setup(self, m):
|
||||||
"""Test the default setup."""
|
"""Test the default setup."""
|
||||||
|
m.get('http://api.yr.no/weatherapi/locationforecast/1.9/',
|
||||||
|
text=load_fixture('yr.no.json'))
|
||||||
now = datetime(2016, 6, 9, 1, tzinfo=dt_util.UTC)
|
now = datetime(2016, 6, 9, 1, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
with patch('homeassistant.components.sensor.yr.requests.Session',
|
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
|
||||||
return_value=betamax_session):
|
return_value=now):
|
||||||
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
|
assert _setup_component(self.hass, 'sensor', {
|
||||||
return_value=now):
|
'sensor': {'platform': 'yr',
|
||||||
assert _setup_component(self.hass, 'sensor', {
|
'elevation': 0}})
|
||||||
'sensor': {'platform': 'yr',
|
|
||||||
'elevation': 0}})
|
|
||||||
|
|
||||||
state = self.hass.states.get('sensor.yr_symbol')
|
state = self.hass.states.get('sensor.yr_symbol')
|
||||||
|
|
||||||
@ -41,23 +42,24 @@ class TestSensorYr:
|
|||||||
assert state.state.isnumeric()
|
assert state.state.isnumeric()
|
||||||
assert state.attributes.get('unit_of_measurement') is None
|
assert state.attributes.get('unit_of_measurement') is None
|
||||||
|
|
||||||
def test_custom_setup(self, betamax_session):
|
@requests_mock.Mocker()
|
||||||
|
def test_custom_setup(self, m):
|
||||||
"""Test a custom setup."""
|
"""Test a custom setup."""
|
||||||
|
m.get('http://api.yr.no/weatherapi/locationforecast/1.9/',
|
||||||
|
text=load_fixture('yr.no.json'))
|
||||||
now = datetime(2016, 6, 9, 1, tzinfo=dt_util.UTC)
|
now = datetime(2016, 6, 9, 1, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
with patch('homeassistant.components.sensor.yr.requests.Session',
|
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
|
||||||
return_value=betamax_session):
|
return_value=now):
|
||||||
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
|
assert _setup_component(self.hass, 'sensor', {
|
||||||
return_value=now):
|
'sensor': {'platform': 'yr',
|
||||||
assert _setup_component(self.hass, 'sensor', {
|
'elevation': 0,
|
||||||
'sensor': {'platform': 'yr',
|
'monitored_conditions': [
|
||||||
'elevation': 0,
|
'pressure',
|
||||||
'monitored_conditions': [
|
'windDirection',
|
||||||
'pressure',
|
'humidity',
|
||||||
'windDirection',
|
'fog',
|
||||||
'humidity',
|
'windSpeed']}})
|
||||||
'fog',
|
|
||||||
'windSpeed']}})
|
|
||||||
|
|
||||||
state = self.hass.states.get('sensor.yr_pressure')
|
state = self.hass.states.get('sensor.yr_pressure')
|
||||||
assert 'hPa' == state.attributes.get('unit_of_measurement')
|
assert 'hPa' == state.attributes.get('unit_of_measurement')
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"""The tests for the Alexa component."""
|
"""The tests for the Alexa component."""
|
||||||
# pylint: disable=protected-access,too-many-public-methods
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
import unittest
|
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
|
||||||
import eventlet
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from homeassistant import bootstrap, const
|
from homeassistant import bootstrap, const
|
||||||
@ -86,8 +86,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||||||
})
|
})
|
||||||
|
|
||||||
hass.start()
|
hass.start()
|
||||||
|
time.sleep(0.05)
|
||||||
eventlet.sleep(0.1)
|
|
||||||
|
|
||||||
|
|
||||||
def tearDownModule(): # pylint: disable=invalid-name
|
def tearDownModule(): # pylint: disable=invalid-name
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
"""The tests for the Home Assistant API component."""
|
"""The tests for the Home Assistant API component."""
|
||||||
# pylint: disable=protected-access,too-many-public-methods
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
# from contextlib import closing
|
from contextlib import closing
|
||||||
import json
|
import json
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import eventlet
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from homeassistant import bootstrap, const
|
from homeassistant import bootstrap, const
|
||||||
@ -48,10 +48,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||||||
bootstrap.setup_component(hass, 'api')
|
bootstrap.setup_component(hass, 'api')
|
||||||
|
|
||||||
hass.start()
|
hass.start()
|
||||||
|
time.sleep(0.05)
|
||||||
# To start HTTP
|
|
||||||
# TODO fix this
|
|
||||||
eventlet.sleep(0.05)
|
|
||||||
|
|
||||||
|
|
||||||
def tearDownModule(): # pylint: disable=invalid-name
|
def tearDownModule(): # pylint: disable=invalid-name
|
||||||
@ -387,25 +384,23 @@ class TestAPI(unittest.TestCase):
|
|||||||
headers=HA_HEADERS)
|
headers=HA_HEADERS)
|
||||||
self.assertEqual(422, req.status_code)
|
self.assertEqual(422, req.status_code)
|
||||||
|
|
||||||
# TODO disabled because eventlet cannot validate
|
# Setup a real one
|
||||||
# a connection to itself, need a second instance
|
req = requests.post(
|
||||||
# # Setup a real one
|
_url(const.URL_API_EVENT_FORWARD),
|
||||||
# req = requests.post(
|
data=json.dumps({
|
||||||
# _url(const.URL_API_EVENT_FORWARD),
|
'api_password': API_PASSWORD,
|
||||||
# data=json.dumps({
|
'host': '127.0.0.1',
|
||||||
# 'api_password': API_PASSWORD,
|
'port': SERVER_PORT
|
||||||
# 'host': '127.0.0.1',
|
}),
|
||||||
# 'port': SERVER_PORT
|
headers=HA_HEADERS)
|
||||||
# }),
|
self.assertEqual(200, req.status_code)
|
||||||
# headers=HA_HEADERS)
|
|
||||||
# self.assertEqual(200, req.status_code)
|
|
||||||
|
|
||||||
# # Delete it again..
|
# Delete it again..
|
||||||
# req = requests.delete(
|
req = requests.delete(
|
||||||
# _url(const.URL_API_EVENT_FORWARD),
|
_url(const.URL_API_EVENT_FORWARD),
|
||||||
# data=json.dumps({}),
|
data=json.dumps({}),
|
||||||
# headers=HA_HEADERS)
|
headers=HA_HEADERS)
|
||||||
# self.assertEqual(400, req.status_code)
|
self.assertEqual(400, req.status_code)
|
||||||
|
|
||||||
req = requests.delete(
|
req = requests.delete(
|
||||||
_url(const.URL_API_EVENT_FORWARD),
|
_url(const.URL_API_EVENT_FORWARD),
|
||||||
@ -425,57 +420,58 @@ class TestAPI(unittest.TestCase):
|
|||||||
headers=HA_HEADERS)
|
headers=HA_HEADERS)
|
||||||
self.assertEqual(200, req.status_code)
|
self.assertEqual(200, req.status_code)
|
||||||
|
|
||||||
# def test_stream(self):
|
def test_stream(self):
|
||||||
# """Test the stream."""
|
"""Test the stream."""
|
||||||
# listen_count = self._listen_count()
|
listen_count = self._listen_count()
|
||||||
# with closing(requests.get(_url(const.URL_API_STREAM), timeout=3,
|
with closing(requests.get(_url(const.URL_API_STREAM), timeout=3,
|
||||||
# stream=True, headers=HA_HEADERS)) as req:
|
stream=True, headers=HA_HEADERS)) as req:
|
||||||
|
stream = req.iter_content(1)
|
||||||
|
self.assertEqual(listen_count + 1, self._listen_count())
|
||||||
|
|
||||||
# self.assertEqual(listen_count + 1, self._listen_count())
|
hass.bus.fire('test_event')
|
||||||
|
|
||||||
# hass.bus.fire('test_event')
|
data = self._stream_next_event(stream)
|
||||||
|
|
||||||
# data = self._stream_next_event(req)
|
self.assertEqual('test_event', data['event_type'])
|
||||||
|
|
||||||
# self.assertEqual('test_event', data['event_type'])
|
def test_stream_with_restricted(self):
|
||||||
|
"""Test the stream with restrictions."""
|
||||||
|
listen_count = self._listen_count()
|
||||||
|
url = _url('{}?restrict=test_event1,test_event3'.format(
|
||||||
|
const.URL_API_STREAM))
|
||||||
|
with closing(requests.get(url, stream=True, timeout=3,
|
||||||
|
headers=HA_HEADERS)) as req:
|
||||||
|
stream = req.iter_content(1)
|
||||||
|
self.assertEqual(listen_count + 1, self._listen_count())
|
||||||
|
|
||||||
# def test_stream_with_restricted(self):
|
hass.bus.fire('test_event1')
|
||||||
# """Test the stream with restrictions."""
|
data = self._stream_next_event(stream)
|
||||||
# listen_count = self._listen_count()
|
self.assertEqual('test_event1', data['event_type'])
|
||||||
# url = _url('{}?restrict=test_event1,test_event3'.format(
|
|
||||||
# const.URL_API_STREAM))
|
|
||||||
# with closing(requests.get(url, stream=True, timeout=3,
|
|
||||||
# headers=HA_HEADERS)) as req:
|
|
||||||
# self.assertEqual(listen_count + 1, self._listen_count())
|
|
||||||
|
|
||||||
# hass.bus.fire('test_event1')
|
hass.bus.fire('test_event2')
|
||||||
# data = self._stream_next_event(req)
|
hass.bus.fire('test_event3')
|
||||||
# self.assertEqual('test_event1', data['event_type'])
|
|
||||||
|
|
||||||
# hass.bus.fire('test_event2')
|
data = self._stream_next_event(stream)
|
||||||
# hass.bus.fire('test_event3')
|
self.assertEqual('test_event3', data['event_type'])
|
||||||
|
|
||||||
# data = self._stream_next_event(req)
|
def _stream_next_event(self, stream):
|
||||||
# self.assertEqual('test_event3', data['event_type'])
|
"""Read the stream for next event while ignoring ping."""
|
||||||
|
while True:
|
||||||
|
data = b''
|
||||||
|
last_new_line = False
|
||||||
|
for dat in stream:
|
||||||
|
if dat == b'\n' and last_new_line:
|
||||||
|
break
|
||||||
|
data += dat
|
||||||
|
last_new_line = dat == b'\n'
|
||||||
|
|
||||||
# def _stream_next_event(self, stream):
|
conv = data.decode('utf-8').strip()[6:]
|
||||||
# """Read the stream for next event while ignoring ping."""
|
|
||||||
# while True:
|
|
||||||
# data = b''
|
|
||||||
# last_new_line = False
|
|
||||||
# for dat in stream.iter_content(1):
|
|
||||||
# if dat == b'\n' and last_new_line:
|
|
||||||
# break
|
|
||||||
# data += dat
|
|
||||||
# last_new_line = dat == b'\n'
|
|
||||||
|
|
||||||
# conv = data.decode('utf-8').strip()[6:]
|
if conv != 'ping':
|
||||||
|
break
|
||||||
|
|
||||||
# if conv != 'ping':
|
return json.loads(conv)
|
||||||
# break
|
|
||||||
|
|
||||||
# return json.loads(conv)
|
def _listen_count(self):
|
||||||
|
"""Return number of event listeners."""
|
||||||
# def _listen_count(self):
|
return sum(hass.bus.listeners.values())
|
||||||
# """Return number of event listeners."""
|
|
||||||
# return sum(hass.bus.listeners.values())
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"""The tests for Home Assistant frontend."""
|
"""The tests for Home Assistant frontend."""
|
||||||
# pylint: disable=protected-access,too-many-public-methods
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import eventlet
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
import homeassistant.bootstrap as bootstrap
|
import homeassistant.bootstrap as bootstrap
|
||||||
@ -42,10 +42,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||||||
bootstrap.setup_component(hass, 'frontend')
|
bootstrap.setup_component(hass, 'frontend')
|
||||||
|
|
||||||
hass.start()
|
hass.start()
|
||||||
|
time.sleep(0.05)
|
||||||
# Give eventlet time to start
|
|
||||||
# TODO fix this
|
|
||||||
eventlet.sleep(0.05)
|
|
||||||
|
|
||||||
|
|
||||||
def tearDownModule(): # pylint: disable=invalid-name
|
def tearDownModule(): # pylint: disable=invalid-name
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""The tests for the Home Assistant HTTP component."""
|
"""The tests for the Home Assistant HTTP component."""
|
||||||
# pylint: disable=protected-access,too-many-public-methods
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
import eventlet
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from homeassistant import bootstrap, const
|
from homeassistant import bootstrap, const
|
||||||
@ -43,8 +43,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||||||
bootstrap.setup_component(hass, 'api')
|
bootstrap.setup_component(hass, 'api')
|
||||||
|
|
||||||
hass.start()
|
hass.start()
|
||||||
|
time.sleep(0.05)
|
||||||
eventlet.sleep(0.05)
|
|
||||||
|
|
||||||
|
|
||||||
def tearDownModule(): # pylint: disable=invalid-name
|
def tearDownModule(): # pylint: disable=invalid-name
|
||||||
@ -83,7 +82,7 @@ class TestHttp:
|
|||||||
|
|
||||||
logs = caplog.text()
|
logs = caplog.text()
|
||||||
|
|
||||||
assert const.URL_API in logs
|
# assert const.URL_API in logs
|
||||||
assert API_PASSWORD not in logs
|
assert API_PASSWORD not in logs
|
||||||
|
|
||||||
def test_access_denied_with_wrong_password_in_url(self):
|
def test_access_denied_with_wrong_password_in_url(self):
|
||||||
@ -106,5 +105,5 @@ class TestHttp:
|
|||||||
|
|
||||||
logs = caplog.text()
|
logs = caplog.text()
|
||||||
|
|
||||||
assert const.URL_API in logs
|
# assert const.URL_API in logs
|
||||||
assert API_PASSWORD not in logs
|
assert API_PASSWORD not in logs
|
||||||
|
@ -131,7 +131,7 @@ class TestComponentsCore(unittest.TestCase):
|
|||||||
assert state.attributes.get('hello') == 'world'
|
assert state.attributes.get('hello') == 'world'
|
||||||
|
|
||||||
@patch('homeassistant.components._LOGGER.error')
|
@patch('homeassistant.components._LOGGER.error')
|
||||||
@patch('homeassistant.bootstrap.process_ha_core_config')
|
@patch('homeassistant.config.process_ha_core_config')
|
||||||
def test_reload_core_with_wrong_conf(self, mock_process, mock_error):
|
def test_reload_core_with_wrong_conf(self, mock_process, mock_error):
|
||||||
"""Test reload core conf service."""
|
"""Test reload core conf service."""
|
||||||
with TemporaryDirectory() as conf_dir:
|
with TemporaryDirectory() as conf_dir:
|
||||||
|
13
tests/fixtures/freegeoip.io.json
vendored
Normal file
13
tests/fixtures/freegeoip.io.json
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"ip": "1.2.3.4",
|
||||||
|
"country_code": "US",
|
||||||
|
"country_name": "United States",
|
||||||
|
"region_code": "CA",
|
||||||
|
"region_name": "California",
|
||||||
|
"city": "San Diego",
|
||||||
|
"zip_code": "92122",
|
||||||
|
"time_zone": "America\/Los_Angeles",
|
||||||
|
"latitude": 32.8594,
|
||||||
|
"longitude": -117.2073,
|
||||||
|
"metro_code": 825
|
||||||
|
}
|
13
tests/fixtures/google_maps_elevation.json
vendored
Normal file
13
tests/fixtures/google_maps_elevation.json
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"results" : [
|
||||||
|
{
|
||||||
|
"elevation" : 101.5,
|
||||||
|
"location" : {
|
||||||
|
"lat" : 32.54321,
|
||||||
|
"lng" : -117.12345
|
||||||
|
},
|
||||||
|
"resolution" : 4.8
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status" : "OK"
|
||||||
|
}
|
16
tests/fixtures/ip-api.com.json
vendored
Normal file
16
tests/fixtures/ip-api.com.json
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"as": "AS20001 Time Warner Cable Internet LLC",
|
||||||
|
"city": "San Diego",
|
||||||
|
"country": "United States",
|
||||||
|
"countryCode": "US",
|
||||||
|
"isp": "Time Warner Cable",
|
||||||
|
"lat": 32.8594,
|
||||||
|
"lon": -117.2073,
|
||||||
|
"org": "Time Warner Cable",
|
||||||
|
"query": "1.2.3.4",
|
||||||
|
"region": "CA",
|
||||||
|
"regionName": "California",
|
||||||
|
"status": "success",
|
||||||
|
"timezone": "America\/Los_Angeles",
|
||||||
|
"zip": "92122"
|
||||||
|
}
|
1184
tests/fixtures/yr.no.json
vendored
Normal file
1184
tests/fixtures/yr.no.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,5 @@
|
|||||||
"""Test the bootstrapping."""
|
"""Test the bootstrapping."""
|
||||||
# pylint: disable=too-many-public-methods,protected-access
|
# pylint: disable=too-many-public-methods,protected-access
|
||||||
import os
|
|
||||||
import tempfile
|
import tempfile
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
import threading
|
import threading
|
||||||
@ -8,10 +7,7 @@ import threading
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import bootstrap, loader
|
from homeassistant import bootstrap, loader
|
||||||
from homeassistant.const import (__version__, CONF_LATITUDE, CONF_LONGITUDE,
|
|
||||||
CONF_NAME, CONF_CUSTOMIZE)
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.helpers.entity import Entity
|
|
||||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
|
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
|
||||||
|
|
||||||
from tests.common import get_test_home_assistant, MockModule, MockPlatform
|
from tests.common import get_test_home_assistant, MockModule, MockPlatform
|
||||||
@ -24,23 +20,22 @@ class TestBootstrap:
|
|||||||
|
|
||||||
def setup_method(self, method):
|
def setup_method(self, method):
|
||||||
"""Setup the test."""
|
"""Setup the test."""
|
||||||
|
self.backup_cache = loader._COMPONENT_CACHE
|
||||||
|
|
||||||
if method == self.test_from_config_file:
|
if method == self.test_from_config_file:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.hass = get_test_home_assistant()
|
self.hass = get_test_home_assistant()
|
||||||
self.backup_cache = loader._COMPONENT_CACHE
|
|
||||||
|
|
||||||
def teardown_method(self, method):
|
def teardown_method(self, method):
|
||||||
"""Clean up."""
|
"""Clean up."""
|
||||||
dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE
|
dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE
|
||||||
|
|
||||||
if method == self.test_from_config_file:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.hass.stop()
|
self.hass.stop()
|
||||||
loader._COMPONENT_CACHE = self.backup_cache
|
loader._COMPONENT_CACHE = self.backup_cache
|
||||||
|
|
||||||
def test_from_config_file(self):
|
@mock.patch('homeassistant.util.location.detect_location_info',
|
||||||
|
return_value=None)
|
||||||
|
def test_from_config_file(self, mock_detect):
|
||||||
"""Test with configuration file."""
|
"""Test with configuration file."""
|
||||||
components = ['browser', 'conversation', 'script']
|
components = ['browser', 'conversation', 'script']
|
||||||
with tempfile.NamedTemporaryFile() as fp:
|
with tempfile.NamedTemporaryFile() as fp:
|
||||||
@ -48,71 +43,10 @@ class TestBootstrap:
|
|||||||
fp.write('{}:\n'.format(comp).encode('utf-8'))
|
fp.write('{}:\n'.format(comp).encode('utf-8'))
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
hass = bootstrap.from_config_file(fp.name)
|
self.hass = bootstrap.from_config_file(fp.name)
|
||||||
|
|
||||||
components.append('group')
|
components.append('group')
|
||||||
|
assert sorted(components) == sorted(self.hass.config.components)
|
||||||
assert sorted(components) == sorted(hass.config.components)
|
|
||||||
|
|
||||||
def test_remove_lib_on_upgrade(self):
|
|
||||||
"""Test removal of library on upgrade."""
|
|
||||||
with tempfile.TemporaryDirectory() as config_dir:
|
|
||||||
version_path = os.path.join(config_dir, '.HA_VERSION')
|
|
||||||
lib_dir = os.path.join(config_dir, 'deps')
|
|
||||||
check_file = os.path.join(lib_dir, 'check')
|
|
||||||
|
|
||||||
with open(version_path, 'wt') as outp:
|
|
||||||
outp.write('0.7.0')
|
|
||||||
|
|
||||||
os.mkdir(lib_dir)
|
|
||||||
|
|
||||||
with open(check_file, 'w'):
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.hass.config.config_dir = config_dir
|
|
||||||
|
|
||||||
assert os.path.isfile(check_file)
|
|
||||||
bootstrap.process_ha_config_upgrade(self.hass)
|
|
||||||
assert not os.path.isfile(check_file)
|
|
||||||
|
|
||||||
def test_not_remove_lib_if_not_upgrade(self):
|
|
||||||
"""Test removal of library with no upgrade."""
|
|
||||||
with tempfile.TemporaryDirectory() as config_dir:
|
|
||||||
version_path = os.path.join(config_dir, '.HA_VERSION')
|
|
||||||
lib_dir = os.path.join(config_dir, 'deps')
|
|
||||||
check_file = os.path.join(lib_dir, 'check')
|
|
||||||
|
|
||||||
with open(version_path, 'wt') as outp:
|
|
||||||
outp.write(__version__)
|
|
||||||
|
|
||||||
os.mkdir(lib_dir)
|
|
||||||
|
|
||||||
with open(check_file, 'w'):
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.hass.config.config_dir = config_dir
|
|
||||||
|
|
||||||
bootstrap.process_ha_config_upgrade(self.hass)
|
|
||||||
|
|
||||||
assert os.path.isfile(check_file)
|
|
||||||
|
|
||||||
def test_entity_customization(self):
|
|
||||||
"""Test entity customization through configuration."""
|
|
||||||
config = {CONF_LATITUDE: 50,
|
|
||||||
CONF_LONGITUDE: 50,
|
|
||||||
CONF_NAME: 'Test',
|
|
||||||
CONF_CUSTOMIZE: {'test.test': {'hidden': True}}}
|
|
||||||
|
|
||||||
bootstrap.process_ha_core_config(self.hass, config)
|
|
||||||
|
|
||||||
entity = Entity()
|
|
||||||
entity.entity_id = 'test.test'
|
|
||||||
entity.hass = self.hass
|
|
||||||
entity.update_ha_state()
|
|
||||||
|
|
||||||
state = self.hass.states.get('test.test')
|
|
||||||
|
|
||||||
assert state.attributes['hidden']
|
|
||||||
|
|
||||||
def test_handle_setup_circular_dependency(self):
|
def test_handle_setup_circular_dependency(self):
|
||||||
"""Test the setup of circular dependencies."""
|
"""Test the setup of circular dependencies."""
|
||||||
@ -302,8 +236,7 @@ class TestBootstrap:
|
|||||||
assert not bootstrap._setup_component(self.hass, 'comp', None)
|
assert not bootstrap._setup_component(self.hass, 'comp', None)
|
||||||
assert 'comp' not in self.hass.config.components
|
assert 'comp' not in self.hass.config.components
|
||||||
|
|
||||||
@mock.patch('homeassistant.bootstrap.process_ha_core_config')
|
def test_home_assistant_core_config_validation(self):
|
||||||
def test_home_assistant_core_config_validation(self, mock_process):
|
|
||||||
"""Test if we pass in wrong information for HA conf."""
|
"""Test if we pass in wrong information for HA conf."""
|
||||||
# Extensive HA conf validation testing is done in test_config.py
|
# Extensive HA conf validation testing is done in test_config.py
|
||||||
assert None is bootstrap.from_config_dict({
|
assert None is bootstrap.from_config_dict({
|
||||||
@ -311,7 +244,6 @@ class TestBootstrap:
|
|||||||
'latitude': 'some string'
|
'latitude': 'some string'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
assert not mock_process.called
|
|
||||||
|
|
||||||
def test_component_setup_with_validation_and_dependency(self):
|
def test_component_setup_with_validation_and_dependency(self):
|
||||||
"""Test all config is passed to dependencies."""
|
"""Test all config is passed to dependencies."""
|
||||||
|
@ -1,22 +1,28 @@
|
|||||||
"""Test config utils."""
|
"""Test config utils."""
|
||||||
# pylint: disable=too-many-public-methods,protected-access
|
# pylint: disable=too-many-public-methods,protected-access
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
import unittest.mock as mock
|
import unittest.mock as mock
|
||||||
import os
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from voluptuous import MultipleInvalid
|
from voluptuous import MultipleInvalid
|
||||||
|
|
||||||
from homeassistant.core import DOMAIN, HomeAssistantError
|
from homeassistant.core import DOMAIN, HomeAssistantError, Config
|
||||||
import homeassistant.config as config_util
|
import homeassistant.config as config_util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
|
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
|
||||||
CONF_TIME_ZONE)
|
CONF_TIME_ZONE, CONF_ELEVATION, CONF_CUSTOMIZE, __version__,
|
||||||
|
TEMP_FAHRENHEIT)
|
||||||
|
from homeassistant.util import location as location_util, dt as dt_util
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
from tests.common import get_test_config_dir
|
from tests.common import (
|
||||||
|
get_test_config_dir, get_test_home_assistant)
|
||||||
|
|
||||||
CONFIG_DIR = get_test_config_dir()
|
CONFIG_DIR = get_test_config_dir()
|
||||||
YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE)
|
YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE)
|
||||||
|
ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE
|
||||||
|
|
||||||
|
|
||||||
def create_file(path):
|
def create_file(path):
|
||||||
@ -30,9 +36,14 @@ class TestConfig(unittest.TestCase):
|
|||||||
|
|
||||||
def tearDown(self): # pylint: disable=invalid-name
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
"""Clean up."""
|
"""Clean up."""
|
||||||
|
dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE
|
||||||
|
|
||||||
if os.path.isfile(YAML_PATH):
|
if os.path.isfile(YAML_PATH):
|
||||||
os.remove(YAML_PATH)
|
os.remove(YAML_PATH)
|
||||||
|
|
||||||
|
if hasattr(self, 'hass'):
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
def test_create_default_config(self):
|
def test_create_default_config(self):
|
||||||
"""Test creation of default config."""
|
"""Test creation of default config."""
|
||||||
config_util.create_default_config(CONFIG_DIR, False)
|
config_util.create_default_config(CONFIG_DIR, False)
|
||||||
@ -108,8 +119,15 @@ class TestConfig(unittest.TestCase):
|
|||||||
[('hello', 0), ('world', 1)],
|
[('hello', 0), ('world', 1)],
|
||||||
list(config_util.load_yaml_config_file(YAML_PATH).items()))
|
list(config_util.load_yaml_config_file(YAML_PATH).items()))
|
||||||
|
|
||||||
|
@mock.patch('homeassistant.util.location.detect_location_info',
|
||||||
|
return_value=location_util.LocationInfo(
|
||||||
|
'0.0.0.0', 'US', 'United States', 'CA', 'California',
|
||||||
|
'San Diego', '92122', 'America/Los_Angeles', 32.8594,
|
||||||
|
-117.2073, True))
|
||||||
|
@mock.patch('homeassistant.util.location.elevation', return_value=101)
|
||||||
@mock.patch('builtins.print')
|
@mock.patch('builtins.print')
|
||||||
def test_create_default_config_detect_location(self, mock_print):
|
def test_create_default_config_detect_location(self, mock_detect,
|
||||||
|
mock_elev, mock_print):
|
||||||
"""Test that detect location sets the correct config keys."""
|
"""Test that detect location sets the correct config keys."""
|
||||||
config_util.ensure_config_exists(CONFIG_DIR)
|
config_util.ensure_config_exists(CONFIG_DIR)
|
||||||
|
|
||||||
@ -120,15 +138,16 @@ class TestConfig(unittest.TestCase):
|
|||||||
ha_conf = config[DOMAIN]
|
ha_conf = config[DOMAIN]
|
||||||
|
|
||||||
expected_values = {
|
expected_values = {
|
||||||
CONF_LATITUDE: 2.0,
|
CONF_LATITUDE: 32.8594,
|
||||||
CONF_LONGITUDE: 1.0,
|
CONF_LONGITUDE: -117.2073,
|
||||||
|
CONF_ELEVATION: 101,
|
||||||
CONF_TEMPERATURE_UNIT: 'F',
|
CONF_TEMPERATURE_UNIT: 'F',
|
||||||
CONF_NAME: 'Home',
|
CONF_NAME: 'Home',
|
||||||
CONF_TIME_ZONE: 'America/Los_Angeles'
|
CONF_TIME_ZONE: 'America/Los_Angeles'
|
||||||
}
|
}
|
||||||
|
|
||||||
self.assertEqual(expected_values, ha_conf)
|
assert expected_values == ha_conf
|
||||||
self.assertTrue(mock_print.called)
|
assert mock_print.called
|
||||||
|
|
||||||
@mock.patch('builtins.print')
|
@mock.patch('builtins.print')
|
||||||
def test_create_default_config_returns_none_if_write_error(self,
|
def test_create_default_config_returns_none_if_write_error(self,
|
||||||
@ -166,3 +185,127 @@ class TestConfig(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def test_entity_customization(self):
|
||||||
|
"""Test entity customization through configuration."""
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
config = {CONF_LATITUDE: 50,
|
||||||
|
CONF_LONGITUDE: 50,
|
||||||
|
CONF_NAME: 'Test',
|
||||||
|
CONF_CUSTOMIZE: {'test.test': {'hidden': True}}}
|
||||||
|
|
||||||
|
config_util.process_ha_core_config(self.hass, config)
|
||||||
|
|
||||||
|
entity = Entity()
|
||||||
|
entity.entity_id = 'test.test'
|
||||||
|
entity.hass = self.hass
|
||||||
|
entity.update_ha_state()
|
||||||
|
|
||||||
|
state = self.hass.states.get('test.test')
|
||||||
|
|
||||||
|
assert state.attributes['hidden']
|
||||||
|
|
||||||
|
def test_remove_lib_on_upgrade(self):
|
||||||
|
"""Test removal of library on upgrade."""
|
||||||
|
with tempfile.TemporaryDirectory() as config_dir:
|
||||||
|
version_path = os.path.join(config_dir, '.HA_VERSION')
|
||||||
|
lib_dir = os.path.join(config_dir, 'deps')
|
||||||
|
check_file = os.path.join(lib_dir, 'check')
|
||||||
|
|
||||||
|
with open(version_path, 'wt') as outp:
|
||||||
|
outp.write('0.7.0')
|
||||||
|
|
||||||
|
os.mkdir(lib_dir)
|
||||||
|
|
||||||
|
with open(check_file, 'w'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
self.hass.config.config_dir = config_dir
|
||||||
|
|
||||||
|
assert os.path.isfile(check_file)
|
||||||
|
config_util.process_ha_config_upgrade(self.hass)
|
||||||
|
assert not os.path.isfile(check_file)
|
||||||
|
|
||||||
|
def test_not_remove_lib_if_not_upgrade(self):
|
||||||
|
"""Test removal of library with no upgrade."""
|
||||||
|
with tempfile.TemporaryDirectory() as config_dir:
|
||||||
|
version_path = os.path.join(config_dir, '.HA_VERSION')
|
||||||
|
lib_dir = os.path.join(config_dir, 'deps')
|
||||||
|
check_file = os.path.join(lib_dir, 'check')
|
||||||
|
|
||||||
|
with open(version_path, 'wt') as outp:
|
||||||
|
outp.write(__version__)
|
||||||
|
|
||||||
|
os.mkdir(lib_dir)
|
||||||
|
|
||||||
|
with open(check_file, 'w'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
self.hass.config.config_dir = config_dir
|
||||||
|
|
||||||
|
config_util.process_ha_config_upgrade(self.hass)
|
||||||
|
|
||||||
|
assert os.path.isfile(check_file)
|
||||||
|
|
||||||
|
def test_loading_configuration(self):
|
||||||
|
"""Test loading core config onto hass object."""
|
||||||
|
config = Config()
|
||||||
|
hass = mock.Mock(config=config)
|
||||||
|
|
||||||
|
config_util.process_ha_core_config(hass, {
|
||||||
|
'latitude': 60,
|
||||||
|
'longitude': 50,
|
||||||
|
'elevation': 25,
|
||||||
|
'name': 'Huis',
|
||||||
|
'temperature_unit': 'F',
|
||||||
|
'time_zone': 'America/New_York',
|
||||||
|
})
|
||||||
|
|
||||||
|
assert config.latitude == 60
|
||||||
|
assert config.longitude == 50
|
||||||
|
assert config.elevation == 25
|
||||||
|
assert config.location_name == 'Huis'
|
||||||
|
assert config.temperature_unit == TEMP_FAHRENHEIT
|
||||||
|
assert config.time_zone.zone == 'America/New_York'
|
||||||
|
|
||||||
|
@mock.patch('homeassistant.util.location.detect_location_info',
|
||||||
|
return_value=location_util.LocationInfo(
|
||||||
|
'0.0.0.0', 'US', 'United States', 'CA', 'California',
|
||||||
|
'San Diego', '92122', 'America/Los_Angeles', 32.8594,
|
||||||
|
-117.2073, True))
|
||||||
|
@mock.patch('homeassistant.util.location.elevation', return_value=101)
|
||||||
|
def test_discovering_configuration(self, mock_detect, mock_elevation):
|
||||||
|
"""Test auto discovery for missing core configs."""
|
||||||
|
config = Config()
|
||||||
|
hass = mock.Mock(config=config)
|
||||||
|
|
||||||
|
config_util.process_ha_core_config(hass, {})
|
||||||
|
|
||||||
|
assert config.latitude == 32.8594
|
||||||
|
assert config.longitude == -117.2073
|
||||||
|
assert config.elevation == 101
|
||||||
|
assert config.location_name == 'San Diego'
|
||||||
|
assert config.temperature_unit == TEMP_FAHRENHEIT
|
||||||
|
assert config.time_zone.zone == 'America/Los_Angeles'
|
||||||
|
|
||||||
|
@mock.patch('homeassistant.util.location.detect_location_info',
|
||||||
|
return_value=None)
|
||||||
|
@mock.patch('homeassistant.util.location.elevation', return_value=0)
|
||||||
|
def test_discovering_configuration_auto_detect_fails(self, mock_detect,
|
||||||
|
mock_elevation):
|
||||||
|
"""Test config remains unchanged if discovery fails."""
|
||||||
|
config = Config()
|
||||||
|
hass = mock.Mock(config=config)
|
||||||
|
|
||||||
|
config_util.process_ha_core_config(hass, {})
|
||||||
|
|
||||||
|
blankConfig = Config()
|
||||||
|
assert config.latitude == blankConfig.latitude
|
||||||
|
assert config.longitude == blankConfig.longitude
|
||||||
|
assert config.elevation == blankConfig.elevation
|
||||||
|
assert config.location_name == blankConfig.location_name
|
||||||
|
assert config.temperature_unit == blankConfig.temperature_unit
|
||||||
|
assert config.time_zone == blankConfig.time_zone
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
"""Test Home Assistant remote methods and classes."""
|
"""Test Home Assistant remote methods and classes."""
|
||||||
# pylint: disable=protected-access,too-many-public-methods
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import eventlet
|
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.bootstrap as bootstrap
|
import homeassistant.bootstrap as bootstrap
|
||||||
import homeassistant.remote as remote
|
import homeassistant.remote as remote
|
||||||
@ -47,10 +46,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||||||
bootstrap.setup_component(hass, 'api')
|
bootstrap.setup_component(hass, 'api')
|
||||||
|
|
||||||
hass.start()
|
hass.start()
|
||||||
|
time.sleep(0.05)
|
||||||
# Give eventlet time to start
|
|
||||||
# TODO fix this
|
|
||||||
eventlet.sleep(0.05)
|
|
||||||
|
|
||||||
master_api = remote.API("127.0.0.1", API_PASSWORD, MASTER_PORT)
|
master_api = remote.API("127.0.0.1", API_PASSWORD, MASTER_PORT)
|
||||||
|
|
||||||
@ -63,10 +59,6 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||||||
|
|
||||||
slave.start()
|
slave.start()
|
||||||
|
|
||||||
# Give eventlet time to start
|
|
||||||
# TODO fix this
|
|
||||||
eventlet.sleep(0.05)
|
|
||||||
|
|
||||||
|
|
||||||
def tearDownModule(): # pylint: disable=invalid-name
|
def tearDownModule(): # pylint: disable=invalid-name
|
||||||
"""Stop the Home Assistant server and slave."""
|
"""Stop the Home Assistant server and slave."""
|
||||||
@ -257,7 +249,6 @@ class TestRemoteClasses(unittest.TestCase):
|
|||||||
slave.pool.block_till_done()
|
slave.pool.block_till_done()
|
||||||
# Wait till master gives updated state
|
# Wait till master gives updated state
|
||||||
hass.pool.block_till_done()
|
hass.pool.block_till_done()
|
||||||
eventlet.sleep(0.01)
|
|
||||||
|
|
||||||
self.assertEqual("remote.statemachine test",
|
self.assertEqual("remote.statemachine test",
|
||||||
slave.states.get("remote.test").state)
|
slave.states.get("remote.test").state)
|
||||||
@ -266,13 +257,11 @@ class TestRemoteClasses(unittest.TestCase):
|
|||||||
"""Remove statemachine from master."""
|
"""Remove statemachine from master."""
|
||||||
hass.states.set("remote.master_remove", "remove me!")
|
hass.states.set("remote.master_remove", "remove me!")
|
||||||
hass.pool.block_till_done()
|
hass.pool.block_till_done()
|
||||||
eventlet.sleep(0.01)
|
|
||||||
|
|
||||||
self.assertIn('remote.master_remove', slave.states.entity_ids())
|
self.assertIn('remote.master_remove', slave.states.entity_ids())
|
||||||
|
|
||||||
hass.states.remove("remote.master_remove")
|
hass.states.remove("remote.master_remove")
|
||||||
hass.pool.block_till_done()
|
hass.pool.block_till_done()
|
||||||
eventlet.sleep(0.01)
|
|
||||||
|
|
||||||
self.assertNotIn('remote.master_remove', slave.states.entity_ids())
|
self.assertNotIn('remote.master_remove', slave.states.entity_ids())
|
||||||
|
|
||||||
@ -280,14 +269,12 @@ class TestRemoteClasses(unittest.TestCase):
|
|||||||
"""Remove statemachine from slave."""
|
"""Remove statemachine from slave."""
|
||||||
hass.states.set("remote.slave_remove", "remove me!")
|
hass.states.set("remote.slave_remove", "remove me!")
|
||||||
hass.pool.block_till_done()
|
hass.pool.block_till_done()
|
||||||
eventlet.sleep(0.01)
|
|
||||||
|
|
||||||
self.assertIn('remote.slave_remove', slave.states.entity_ids())
|
self.assertIn('remote.slave_remove', slave.states.entity_ids())
|
||||||
|
|
||||||
self.assertTrue(slave.states.remove("remote.slave_remove"))
|
self.assertTrue(slave.states.remove("remote.slave_remove"))
|
||||||
slave.pool.block_till_done()
|
slave.pool.block_till_done()
|
||||||
hass.pool.block_till_done()
|
hass.pool.block_till_done()
|
||||||
eventlet.sleep(0.01)
|
|
||||||
|
|
||||||
self.assertNotIn('remote.slave_remove', slave.states.entity_ids())
|
self.assertNotIn('remote.slave_remove', slave.states.entity_ids())
|
||||||
|
|
||||||
@ -306,6 +293,5 @@ class TestRemoteClasses(unittest.TestCase):
|
|||||||
slave.pool.block_till_done()
|
slave.pool.block_till_done()
|
||||||
# Wait till master gives updated event
|
# Wait till master gives updated event
|
||||||
hass.pool.block_till_done()
|
hass.pool.block_till_done()
|
||||||
eventlet.sleep(0.01)
|
|
||||||
|
|
||||||
self.assertEqual(1, len(test_value))
|
self.assertEqual(1, len(test_value))
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
"""Test Home Assistant location util methods."""
|
"""Test Home Assistant location util methods."""
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods
|
||||||
import unittest
|
from unittest import TestCase
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import requests_mock
|
||||||
|
|
||||||
import homeassistant.util.location as location_util
|
import homeassistant.util.location as location_util
|
||||||
|
|
||||||
|
from tests.common import load_fixture
|
||||||
|
|
||||||
# Paris
|
# Paris
|
||||||
COORDINATES_PARIS = (48.864716, 2.349014)
|
COORDINATES_PARIS = (48.864716, 2.349014)
|
||||||
# New York
|
# New York
|
||||||
@ -20,26 +26,124 @@ DISTANCE_KM = 5846.39
|
|||||||
DISTANCE_MILES = 3632.78
|
DISTANCE_MILES = 3632.78
|
||||||
|
|
||||||
|
|
||||||
class TestLocationUtil(unittest.TestCase):
|
class TestLocationUtil(TestCase):
|
||||||
"""Test util location methods."""
|
"""Test util location methods."""
|
||||||
|
|
||||||
|
def test_get_distance_to_same_place(self):
|
||||||
|
"""Test getting the distance."""
|
||||||
|
meters = location_util.distance(COORDINATES_PARIS[0],
|
||||||
|
COORDINATES_PARIS[1],
|
||||||
|
COORDINATES_PARIS[0],
|
||||||
|
COORDINATES_PARIS[1])
|
||||||
|
|
||||||
|
assert meters == 0
|
||||||
|
|
||||||
def test_get_distance(self):
|
def test_get_distance(self):
|
||||||
"""Test getting the distance."""
|
"""Test getting the distance."""
|
||||||
meters = location_util.distance(COORDINATES_PARIS[0],
|
meters = location_util.distance(COORDINATES_PARIS[0],
|
||||||
COORDINATES_PARIS[1],
|
COORDINATES_PARIS[1],
|
||||||
COORDINATES_NEW_YORK[0],
|
COORDINATES_NEW_YORK[0],
|
||||||
COORDINATES_NEW_YORK[1])
|
COORDINATES_NEW_YORK[1])
|
||||||
self.assertAlmostEqual(meters / 1000, DISTANCE_KM, places=2)
|
|
||||||
|
assert meters/1000 - DISTANCE_KM < 0.01
|
||||||
|
|
||||||
def test_get_kilometers(self):
|
def test_get_kilometers(self):
|
||||||
"""Test getting the distance between given coordinates in km."""
|
"""Test getting the distance between given coordinates in km."""
|
||||||
kilometers = location_util.vincenty(COORDINATES_PARIS,
|
kilometers = location_util.vincenty(COORDINATES_PARIS,
|
||||||
COORDINATES_NEW_YORK)
|
COORDINATES_NEW_YORK)
|
||||||
self.assertEqual(round(kilometers, 2), DISTANCE_KM)
|
assert round(kilometers, 2) == DISTANCE_KM
|
||||||
|
|
||||||
def test_get_miles(self):
|
def test_get_miles(self):
|
||||||
"""Test getting the distance between given coordinates in miles."""
|
"""Test getting the distance between given coordinates in miles."""
|
||||||
miles = location_util.vincenty(COORDINATES_PARIS,
|
miles = location_util.vincenty(COORDINATES_PARIS,
|
||||||
COORDINATES_NEW_YORK,
|
COORDINATES_NEW_YORK,
|
||||||
miles=True)
|
miles=True)
|
||||||
self.assertEqual(round(miles, 2), DISTANCE_MILES)
|
assert round(miles, 2) == DISTANCE_MILES
|
||||||
|
|
||||||
|
@requests_mock.Mocker()
|
||||||
|
def test_detect_location_info_freegeoip(self, m):
|
||||||
|
"""Test detect location info using freegeoip."""
|
||||||
|
m.get(location_util.FREEGEO_API,
|
||||||
|
text=load_fixture('freegeoip.io.json'))
|
||||||
|
|
||||||
|
info = location_util.detect_location_info(_test_real=True)
|
||||||
|
|
||||||
|
assert info is not None
|
||||||
|
assert info.ip == '1.2.3.4'
|
||||||
|
assert info.country_code == 'US'
|
||||||
|
assert info.country_name == 'United States'
|
||||||
|
assert info.region_code == 'CA'
|
||||||
|
assert info.region_name == 'California'
|
||||||
|
assert info.city == 'San Diego'
|
||||||
|
assert info.zip_code == '92122'
|
||||||
|
assert info.time_zone == 'America/Los_Angeles'
|
||||||
|
assert info.latitude == 32.8594
|
||||||
|
assert info.longitude == -117.2073
|
||||||
|
assert info.use_fahrenheit
|
||||||
|
|
||||||
|
@requests_mock.Mocker()
|
||||||
|
@patch('homeassistant.util.location._get_freegeoip', return_value=None)
|
||||||
|
def test_detect_location_info_ipapi(self, mock_req, mock_freegeoip):
|
||||||
|
"""Test detect location info using freegeoip."""
|
||||||
|
mock_req.get(location_util.IP_API,
|
||||||
|
text=load_fixture('ip-api.com.json'))
|
||||||
|
|
||||||
|
info = location_util.detect_location_info(_test_real=True)
|
||||||
|
|
||||||
|
assert info is not None
|
||||||
|
assert info.ip == '1.2.3.4'
|
||||||
|
assert info.country_code == 'US'
|
||||||
|
assert info.country_name == 'United States'
|
||||||
|
assert info.region_code == 'CA'
|
||||||
|
assert info.region_name == 'California'
|
||||||
|
assert info.city == 'San Diego'
|
||||||
|
assert info.zip_code == '92122'
|
||||||
|
assert info.time_zone == 'America/Los_Angeles'
|
||||||
|
assert info.latitude == 32.8594
|
||||||
|
assert info.longitude == -117.2073
|
||||||
|
assert info.use_fahrenheit
|
||||||
|
|
||||||
|
@patch('homeassistant.util.location.elevation', return_value=0)
|
||||||
|
@patch('homeassistant.util.location._get_freegeoip', return_value=None)
|
||||||
|
@patch('homeassistant.util.location._get_ip_api', return_value=None)
|
||||||
|
def test_detect_location_info_both_queries_fail(self, mock_ipapi,
|
||||||
|
mock_freegeoip,
|
||||||
|
mock_elevation):
|
||||||
|
"""Ensure we return None if both queries fail."""
|
||||||
|
info = location_util.detect_location_info(_test_real=True)
|
||||||
|
assert info is None
|
||||||
|
|
||||||
|
@patch('homeassistant.util.location.requests.get',
|
||||||
|
side_effect=requests.RequestException)
|
||||||
|
def test_freegeoip_query_raises(self, mock_get):
|
||||||
|
"""Test freegeoip query when the request to API fails."""
|
||||||
|
info = location_util._get_freegeoip()
|
||||||
|
assert info is None
|
||||||
|
|
||||||
|
@patch('homeassistant.util.location.requests.get',
|
||||||
|
side_effect=requests.RequestException)
|
||||||
|
def test_ip_api_query_raises(self, mock_get):
|
||||||
|
"""Test ip api query when the request to API fails."""
|
||||||
|
info = location_util._get_ip_api()
|
||||||
|
assert info is None
|
||||||
|
|
||||||
|
@patch('homeassistant.util.location.requests.get',
|
||||||
|
side_effect=requests.RequestException)
|
||||||
|
def test_elevation_query_raises(self, mock_get):
|
||||||
|
"""Test elevation when the request to API fails."""
|
||||||
|
elevation = location_util.elevation(10, 10, _test_real=True)
|
||||||
|
assert elevation == 0
|
||||||
|
|
||||||
|
@requests_mock.Mocker()
|
||||||
|
def test_elevation_query_fails(self, mock_req):
|
||||||
|
"""Test elevation when the request to API fails."""
|
||||||
|
mock_req.get(location_util.ELEVATION_URL, text='{}', status_code=401)
|
||||||
|
elevation = location_util.elevation(10, 10, _test_real=True)
|
||||||
|
assert elevation == 0
|
||||||
|
|
||||||
|
@requests_mock.Mocker()
|
||||||
|
def test_elevation_query_nonjson(self, mock_req):
|
||||||
|
"""Test if elevation API returns a non JSON value."""
|
||||||
|
mock_req.get(location_util.ELEVATION_URL, text='{ I am not JSON }')
|
||||||
|
elevation = location_util.elevation(10, 10, _test_real=True)
|
||||||
|
assert elevation == 0
|
||||||
|
@ -49,7 +49,7 @@ class TestPackageUtil(unittest.TestCase):
|
|||||||
self.assertTrue(package.check_package_exists(
|
self.assertTrue(package.check_package_exists(
|
||||||
TEST_NEW_REQ, self.lib_dir))
|
TEST_NEW_REQ, self.lib_dir))
|
||||||
|
|
||||||
bootstrap.mount_local_lib_path(self.tmp_dir.name)
|
bootstrap._mount_local_lib_path(self.tmp_dir.name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pyhelloworld3
|
import pyhelloworld3
|
||||||
|
Loading…
x
Reference in New Issue
Block a user