Merge remote-tracking branch 'refs/remotes/home-assistant/dev' into Homematic_fix
commit
d0b1619946
|
@ -3,7 +3,6 @@
|
|||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from threading import RLock
|
||||
|
@ -12,21 +11,15 @@ import voluptuous as vol
|
|||
|
||||
import homeassistant.components as core_components
|
||||
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.helpers.config_validation as cv
|
||||
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
|
||||
from homeassistant.const import (
|
||||
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.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import (
|
||||
event_decorators, service, config_per_platform, extract_domain_configs,
|
||||
entity)
|
||||
event_decorators, service, config_per_platform, extract_domain_configs)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_SETUP_LOCK = RLock()
|
||||
|
@ -208,11 +201,6 @@ def prepare_setup_platform(hass, config, domain, platform_name):
|
|||
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
|
||||
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
|
||||
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:
|
||||
config_dir = os.path.abspath(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, {})
|
||||
|
||||
try:
|
||||
process_ha_core_config(hass, config_util.CORE_CONFIG_SCHEMA(
|
||||
core_config))
|
||||
except vol.MultipleInvalid as ex:
|
||||
conf_util.process_ha_core_config(hass, core_config)
|
||||
except vol.Invalid as ex:
|
||||
cv.log_exception(_LOGGER, ex, 'homeassistant', core_config)
|
||||
return None
|
||||
|
||||
process_ha_config_upgrade(hass)
|
||||
conf_util.process_ha_config_upgrade(hass)
|
||||
|
||||
if enable_log:
|
||||
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
|
||||
config_dir = os.path.abspath(os.path.dirname(config_path))
|
||||
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)
|
||||
|
||||
try:
|
||||
config_dict = config_util.load_yaml_config_file(config_path)
|
||||
config_dict = conf_util.load_yaml_config_file(config_path)
|
||||
except HomeAssistantError:
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
"""Ensure Home Assistant loader is prepared."""
|
||||
if not loader.PREPARED:
|
||||
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):
|
||||
"""Service handler for reloading core config."""
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant import config, bootstrap
|
||||
from homeassistant import config as conf_util
|
||||
|
||||
try:
|
||||
path = config.find_config_file(hass.config.config_dir)
|
||||
conf = config.load_yaml_config_file(path)
|
||||
path = conf_util.find_config_file(hass.config.config_dir)
|
||||
conf = conf_util.load_yaml_config_file(path)
|
||||
except HomeAssistantError as err:
|
||||
_LOGGER.error(err)
|
||||
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,
|
||||
handle_reload_config)
|
||||
|
|
|
@ -6,7 +6,7 @@ https://home-assistant.io/developers/api/
|
|||
"""
|
||||
import json
|
||||
import logging
|
||||
from time import time
|
||||
import queue
|
||||
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.remote as rem
|
||||
|
@ -72,19 +72,14 @@ class APIEventStream(HomeAssistantView):
|
|||
|
||||
def get(self, request):
|
||||
"""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()
|
||||
to_write = queue.Queue()
|
||||
|
||||
restrict = request.args.get('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."""
|
||||
if event.event_type == EVENT_TIME_CHANGED:
|
||||
return
|
||||
|
@ -99,28 +94,20 @@ class APIEventStream(HomeAssistantView):
|
|||
else:
|
||||
data = json.dumps(event, cls=rem.JSONEncoder)
|
||||
|
||||
cur_hub.schedule_call_global(0, lambda: to_write.put(data))
|
||||
to_write.put(data)
|
||||
|
||||
def stream():
|
||||
"""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))
|
||||
|
||||
last_msg = time()
|
||||
# Fire off one message right away to have browsers fire open event
|
||||
to_write.put(STREAM_PING_PAYLOAD)
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Somehow our queue.get sometimes takes too long to
|
||||
# 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)
|
||||
payload = to_write.get(timeout=STREAM_PING_INTERVAL)
|
||||
|
||||
if payload is stop_obj:
|
||||
break
|
||||
|
@ -129,15 +116,13 @@ class APIEventStream(HomeAssistantView):
|
|||
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
|
||||
msg.strip())
|
||||
yield msg.encode("UTF-8")
|
||||
last_msg = time()
|
||||
except Empty:
|
||||
if time() - last_msg > 50:
|
||||
to_write.put(STREAM_PING_PAYLOAD)
|
||||
except queue.Empty:
|
||||
to_write.put(STREAM_PING_PAYLOAD)
|
||||
except GeneratorExit:
|
||||
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
|
||||
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')
|
||||
|
||||
|
|
|
@ -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
|
||||
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
|
||||
from homeassistant.const import STATE_UNKNOWN
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
|
@ -47,44 +23,25 @@ SENSOR_TYPES_CLASS = {
|
|||
"RemoteMotion": None
|
||||
}
|
||||
|
||||
SUPPORT_HM_EVENT_AS_BINMOD = [
|
||||
"PRESS_LONG",
|
||||
"PRESS_SHORT"
|
||||
]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||
"""Setup the platform."""
|
||||
if discovery_info:
|
||||
return homematic.setup_hmdevice_discovery_helper(HMBinarySensor,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
# Manual
|
||||
return homematic.setup_hmdevice_entity_helper(HMBinarySensor,
|
||||
config,
|
||||
add_callback_devices)
|
||||
"""Setup the Homematic binary sensor platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
return homematic.setup_hmdevice_discovery_helper(HMBinarySensor,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
|
||||
|
||||
class HMBinarySensor(homematic.HMDevice, BinarySensorDevice):
|
||||
"""Represents diverse binary Homematic units in Home Assistant."""
|
||||
"""Representation of a binary Homematic device."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if switch is on."""
|
||||
"""Return true if switch is on."""
|
||||
if not self.available:
|
||||
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())
|
||||
|
||||
@property
|
||||
|
@ -107,63 +64,37 @@ class HMBinarySensor(homematic.HMDevice, BinarySensorDevice):
|
|||
if not super()._check_hm_to_ha_object():
|
||||
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):
|
||||
_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
|
||||
|
||||
# load possible binary sensor
|
||||
available_bin = self._create_binary_list_from_hm()
|
||||
|
||||
# if exists user value?
|
||||
if self._state and self._state not in available_bin:
|
||||
_LOGGER.critical("This %s have no binary with %s!", self._name,
|
||||
if self._state and self._state not in self._hmdevice.BINARYNODE:
|
||||
_LOGGER.critical("This %s have no binary with %s", self._name,
|
||||
self._state)
|
||||
return False
|
||||
|
||||
# only check and give a warining to User
|
||||
if self._state is None and len(available_bin) > 1:
|
||||
_LOGGER.warning("%s have multible binary params. It use all " +
|
||||
"binary nodes as one. Possible param values: %s",
|
||||
self._name, str(available_bin))
|
||||
# only check and give a warning to the user
|
||||
if self._state is None and len(self._hmdevice.BINARYNODE) > 1:
|
||||
_LOGGER.critical("%s have multiple binary params. It use all "
|
||||
"binary nodes as one. Possible param values: %s",
|
||||
self._name, str(self._hmdevice.BINARYNODE))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
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()
|
||||
|
||||
# load possible binary sensor
|
||||
available_bin = self._create_binary_list_from_hm()
|
||||
|
||||
# object have 1 binary
|
||||
if self._state is None and len(available_bin) == 1:
|
||||
for value in available_bin:
|
||||
if self._state is None and len(self._hmdevice.BINARYNODE) == 1:
|
||||
for value in self._hmdevice.BINARYNODE:
|
||||
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
|
||||
if self._state:
|
||||
_LOGGER.debug("%s init datastruct with main node '%s'", self._name,
|
||||
self._state)
|
||||
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
|
||||
|
||||
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.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
|
||||
SENSOR_TYPES = {
|
||||
|
@ -41,14 +43,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
add_devices([WinkBinarySensorDevice(sensor)])
|
||||
|
||||
|
||||
class WinkBinarySensorDevice(BinarySensorDevice, Entity):
|
||||
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
|
||||
"""Representation of a Wink sensor."""
|
||||
|
||||
def __init__(self, wink):
|
||||
"""Initialize the Wink binary sensor."""
|
||||
self.wink = wink
|
||||
super().__init__(wink)
|
||||
wink = get_component('wink')
|
||||
self._unit_of_measurement = self.wink.UNIT
|
||||
self._battery = self.wink.battery_level
|
||||
self.capability = self.wink.capability()
|
||||
|
||||
@property
|
||||
|
@ -67,35 +69,3 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity):
|
|||
def sensor_class(self):
|
||||
"""Return the class of this sensor, from SENSOR_CLASSES."""
|
||||
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/
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
@ -81,8 +82,6 @@ class Camera(Entity):
|
|||
|
||||
def mjpeg_stream(self, response):
|
||||
"""Generate an HTTP MJPEG stream from camera images."""
|
||||
import eventlet
|
||||
|
||||
def stream():
|
||||
"""Stream images as mjpeg stream."""
|
||||
try:
|
||||
|
@ -99,7 +98,7 @@ class Camera(Entity):
|
|||
|
||||
last_image = img_bytes
|
||||
|
||||
eventlet.sleep(0.5)
|
||||
time.sleep(0.5)
|
||||
except GeneratorExit:
|
||||
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 subprocess
|
||||
import logging
|
||||
|
@ -43,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
|
||||
class RaspberryCamera(Camera):
|
||||
"""Raspberry Pi camera."""
|
||||
"""Representation of a Raspberry Pi camera."""
|
||||
|
||||
def __init__(self, device_info):
|
||||
"""Initialize Raspberry Pi camera component."""
|
||||
|
|
|
@ -7,9 +7,10 @@ https://home-assistant.io/components/garage_door.wink/
|
|||
import logging
|
||||
|
||||
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):
|
||||
|
@ -31,38 +32,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
pywink.get_garage_doors())
|
||||
|
||||
|
||||
class WinkGarageDoorDevice(GarageDoorDevice):
|
||||
class WinkGarageDoorDevice(WinkDevice, GarageDoorDevice):
|
||||
"""Representation of a Wink garage door."""
|
||||
|
||||
def __init__(self, wink):
|
||||
"""Initialize the garage door."""
|
||||
self.wink = 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()
|
||||
WinkDevice.__init__(self, wink)
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return true if door is closed."""
|
||||
return self.wink.state() == 0
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""True if connection == True."""
|
||||
return self.wink.available
|
||||
|
||||
def close_door(self):
|
||||
"""Close the door."""
|
||||
self.wink.set_state(0)
|
||||
|
@ -70,16 +51,3 @@ class WinkGarageDoorDevice(GarageDoorDevice):
|
|||
def open_door(self):
|
||||
"""Open the door."""
|
||||
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
|
||||
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 logging
|
||||
|
@ -21,11 +12,10 @@ from homeassistant.helpers import discovery
|
|||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
DOMAIN = 'homematic'
|
||||
REQUIREMENTS = ['pyhomematic==0.1.6']
|
||||
REQUIREMENTS = ['pyhomematic==0.1.8']
|
||||
|
||||
HOMEMATIC = None
|
||||
HOMEMATIC_LINK_DELAY = 0.5
|
||||
HOMEMATIC_DEVICES = {}
|
||||
|
||||
DISCOVER_SWITCHES = "homematic.switch"
|
||||
DISCOVER_LIGHTS = "homematic.light"
|
||||
|
@ -35,18 +25,22 @@ DISCOVER_ROLLERSHUTTER = "homematic.rollershutter"
|
|||
DISCOVER_THERMOSTATS = "homematic.thermostat"
|
||||
|
||||
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 = {
|
||||
DISCOVER_SWITCHES: ["Switch", "SwitchPowermeter"],
|
||||
DISCOVER_LIGHTS: ["Dimmer"],
|
||||
DISCOVER_SENSORS: ["SwitchPowermeter", "Motion", "MotionV2",
|
||||
"RemoteMotion", "ThermostatWall", "AreaThermostat",
|
||||
"RotaryHandleSensor"],
|
||||
"RotaryHandleSensor", "WaterSensor"],
|
||||
DISCOVER_THERMOSTATS: ["Thermostat", "ThermostatWall", "MAXThermostat"],
|
||||
DISCOVER_BINARY_SENSORS: ["Remote", "ShutterContact", "Smoke", "SmokeV2",
|
||||
"Motion", "MotionV2", "RemoteMotion",
|
||||
"GongSensor"],
|
||||
DISCOVER_BINARY_SENSORS: ["ShutterContact", "Smoke", "SmokeV2",
|
||||
"Motion", "MotionV2", "RemoteMotion"],
|
||||
DISCOVER_ROLLERSHUTTER: ["Blind"]
|
||||
}
|
||||
|
||||
|
@ -66,6 +60,13 @@ HM_ATTRIBUTE_SUPPORT = {
|
|||
"VOLTAGE": ["Voltage", {}]
|
||||
}
|
||||
|
||||
HM_PRESS_EVENTS = [
|
||||
"PRESS_SHORT",
|
||||
"PRESS_LONG",
|
||||
"PRESS_CONT",
|
||||
"PRESS_LONG_RELEASE"
|
||||
]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -81,6 +82,8 @@ def setup(hass, config):
|
|||
remote_ip = config[DOMAIN].get("remote_ip", None)
|
||||
remote_port = config[DOMAIN].get("remote_port", 2001)
|
||||
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)
|
||||
|
||||
if remote_ip is None or local_ip is None:
|
||||
|
@ -89,12 +92,15 @@ def setup(hass, config):
|
|||
|
||||
# Create server thread
|
||||
bound_system_callback = partial(system_callback_handler, hass, config)
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
HOMEMATIC = HMConnection(local=local_ip,
|
||||
localport=local_port,
|
||||
remote=remote_ip,
|
||||
remoteport=remote_port,
|
||||
systemcallback=bound_system_callback,
|
||||
resolvenames=resolvenames,
|
||||
rpcusername=username,
|
||||
rpcpassword=password,
|
||||
interface_id="homeassistant")
|
||||
|
||||
# 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:
|
||||
key_dict[dev['ADDRESS'].split(':')[0]] = True
|
||||
|
||||
# Connect devices already created in HA to pyhomematic and
|
||||
# add remaining devices to list
|
||||
devices_not_created = []
|
||||
# Register EVENTS
|
||||
# Search all device with a EVENTNODE that include data
|
||||
bound_event_callback = partial(_hm_event_handler, hass)
|
||||
for dev in key_dict:
|
||||
if dev in HOMEMATIC_DEVICES:
|
||||
for hm_element in HOMEMATIC_DEVICES[dev]:
|
||||
hm_element.link_homematic()
|
||||
else:
|
||||
devices_not_created.append(dev)
|
||||
if dev not in HOMEMATIC.devices:
|
||||
continue
|
||||
|
||||
hmdevice = HOMEMATIC.devices.get(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,
|
||||
# all devices not configured are added.
|
||||
autodetect = config[DOMAIN].get("autodetect", False)
|
||||
_LOGGER.debug("Autodetect is %s / unknown device: %s", str(autodetect),
|
||||
str(devices_not_created))
|
||||
if autodetect and devices_not_created:
|
||||
if key_dict:
|
||||
for component_name, discovery_type in (
|
||||
('switch', DISCOVER_SWITCHES),
|
||||
('light', DISCOVER_LIGHTS),
|
||||
|
@ -143,8 +150,7 @@ def system_callback_handler(hass, config, src, *args):
|
|||
('sensor', DISCOVER_SENSORS),
|
||||
('thermostat', DISCOVER_THERMOSTATS)):
|
||||
# Get all devices of a specific type
|
||||
found_devices = _get_devices(discovery_type,
|
||||
devices_not_created)
|
||||
found_devices = _get_devices(discovery_type, key_dict)
|
||||
|
||||
# When devices of this type are found
|
||||
# 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):
|
||||
"""Get devices."""
|
||||
from homeassistant.components.binary_sensor.homematic import \
|
||||
SUPPORT_HM_EVENT_AS_BINMOD
|
||||
|
||||
"""Get the Homematic devices."""
|
||||
# run
|
||||
device_arr = []
|
||||
if not keys:
|
||||
keys = HOMEMATIC.devices
|
||||
for key in keys:
|
||||
device = HOMEMATIC.devices[key]
|
||||
if device.__class__.__name__ not in HM_DEVICE_TYPES[device_type]:
|
||||
continue
|
||||
class_name = device.__class__.__name__
|
||||
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
|
||||
if device_type == DISCOVER_SENSORS:
|
||||
metadata.update(device.SENSORNODE)
|
||||
elif device_type == DISCOVER_BINARY_SENSORS:
|
||||
metadata.update(device.BINARYNODE)
|
||||
|
||||
# Also add supported events as binary 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)
|
||||
params = _create_params_list(device, metadata, device_type)
|
||||
if params:
|
||||
# Generate options for 1...n elements with 1...n params
|
||||
for channel in range(1, device.ELEMENT + 1):
|
||||
|
@ -194,9 +193,9 @@ def _get_devices(device_type, keys):
|
|||
device_dict = dict(platform="homematic",
|
||||
address=key,
|
||||
name=name,
|
||||
button=channel)
|
||||
channel=channel)
|
||||
if param is not None:
|
||||
device_dict["param"] = param
|
||||
device_dict[ATTR_PARAM] = param
|
||||
|
||||
# Add new device
|
||||
device_arr.append(device_dict)
|
||||
|
@ -209,15 +208,22 @@ def _get_devices(device_type, keys):
|
|||
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."""
|
||||
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
|
||||
for channel in range(1, hmdevice.ELEMENT + 1):
|
||||
param_chan = []
|
||||
try:
|
||||
for node, meta_chan in metadata.items():
|
||||
for node, meta_chan in metadata.items():
|
||||
try:
|
||||
# Is this attribute ignored?
|
||||
if node in HM_IGNORE_DISCOVERY_NODE:
|
||||
continue
|
||||
|
@ -227,15 +233,17 @@ def _create_params_list(hmdevice, metadata):
|
|||
elif channel == 1:
|
||||
# First channel can have other data channel
|
||||
param_chan.append(node)
|
||||
# pylint: disable=broad-except
|
||||
except Exception as err:
|
||||
_LOGGER.error("Exception generating %s (%s): %s",
|
||||
hmdevice.ADDRESS, str(metadata), str(err))
|
||||
# Default parameter
|
||||
if not param_chan:
|
||||
except (TypeError, ValueError):
|
||||
_LOGGER.error("Exception generating %s (%s)",
|
||||
hmdevice.ADDRESS, str(metadata))
|
||||
|
||||
# default parameter is merge is off
|
||||
if len(param_chan) == 0 and not merge:
|
||||
param_chan.append(None)
|
||||
|
||||
# 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,
|
||||
str(params))
|
||||
|
@ -264,55 +272,55 @@ def _create_ha_name(name, channel, param):
|
|||
def setup_hmdevice_discovery_helper(hmdevicetype, discovery_info,
|
||||
add_callback_devices):
|
||||
"""Helper to setup Homematic devices with discovery info."""
|
||||
for config in discovery_info["devices"]:
|
||||
ret = setup_hmdevice_entity_helper(hmdevicetype, config,
|
||||
add_callback_devices)
|
||||
if not ret:
|
||||
_LOGGER.error("Setup discovery error with config %s", str(config))
|
||||
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
|
||||
_LOGGER.debug("Add device %s from config: %s",
|
||||
str(hmdevicetype), 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
|
||||
|
||||
|
||||
def setup_hmdevice_entity_helper(hmdevicetype, config, add_callback_devices):
|
||||
"""Helper to setup Homematic devices."""
|
||||
if HOMEMATIC is None:
|
||||
_LOGGER.error('Error setting up HMDevice: Server not configured.')
|
||||
return False
|
||||
def _hm_event_handler(hass, device, caller, attribute, value):
|
||||
"""Handle all pyhomematic device events."""
|
||||
channel = device.split(":")[1]
|
||||
address = device.split(":")[0]
|
||||
hmdevice = HOMEMATIC.devices.get(address)
|
||||
|
||||
address = config.get('address', None)
|
||||
if address is None:
|
||||
_LOGGER.error("Error setting up device '%s': " +
|
||||
"'address' missing in configuration.", address)
|
||||
return False
|
||||
# is not a event?
|
||||
if attribute not in hmdevice.EVENTNODE:
|
||||
return
|
||||
|
||||
_LOGGER.debug("Add device %s from config: %s",
|
||||
str(hmdevicetype), str(config))
|
||||
# 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)
|
||||
_LOGGER.debug("Event %s for %s channel %s", attribute,
|
||||
hmdevice.NAME, channel)
|
||||
|
||||
# Add to HA
|
||||
add_callback_devices([new_device])
|
||||
# a keypress event
|
||||
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
|
||||
if address in HOMEMATIC.devices:
|
||||
return new_device.link_homematic()
|
||||
return True
|
||||
_LOGGER.warning("Event is unknown and not forwarded to HA")
|
||||
|
||||
|
||||
class HMDevice(Entity):
|
||||
"""Homematic device base object."""
|
||||
"""The Homematic device base object."""
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
def __init__(self, config):
|
||||
"""Initialize generic HM device."""
|
||||
self._name = config.get("name", None)
|
||||
self._address = config.get("address", None)
|
||||
self._channel = config.get("button", 1)
|
||||
self._state = config.get("param", None)
|
||||
self._hidden = config.get("hidden", False)
|
||||
"""Initialize a generic Homematic device."""
|
||||
self._name = config.get(ATTR_NAME, None)
|
||||
self._address = config.get(ATTR_ADDRESS, None)
|
||||
self._channel = config.get(ATTR_CHANNEL, 1)
|
||||
self._state = config.get(ATTR_PARAM, None)
|
||||
self._data = {}
|
||||
self._hmdevice = None
|
||||
self._connected = False
|
||||
|
@ -330,7 +338,7 @@ class HMDevice(Entity):
|
|||
|
||||
@property
|
||||
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
|
||||
|
||||
@property
|
||||
|
@ -340,24 +348,23 @@ class HMDevice(Entity):
|
|||
|
||||
@property
|
||||
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
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if device is available."""
|
||||
"""Return true if device is available."""
|
||||
return self._available
|
||||
|
||||
@property
|
||||
def hidden(self):
|
||||
"""Return True if the entity should be hidden from UIs."""
|
||||
return self._hidden
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
attr = {}
|
||||
|
||||
# no data available to create
|
||||
if not self.available:
|
||||
return attr
|
||||
|
||||
# Generate an attributes list
|
||||
for node, data in HM_ATTRIBUTE_SUPPORT.items():
|
||||
# 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])
|
||||
attr[data[0]] = value
|
||||
|
||||
# static attributes
|
||||
attr["ID"] = self._hmdevice.ADDRESS
|
||||
|
||||
return attr
|
||||
|
||||
def link_homematic(self):
|
||||
"""Connect to homematic."""
|
||||
"""Connect to Homematic."""
|
||||
# device is already linked
|
||||
if self._connected:
|
||||
return True
|
||||
|
@ -379,7 +389,7 @@ class HMDevice(Entity):
|
|||
self._hmdevice = HOMEMATIC.devices[self._address]
|
||||
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)
|
||||
if self._check_hm_to_ha_object():
|
||||
try:
|
||||
|
@ -402,7 +412,7 @@ class HMDevice(Entity):
|
|||
_LOGGER.error("Exception while linking %s: %s",
|
||||
self._address, str(err))
|
||||
else:
|
||||
_LOGGER.critical("Delink %s object from HM!", self._name)
|
||||
_LOGGER.critical("Delink %s object from HM", self._name)
|
||||
self._connected = False
|
||||
|
||||
# Update HA
|
||||
|
@ -429,18 +439,12 @@ class HMDevice(Entity):
|
|||
self._available = bool(value)
|
||||
have_change = True
|
||||
|
||||
# If it has changed, update HA
|
||||
# If it has changed data point, update HA
|
||||
if have_change:
|
||||
_LOGGER.debug("%s update_ha_state after '%s'", self._name,
|
||||
attribute)
|
||||
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):
|
||||
"""Subscribe all required events to handle job."""
|
||||
channels_to_sub = {}
|
||||
|
@ -488,24 +492,21 @@ class HMDevice(Entity):
|
|||
if node in self._data:
|
||||
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
|
||||
|
||||
def _hm_set_state(self, value):
|
||||
"""Set data to main datapoint."""
|
||||
if self._state in self._data:
|
||||
self._data[self._state] = value
|
||||
|
||||
def _hm_get_state(self):
|
||||
"""Get data from main datapoint."""
|
||||
if self._state in self._data:
|
||||
return self._data[self._state]
|
||||
return None
|
||||
|
||||
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!
|
||||
"""
|
||||
|
@ -521,7 +522,7 @@ class HMDevice(Entity):
|
|||
return True
|
||||
|
||||
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!
|
||||
"""
|
||||
|
|
|
@ -13,19 +13,19 @@ import re
|
|||
import ssl
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.remote as rem
|
||||
from homeassistant import util
|
||||
from homeassistant.const import (
|
||||
SERVER_PORT, HTTP_HEADER_HA_AUTH, HTTP_HEADER_CACHE_CONTROL,
|
||||
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
|
||||
import homeassistant.util.dt as dt_util
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
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_SERVER_HOST = "server_host"
|
||||
|
@ -40,7 +40,8 @@ DATA_API_PASSWORD = 'api_password'
|
|||
# TLS configuation follows the best-practice guidelines
|
||||
# specified here: https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
# 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:" \
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:" \
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:" \
|
||||
|
@ -117,11 +118,17 @@ def setup(hass, config):
|
|||
cors_origins=cors_origins
|
||||
)
|
||||
|
||||
hass.bus.listen_once(
|
||||
ha.EVENT_HOMEASSISTANT_START,
|
||||
lambda event:
|
||||
threading.Thread(target=server.start, daemon=True,
|
||||
name='WSGI-server').start())
|
||||
def start_wsgi_server(event):
|
||||
"""Start the WSGI server."""
|
||||
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.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.cors_origins = cors_origins
|
||||
self.event_forwarder = None
|
||||
self.server = None
|
||||
|
||||
def register_view(self, view):
|
||||
"""Register a view with the WSGI server.
|
||||
|
@ -307,15 +315,34 @@ class HomeAssistantWSGI(object):
|
|||
|
||||
def start(self):
|
||||
"""Start the wsgi server."""
|
||||
from eventlet import wsgi
|
||||
import eventlet
|
||||
from cherrypy import wsgiserver
|
||||
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:
|
||||
sock = eventlet.wrap_ssl(sock, certfile=self.ssl_certificate,
|
||||
keyfile=self.ssl_key, server_side=True,
|
||||
ssl_version=SSL_VERSION, ciphers=CIPHERS)
|
||||
wsgi.server(sock, self, log=_LOGGER)
|
||||
context = ssl.SSLContext(SSL_VERSION)
|
||||
context.options |= SSL_OPTS
|
||||
context.set_ciphers(CIPHERS)
|
||||
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):
|
||||
"""Handle incoming request."""
|
||||
|
@ -362,6 +389,10 @@ class HomeAssistantWSGI(object):
|
|||
"""Handle a request for base app + extra apps."""
|
||||
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)
|
||||
# Strip out any cachebusting MD5 fingerprints
|
||||
fingerprinted = _FINGERPRINT.match(environ.get('PATH_INFO', ''))
|
||||
|
|
|
@ -23,7 +23,7 @@ DEFAULT_DATABASE = 'home_assistant'
|
|||
DEFAULT_SSL = False
|
||||
DEFAULT_VERIFY_SSL = False
|
||||
|
||||
REQUIREMENTS = ['influxdb==2.12.0']
|
||||
REQUIREMENTS = ['influxdb==3.0.0']
|
||||
|
||||
CONF_HOST = 'host'
|
||||
CONF_PORT = 'port'
|
||||
|
|
|
@ -4,7 +4,6 @@ Support for EnOcean light sources.
|
|||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/light.enocean/
|
||||
"""
|
||||
|
||||
import logging
|
||||
import math
|
||||
|
||||
|
@ -86,7 +85,7 @@ class EnOceanLight(enocean.EnOceanDevice, Light):
|
|||
self._on_state = False
|
||||
|
||||
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._on_state = bool(val != 0)
|
||||
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
|
||||
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
|
||||
from homeassistant.components.light import (ATTR_BRIGHTNESS, Light)
|
||||
from homeassistant.const import STATE_UNKNOWN
|
||||
|
@ -23,24 +11,21 @@ import homeassistant.components.homematic as homematic
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# List of component names (string) your component depends upon.
|
||||
DEPENDENCIES = ['homematic']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||
"""Setup the platform."""
|
||||
if discovery_info:
|
||||
return homematic.setup_hmdevice_discovery_helper(HMLight,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
# Manual
|
||||
return homematic.setup_hmdevice_entity_helper(HMLight,
|
||||
config,
|
||||
add_callback_devices)
|
||||
"""Setup the Homematic light platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
return homematic.setup_hmdevice_discovery_helper(HMLight,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
|
||||
|
||||
class HMLight(homematic.HMDevice, Light):
|
||||
"""Represents a Homematic Light in Home Assistant."""
|
||||
"""Representation of a Homematic light."""
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
|
@ -55,7 +40,7 @@ class HMLight(homematic.HMDevice, Light):
|
|||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if light is on."""
|
||||
"""Return true if light is on."""
|
||||
try:
|
||||
return self._hm_get_state() > 0
|
||||
except TypeError:
|
||||
|
@ -78,24 +63,24 @@ class HMLight(homematic.HMDevice, Light):
|
|||
self._hmdevice.off(self._channel)
|
||||
|
||||
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
|
||||
|
||||
# Check compatibility from HMDevice
|
||||
if not super()._check_hm_to_ha_object():
|
||||
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):
|
||||
return True
|
||||
if isinstance(self._hmdevice, Dimmer):
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
super()._init_data_struct()
|
||||
|
|
|
@ -1,19 +1,9 @@
|
|||
"""
|
||||
Support for Osram Lightify.
|
||||
|
||||
Uses: https://github.com/aneumeier/python-lightify for the Osram light
|
||||
interface.
|
||||
|
||||
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.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/light.osramlightify/
|
||||
"""
|
||||
|
||||
import logging
|
||||
import socket
|
||||
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):
|
||||
"""Find and return lights."""
|
||||
"""Setup Osram Lightify lights."""
|
||||
import lightify
|
||||
host = config.get(CONF_HOST)
|
||||
if host:
|
||||
|
@ -85,7 +75,7 @@ def setup_bridge(bridge, add_devices_callback):
|
|||
|
||||
|
||||
class OsramLightifyLight(Light):
|
||||
"""Defines an Osram Lightify Light."""
|
||||
"""Representation of an Osram Lightify Light."""
|
||||
|
||||
def __init__(self, light_id, light, update_lights):
|
||||
"""Initialize the light."""
|
||||
|
|
|
@ -8,12 +8,13 @@ import logging
|
|||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \
|
||||
Light, ATTR_RGB_COLOR
|
||||
from homeassistant.components.wink import WinkDevice
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
from homeassistant.util import color as color_util
|
||||
from homeassistant.util.color import \
|
||||
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):
|
||||
|
@ -35,26 +36,12 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
WinkLight(light) for light in pywink.get_bulbs())
|
||||
|
||||
|
||||
class WinkLight(Light):
|
||||
class WinkLight(WinkDevice, Light):
|
||||
"""Representation of a Wink light."""
|
||||
|
||||
def __init__(self, wink):
|
||||
"""
|
||||
Initialize the light.
|
||||
|
||||
: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()
|
||||
"""Initialize the Wink device."""
|
||||
WinkDevice.__init__(self, wink)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
@ -66,11 +53,6 @@ class WinkLight(Light):
|
|||
"""Return the brightness of the light."""
|
||||
return int(self.wink.brightness() * 255)
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""True if connection == True."""
|
||||
return self.wink.available
|
||||
|
||||
@property
|
||||
def xy_color(self):
|
||||
"""Current bulb color in CIE 1931 (XY) color space."""
|
||||
|
@ -112,7 +94,3 @@ class WinkLight(Light):
|
|||
def turn_off(self):
|
||||
"""Turn the switch off."""
|
||||
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
|
||||
https://home-assistant.io/components/light.zwave/
|
||||
"""
|
||||
import logging
|
||||
|
||||
# Because we do not compile openzwave on CI
|
||||
# pylint: disable=import-error
|
||||
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.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):
|
||||
|
@ -28,7 +47,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
return
|
||||
|
||||
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):
|
||||
|
@ -49,8 +78,9 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
|||
from pydispatch import dispatcher
|
||||
|
||||
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
||||
|
||||
self._brightness, self._state = brightness_state(value)
|
||||
self._brightness = None
|
||||
self._state = None
|
||||
self.update_properties()
|
||||
|
||||
# Used for value change event handling
|
||||
self._refreshing = False
|
||||
|
@ -59,6 +89,11 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
|||
dispatcher.connect(
|
||||
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):
|
||||
"""Called when a value has changed on the network."""
|
||||
if self._value.value_id != value.value_id:
|
||||
|
@ -66,7 +101,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
|||
|
||||
if self._refreshing:
|
||||
self._refreshing = False
|
||||
self._brightness, self._state = brightness_state(value)
|
||||
self.update_properties()
|
||||
else:
|
||||
def _refresh_value():
|
||||
"""Used timer callback for delayed value refresh."""
|
||||
|
@ -107,3 +142,168 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
|||
"""Turn the device off."""
|
||||
if self._value.node.set_dimmer(self._value.value_id, 0):
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
||||
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):
|
||||
|
@ -30,38 +31,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
add_devices(WinkLockDevice(lock) for lock in pywink.get_locks())
|
||||
|
||||
|
||||
class WinkLockDevice(LockDevice):
|
||||
class WinkLockDevice(WinkDevice, LockDevice):
|
||||
"""Representation of a Wink lock."""
|
||||
|
||||
def __init__(self, wink):
|
||||
"""Initialize the lock."""
|
||||
self.wink = 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()
|
||||
WinkDevice.__init__(self, wink)
|
||||
|
||||
@property
|
||||
def is_locked(self):
|
||||
"""Return true if device is locked."""
|
||||
return self.wink.state()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""True if connection == True."""
|
||||
return self.wink.available
|
||||
|
||||
def lock(self, **kwargs):
|
||||
"""Lock the device."""
|
||||
self.wink.set_state(True)
|
||||
|
@ -69,16 +50,3 @@ class WinkLockDevice(LockDevice):
|
|||
def unlock(self, **kwargs):
|
||||
"""Unlock the device."""
|
||||
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.
|
||||
|
||||
By Antonio Parraga Navarro
|
||||
|
||||
dedicated to Isabel
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/media_player.braviatv/
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
|
@ -38,6 +36,7 @@ SUPPORT_BRAVIA = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
|
|||
|
||||
|
||||
def _get_mac_address(ip_address):
|
||||
"""Get the MAC address of the device."""
|
||||
from subprocess import Popen, 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):
|
||||
"""Small configuration file management function."""
|
||||
"""Create the configuration from a file."""
|
||||
if config:
|
||||
# We're writing configuration
|
||||
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
|
||||
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)
|
||||
name = config.get(CONF_NAME)
|
||||
if name is None:
|
||||
|
@ -176,7 +175,7 @@ class BraviaTVDevice(MediaPlayerDevice):
|
|||
"""Representation of a Sony Bravia TV."""
|
||||
|
||||
def __init__(self, host, mac, name, pin):
|
||||
"""Initialize the sony bravia device."""
|
||||
"""Initialize the Sony Bravia device."""
|
||||
from braviarc import braviarc
|
||||
|
||||
self._pin = pin
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
Support for interacting with and controlling the cmus music player.
|
||||
|
||||
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
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
|
@ -17,7 +16,7 @@ from homeassistant.const import (STATE_OFF, STATE_PAUSED, STATE_PLAYING,
|
|||
CONF_PORT)
|
||||
|
||||
_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_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):
|
||||
"""Setup the Cmus platform."""
|
||||
"""Setup the CMUS platform."""
|
||||
from pycmus import exceptions
|
||||
|
||||
host = config.get(CONF_HOST, None)
|
||||
|
@ -44,7 +43,7 @@ def setup_platform(hass, config, add_devices, discover_info=None):
|
|||
|
||||
|
||||
class CmusDevice(MediaPlayerDevice):
|
||||
"""Representation of a running cmus."""
|
||||
"""Representation of a running CMUS."""
|
||||
|
||||
# pylint: disable=no-member, too-many-public-methods, abstract-method
|
||||
def __init__(self, server, password, port, name):
|
||||
|
|
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
|||
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
|
||||
|
||||
_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
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
|||
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
|
||||
|
||||
_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_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
|
||||
https://home-assistant.io/components/media_player.roku/
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
|
@ -77,7 +76,8 @@ class RokuDevice(MediaPlayerDevice):
|
|||
self.current_app = self.roku.current_app
|
||||
else:
|
||||
self.current_app = None
|
||||
except requests.exceptions.ConnectionError:
|
||||
except (requests.exceptions.ConnectionError,
|
||||
requests.exceptions.ReadTimeout):
|
||||
self.current_app = None
|
||||
|
||||
def get_source_list(self):
|
||||
|
|
|
@ -154,12 +154,20 @@ sonos_group_players:
|
|||
description: Name(s) of entites that will coordinate the grouping. Platform dependent.
|
||||
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:
|
||||
description: Take a snapshot of the media player.
|
||||
|
||||
fields:
|
||||
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'
|
||||
|
||||
sonos_restore:
|
||||
|
@ -167,5 +175,5 @@ sonos_restore:
|
|||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entites that will coordinate the grouping. Platform dependent.
|
||||
example: 'media_player.living_room_sonos'
|
||||
description: Name(s) of entites that will be restored. Platform dependent.
|
||||
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
|
||||
https://home-assistant.io/components/media_player.snapcast/
|
||||
"""
|
||||
|
||||
import logging
|
||||
import socket
|
||||
|
||||
|
|
|
@ -34,11 +34,12 @@ SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
|
|||
SUPPORT_SEEK
|
||||
|
||||
SERVICE_GROUP_PLAYERS = 'sonos_group_players'
|
||||
SERVICE_UNJOIN = 'sonos_unjoin'
|
||||
SERVICE_SNAPSHOT = 'sonos_snapshot'
|
||||
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):
|
||||
"""Setup the Sonos platform."""
|
||||
import soco
|
||||
|
@ -72,47 +73,35 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
add_devices(devices)
|
||||
_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):
|
||||
"""Group media players, use player as coordinator."""
|
||||
entity_id = service.data.get('entity_id')
|
||||
_apply_service(service, SonosDevice.group_players)
|
||||
|
||||
if entity_id:
|
||||
_devices = [device for device in devices
|
||||
if device.entity_id == entity_id]
|
||||
else:
|
||||
_devices = devices
|
||||
def unjoin_service(service):
|
||||
"""Unjoin the player from a group."""
|
||||
_apply_service(service, SonosDevice.unjoin)
|
||||
|
||||
for device in _devices:
|
||||
device.group_players()
|
||||
device.update_ha_state(True)
|
||||
|
||||
def snapshot(service):
|
||||
def snapshot_service(service):
|
||||
"""Take a snapshot."""
|
||||
entity_id = service.data.get('entity_id')
|
||||
_apply_service(service, SonosDevice.snapshot)
|
||||
|
||||
if entity_id:
|
||||
_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):
|
||||
def restore_service(service):
|
||||
"""Restore a snapshot."""
|
||||
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:
|
||||
device.restore(service)
|
||||
device.update_ha_state(True)
|
||||
_apply_service(service, SonosDevice.restore)
|
||||
|
||||
descriptions = load_yaml_config_file(
|
||||
path.join(path.dirname(__file__), 'services.yaml'))
|
||||
|
@ -121,12 +110,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
group_players_service,
|
||||
descriptions.get(SERVICE_GROUP_PLAYERS))
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_UNJOIN,
|
||||
unjoin_service,
|
||||
descriptions.get(SERVICE_UNJOIN))
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_SNAPSHOT,
|
||||
snapshot,
|
||||
snapshot_service,
|
||||
descriptions.get(SERVICE_SNAPSHOT))
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_RESTORE,
|
||||
restore,
|
||||
restore_service,
|
||||
descriptions.get(SERVICE_RESTORE))
|
||||
|
||||
return True
|
||||
|
@ -356,12 +349,17 @@ class SonosDevice(MediaPlayerDevice):
|
|||
self._player.partymode()
|
||||
|
||||
@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."""
|
||||
self.soco_snapshot.snapshot()
|
||||
|
||||
@only_if_coordinator
|
||||
def restore(self, service):
|
||||
def restore(self):
|
||||
"""Restore snapshot for the player."""
|
||||
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
|
||||
https://home-assistant.io/components/media_player.universal/
|
||||
"""
|
||||
|
||||
import logging
|
||||
# pylint: disable=import-error
|
||||
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
|
||||
properly configured.
|
||||
|
||||
Configuration:
|
||||
|
||||
rollershutter:
|
||||
- platform: homematic
|
||||
address: "<Homematic address for device>" # e.g. "JEQ0XXXXXXX"
|
||||
name: "<User defined name>" (optional)
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
@ -29,14 +22,12 @@ DEPENDENCIES = ['homematic']
|
|||
|
||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||
"""Setup the platform."""
|
||||
if discovery_info:
|
||||
return homematic.setup_hmdevice_discovery_helper(HMRollershutter,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
# Manual
|
||||
return homematic.setup_hmdevice_entity_helper(HMRollershutter,
|
||||
config,
|
||||
add_callback_devices)
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
return homematic.setup_hmdevice_discovery_helper(HMRollershutter,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
|
||||
|
||||
class HMRollershutter(homematic.HMDevice, RollershutterDevice):
|
||||
|
|
|
@ -7,9 +7,10 @@ https://home-assistant.io/components/rollershutter.wink/
|
|||
import logging
|
||||
|
||||
from homeassistant.components.rollershutter import RollershutterDevice
|
||||
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):
|
||||
|
@ -31,38 +32,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
pywink.get_shades())
|
||||
|
||||
|
||||
class WinkRollershutterDevice(RollershutterDevice):
|
||||
class WinkRollershutterDevice(WinkDevice, RollershutterDevice):
|
||||
"""Representation of a Wink rollershutter (shades)."""
|
||||
|
||||
def __init__(self, wink):
|
||||
"""Initialize the rollershutter."""
|
||||
self.wink = wink
|
||||
self._battery = None
|
||||
WinkDevice.__init__(self, wink)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Wink Shades don't track their position."""
|
||||
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):
|
||||
"""Close the shade."""
|
||||
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
|
||||
https://home-assistant.io/components/sensor.deutsche_bahn/
|
||||
"""
|
||||
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.helpers.entity import Entity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
REQUIREMENTS = ['schiene==0.17']
|
||||
|
||||
CONF_START = 'from'
|
||||
CONF_DESTINATION = 'to'
|
||||
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.
|
||||
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."""
|
||||
start = config.get('from')
|
||||
goal = config.get('to')
|
||||
start = config.get(CONF_START)
|
||||
destination = config.get(CONF_DESTINATION)
|
||||
|
||||
if start is None:
|
||||
_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)
|
||||
add_devices([DeutscheBahnSensor(start, destination)])
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
@ -63,16 +68,17 @@ class DeutscheBahnSensor(Entity):
|
|||
@property
|
||||
def state_attributes(self):
|
||||
"""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):
|
||||
"""Get the latest delay from bahn.de and updates the state."""
|
||||
self.data.update()
|
||||
self._state = self.data.connections[0].get('departure', 'Unknown')
|
||||
if self.data.connections[0]['delay'] != 0:
|
||||
self._state += " + {}".format(
|
||||
self.data.connections[0]['delay']
|
||||
)
|
||||
self._state += " + {}".format(self.data.connections[0]['delay'])
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
@ -90,18 +96,15 @@ class SchieneData(object):
|
|||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
"""Update the connection data."""
|
||||
self.connections = self.schiene.connections(self.start,
|
||||
self.goal,
|
||||
datetime.now())
|
||||
self.connections = self.schiene.connections(self.start, self.goal)
|
||||
|
||||
for con in self.connections:
|
||||
# Details info is not useful.
|
||||
# Having a more consistent interface simplifies
|
||||
# usage of Template sensors later on
|
||||
# Detail info is not useful. Having a more consistent interface
|
||||
# simplifies usage of template sensors.
|
||||
if 'details' in con:
|
||||
con.pop('details')
|
||||
delay = con.get('delay',
|
||||
{'delay_departure': 0,
|
||||
'delay_arrival': 0})
|
||||
# IMHO only delay_departure is usefull
|
||||
delay = con.get('delay', {'delay_departure': 0,
|
||||
'delay_arrival': 0})
|
||||
# IMHO only delay_departure is useful
|
||||
con['delay'] = delay['delay_departure']
|
||||
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
|
||||
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
|
||||
|
@ -41,14 +33,12 @@ HM_UNIT_HA_CAST = {
|
|||
|
||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||
"""Setup the platform."""
|
||||
if discovery_info:
|
||||
return homematic.setup_hmdevice_discovery_helper(HMSensor,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
# Manual
|
||||
return homematic.setup_hmdevice_entity_helper(HMSensor,
|
||||
config,
|
||||
add_callback_devices)
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
return homematic.setup_hmdevice_discovery_helper(HMSensor,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
|
||||
|
||||
class HMSensor(homematic.HMDevice):
|
||||
|
|
|
@ -12,21 +12,24 @@ from glob import glob
|
|||
from homeassistant.const import STATE_UNKNOWN, TEMP_CELSIUS
|
||||
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__)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""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('Check if dtoverlay=w1-gpio,gpiopin=4.')
|
||||
_LOGGER.error('is in your /boot/config.txt and')
|
||||
|
@ -34,7 +37,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
return
|
||||
|
||||
devs = []
|
||||
names = SENSOR_IDS
|
||||
names = sensor_ids
|
||||
|
||||
for key in config.keys():
|
||||
if key == "names":
|
||||
|
@ -47,9 +50,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
# map names to ids.
|
||||
elif isinstance(config['names'], dict):
|
||||
names = []
|
||||
for sensor_id in SENSOR_IDS:
|
||||
for sensor_id in sensor_ids:
|
||||
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))
|
||||
add_devices(devs)
|
||||
|
||||
|
@ -88,14 +91,27 @@ class OneWire(Entity):
|
|||
|
||||
def update(self):
|
||||
"""Get the latest data from the device."""
|
||||
lines = self._read_temp_raw()
|
||||
while lines[0].strip()[-3:] != 'YES':
|
||||
time.sleep(0.2)
|
||||
temp = -99
|
||||
if self._device_file.startswith('/sys/bus/w1/devices'):
|
||||
lines = self._read_temp_raw()
|
||||
equals_pos = lines[1].find('t=')
|
||||
if equals_pos != -1:
|
||||
temp_string = lines[1][equals_pos+2:]
|
||||
temp = round(float(temp_string) / 1000.0, 1)
|
||||
if temp < -55 or temp > 125:
|
||||
return
|
||||
self._state = temp
|
||||
while lines[0].strip()[-3:] != 'YES':
|
||||
time.sleep(0.2)
|
||||
lines = self._read_temp_raw()
|
||||
equals_pos = lines[1].find('t=')
|
||||
if equals_pos != -1:
|
||||
temp_string = lines[1][equals_pos+2:]
|
||||
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
|
||||
import logging
|
||||
import requests
|
||||
|
@ -41,7 +46,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
|
||||
class OpenexchangeratesSensor(Entity):
|
||||
"""Implementing the Openexchangerates sensor."""
|
||||
"""Representation of an Openexchangerates sensor."""
|
||||
|
||||
def __init__(self, rest, name, quote):
|
||||
"""Initialize the sensor."""
|
||||
|
@ -87,7 +92,7 @@ class OpenexchangeratesData(object):
|
|||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
"""Get the latest data from openexchangerates."""
|
||||
"""Get the latest data from openexchangerates.org."""
|
||||
try:
|
||||
result = requests.get(self._resource, params={'base': self._base,
|
||||
'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
|
||||
from datetime import timedelta
|
||||
|
||||
|
|
|
@ -7,11 +7,12 @@ at https://home-assistant.io/components/sensor.wink/
|
|||
import logging
|
||||
|
||||
from homeassistant.const import (CONF_ACCESS_TOKEN, STATE_CLOSED,
|
||||
STATE_OPEN, TEMP_CELSIUS,
|
||||
ATTR_BATTERY_LEVEL)
|
||||
STATE_OPEN, TEMP_CELSIUS)
|
||||
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']
|
||||
|
||||
|
@ -38,14 +39,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
add_devices(WinkEggMinder(eggtray) for eggtray in pywink.get_eggtrays())
|
||||
|
||||
|
||||
class WinkSensorDevice(Entity):
|
||||
class WinkSensorDevice(WinkDevice, Entity):
|
||||
"""Representation of a Wink sensor."""
|
||||
|
||||
def __init__(self, wink):
|
||||
"""Initialize the sensor."""
|
||||
self.wink = wink
|
||||
"""Initialize the Wink device."""
|
||||
super().__init__(wink)
|
||||
wink = get_component('wink')
|
||||
self.capability = self.wink.capability()
|
||||
self._battery = self.wink.battery_level
|
||||
if self.wink.UNIT == "°":
|
||||
self._unit_of_measurement = TEMP_CELSIUS
|
||||
else:
|
||||
|
@ -55,9 +56,9 @@ class WinkSensorDevice(Entity):
|
|||
def state(self):
|
||||
"""Return the state."""
|
||||
if self.capability == "humidity":
|
||||
return self.wink.humidity_percentage()
|
||||
return round(self.wink.humidity_percentage())
|
||||
elif self.capability == "temperature":
|
||||
return self.wink.temperature_float()
|
||||
return round(self.wink.temperature_float(), 1)
|
||||
else:
|
||||
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 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
|
||||
def is_open(self):
|
||||
"""Return true if door is open."""
|
||||
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
|
||||
def _battery_level(self):
|
||||
"""Return the battery level."""
|
||||
return self.wink.battery_level * 100
|
||||
|
||||
|
||||
class WinkEggMinder(Entity):
|
||||
class WinkEggMinder(WinkDevice, Entity):
|
||||
"""Representation of a Wink Egg Minder."""
|
||||
|
||||
def __init__(self, wink):
|
||||
"""Initialize the sensor."""
|
||||
self.wink = wink
|
||||
self._battery = self.wink.battery_level
|
||||
WinkDevice.__init__(self, wink)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the 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.util import dt as dt_util
|
||||
from homeassistant.util import location
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -54,16 +53,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
"""Setup the Yr.no sensor."""
|
||||
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
||||
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):
|
||||
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||
return False
|
||||
|
||||
if elevation is None:
|
||||
elevation = location.elevation(latitude,
|
||||
longitude)
|
||||
|
||||
coordinates = dict(lat=latitude,
|
||||
lon=longitude,
|
||||
msl=elevation)
|
||||
|
|
|
@ -12,7 +12,6 @@ from homeassistant.helpers.entity import Entity
|
|||
from homeassistant.helpers.event import (
|
||||
track_point_in_utc_time, track_utc_time_change)
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util import location as location_util
|
||||
from homeassistant.const import CONF_ELEVATION
|
||||
|
||||
REQUIREMENTS = ['astral==1.2']
|
||||
|
@ -108,7 +107,7 @@ def setup(hass, config):
|
|||
|
||||
elevation = platform_config.get(CONF_ELEVATION)
|
||||
if elevation is None:
|
||||
elevation = location_util.elevation(latitude, longitude)
|
||||
elevation = hass.config.elevation or 0
|
||||
|
||||
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
|
||||
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
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.const import STATE_UNKNOWN
|
||||
|
@ -27,19 +15,17 @@ DEPENDENCIES = ['homematic']
|
|||
|
||||
|
||||
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
|
||||
"""Setup the platform."""
|
||||
if discovery_info:
|
||||
return homematic.setup_hmdevice_discovery_helper(HMSwitch,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
# Manual
|
||||
return homematic.setup_hmdevice_entity_helper(HMSwitch,
|
||||
config,
|
||||
add_callback_devices)
|
||||
"""Setup the Homematic switch platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
return homematic.setup_hmdevice_discovery_helper(HMSwitch,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
|
||||
|
||||
class HMSwitch(homematic.HMDevice, SwitchDevice):
|
||||
"""Represents a Homematic Switch in Home Assistant."""
|
||||
"""Representation of a Homematic switch."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
@ -71,24 +57,24 @@ class HMSwitch(homematic.HMDevice, SwitchDevice):
|
|||
self._hmdevice.off(self._channel)
|
||||
|
||||
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
|
||||
|
||||
# Check compatibility from HMDevice
|
||||
if not super()._check_hm_to_ha_object():
|
||||
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):
|
||||
return True
|
||||
if isinstance(self._hmdevice, Dimmer):
|
||||
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
|
||||
|
||||
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, 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 logging
|
||||
from datetime import timedelta
|
||||
|
|
|
@ -52,7 +52,7 @@ class WOLSwitch(SwitchDevice):
|
|||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""True if switch is on."""
|
||||
"""Return true if switch is on."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
|
|
|
@ -6,10 +6,11 @@ https://home-assistant.io/components/switch.wink/
|
|||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.wink import WinkToggleDevice
|
||||
from homeassistant.components.wink import WinkDevice
|
||||
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):
|
||||
|
@ -31,3 +32,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
add_devices(WinkToggleDevice(switch) for switch in
|
||||
pywink.get_powerstrip_outlets())
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
from homeassistant.components.thermostat import ThermostatDevice
|
||||
from homeassistant.const import TEMP_CELCIUS
|
||||
from homeassistant.helpers.temperature import convert
|
||||
|
||||
REQUIREMENTS = ['bluepy_devices>=0.2.0']
|
||||
REQUIREMENTS = ['bluepy_devices==0.2.0']
|
||||
|
||||
CONF_MAC = 'mac'
|
||||
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
|
||||
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 homeassistant.components.homematic as homematic
|
||||
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):
|
||||
"""Setup the platform."""
|
||||
if discovery_info:
|
||||
return homematic.setup_hmdevice_discovery_helper(HMThermostat,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
# Manual
|
||||
return homematic.setup_hmdevice_entity_helper(HMThermostat,
|
||||
config,
|
||||
add_callback_devices)
|
||||
"""Setup the Homematic thermostat platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
return homematic.setup_hmdevice_discovery_helper(HMThermostat,
|
||||
discovery_info,
|
||||
add_callback_devices)
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class HMThermostat(homematic.HMDevice, ThermostatDevice):
|
||||
"""Represents a Homematic Thermostat in Home Assistant."""
|
||||
"""Representation of a Homematic thermostat."""
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
|
@ -78,7 +65,7 @@ class HMThermostat(homematic.HMDevice, ThermostatDevice):
|
|||
return convert(30.5, TEMP_CELSIUS, self.unit_of_measurement)
|
||||
|
||||
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\
|
||||
as pyHMThermostat
|
||||
|
||||
|
@ -86,7 +73,7 @@ class HMThermostat(homematic.HMDevice, ThermostatDevice):
|
|||
if not super()._check_hm_to_ha_object():
|
||||
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):
|
||||
return True
|
||||
|
||||
|
@ -94,7 +81,7 @@ class HMThermostat(homematic.HMDevice, ThermostatDevice):
|
|||
return False
|
||||
|
||||
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()
|
||||
|
||||
# Add state to data dict
|
||||
|
|
|
@ -13,12 +13,13 @@ from homeassistant.helpers import discovery
|
|||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['pyvera==0.2.10']
|
||||
REQUIREMENTS = ['pyvera==0.2.12']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'vera'
|
||||
|
||||
|
||||
VERA_CONTROLLER = None
|
||||
|
||||
CONF_EXCLUDE = 'exclude'
|
||||
|
@ -33,6 +34,7 @@ DEVICE_CATEGORIES = {
|
|||
'Switch': 'switch',
|
||||
'Armable Sensor': 'switch',
|
||||
'On/Off Switch': 'switch',
|
||||
'Doorlock': 'lock',
|
||||
# 'Window Covering': NOT SUPPORTED YET
|
||||
}
|
||||
|
||||
|
@ -91,7 +93,7 @@ def setup(hass, base_config):
|
|||
dev_type = 'light'
|
||||
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)
|
||||
|
||||
return True
|
||||
|
|
|
@ -5,13 +5,17 @@ For more details about this component, please refer to the documentation at
|
|||
https://home-assistant.io/components/wink/
|
||||
"""
|
||||
import logging
|
||||
import json
|
||||
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL
|
||||
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"
|
||||
REQUIREMENTS = ['python-wink==0.7.7']
|
||||
REQUIREMENTS = ['python-wink==0.7.8', 'pubnub==3.7.8']
|
||||
|
||||
SUBSCRIPTION_HANDLER = None
|
||||
CHANNELS = []
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
|
@ -22,7 +26,11 @@ def setup(hass, config):
|
|||
return False
|
||||
|
||||
import pywink
|
||||
from pubnub import Pubnub
|
||||
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
|
||||
for component_name, func_exists in (
|
||||
|
@ -41,13 +49,33 @@ def setup(hass, config):
|
|||
return True
|
||||
|
||||
|
||||
class WinkToggleDevice(ToggleEntity):
|
||||
"""Represents a Wink toggle (switch) device."""
|
||||
class WinkDevice(Entity):
|
||||
"""Represents a base Wink device."""
|
||||
|
||||
def __init__(self, wink):
|
||||
"""Initialize the Wink device."""
|
||||
from pubnub import Pubnub
|
||||
self.wink = wink
|
||||
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
|
||||
def unique_id(self):
|
||||
|
@ -59,28 +87,20 @@ class WinkToggleDevice(ToggleEntity):
|
|||
"""Return the name of the device."""
|
||||
return self.wink.name()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self.wink.state()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""True if connection == True."""
|
||||
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):
|
||||
"""Update state of the device."""
|
||||
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
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
|
|
|
@ -41,6 +41,7 @@ EVENT_SCENE_ACTIVATED = "zwave.scene_activated"
|
|||
|
||||
COMMAND_CLASS_WHATEVER = None
|
||||
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
|
||||
COMMAND_CLASS_COLOR = 51
|
||||
COMMAND_CLASS_METER = 50
|
||||
COMMAND_CLASS_ALARM = 113
|
||||
COMMAND_CLASS_SWITCH_BINARY = 37
|
||||
|
|
|
@ -1,31 +1,35 @@
|
|||
"""Module to help with parsing and generating configuration files."""
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
from types import MappingProxyType
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.util.location as loc_util
|
||||
from homeassistant.const import (
|
||||
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.util.yaml import load_yaml
|
||||
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__)
|
||||
|
||||
YAML_CONFIG_FILE = 'configuration.yaml'
|
||||
VERSION_FILE = '.HA_VERSION'
|
||||
CONFIG_DIR_NAME = '.homeassistant'
|
||||
|
||||
DEFAULT_CONFIG = (
|
||||
# Tuples (attribute, default, auto detect property, description)
|
||||
(CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is '
|
||||
'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'),
|
||||
(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_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki'
|
||||
'pedia.org/wiki/List_of_tz_database_time_zones'),
|
||||
|
@ -39,7 +43,7 @@ DEFAULT_COMPONENTS = {
|
|||
'history:': 'Enables support for tracking state changes over time.',
|
||||
'logbook:': 'View all events in a logbook',
|
||||
'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_LATITUDE: cv.latitude,
|
||||
CONF_LONGITUDE: cv.longitude,
|
||||
CONF_ELEVATION: vol.Coerce(float),
|
||||
CONF_TEMPERATURE_UNIT: cv.temperature_unit,
|
||||
CONF_TIME_ZONE: cv.time_zone,
|
||||
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.
|
||||
"""
|
||||
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}
|
||||
|
||||
|
@ -111,6 +117,10 @@ def create_default_config(config_dir, detect_location=True):
|
|||
continue
|
||||
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
|
||||
# So we're hard coding a YAML template.
|
||||
try:
|
||||
|
@ -130,6 +140,9 @@ def create_default_config(config_dir, detect_location=True):
|
|||
config_file.write("# {}\n".format(description))
|
||||
config_file.write("{}\n\n".format(component))
|
||||
|
||||
with open(version_path, 'wt') as version_file:
|
||||
version_file.write(__version__)
|
||||
|
||||
return config_path
|
||||
|
||||
except IOError:
|
||||
|
@ -155,3 +168,112 @@ def load_yaml_config_file(config_path):
|
|||
raise HomeAssistantError(msg)
|
||||
|
||||
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__)
|
||||
|
||||
|
||||
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):
|
||||
"""Root object of the Home Assistant home automation."""
|
||||
|
||||
|
@ -59,14 +72,23 @@ class HomeAssistant(object):
|
|||
self.services = ServiceRegistry(self.bus, pool)
|
||||
self.states = StateMachine(self.bus)
|
||||
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):
|
||||
"""Start home assistant."""
|
||||
_LOGGER.info(
|
||||
"Starting Home Assistant (%d threads)", self.pool.worker_count)
|
||||
self.state = CoreState.starting
|
||||
|
||||
create_timer(self)
|
||||
self.bus.fire(EVENT_HOMEASSISTANT_START)
|
||||
self.pool.block_till_done()
|
||||
self.state = CoreState.running
|
||||
|
||||
def block_till_stopped(self):
|
||||
"""Register service homeassistant/stop and will block until called."""
|
||||
|
@ -113,8 +135,10 @@ class HomeAssistant(object):
|
|||
def stop(self):
|
||||
"""Stop Home Assistant and shuts down all threads."""
|
||||
_LOGGER.info("Stopping")
|
||||
self.state = CoreState.stopping
|
||||
self.bus.fire(EVENT_HOMEASSISTANT_STOP)
|
||||
self.pool.stop()
|
||||
self.state = CoreState.not_running
|
||||
|
||||
|
||||
class JobPriority(util.OrderedEnum):
|
||||
|
@ -681,6 +705,7 @@ class Config(object):
|
|||
"""Initialize a new config object."""
|
||||
self.latitude = None
|
||||
self.longitude = None
|
||||
self.elevation = None
|
||||
self.temperature_unit = None
|
||||
self.location_name = None
|
||||
self.time_zone = None
|
||||
|
|
|
@ -11,6 +11,7 @@ from datetime import datetime
|
|||
import enum
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
import threading
|
||||
import urllib.parse
|
||||
|
||||
|
@ -123,6 +124,7 @@ class HomeAssistant(ha.HomeAssistant):
|
|||
self.services = ha.ServiceRegistry(self.bus, pool)
|
||||
self.states = StateMachine(self.bus, self.remote_api)
|
||||
self.config = ha.Config()
|
||||
self.state = ha.CoreState.not_running
|
||||
|
||||
self.config.api = local_api
|
||||
|
||||
|
@ -134,17 +136,20 @@ class HomeAssistant(ha.HomeAssistant):
|
|||
raise HomeAssistantError(
|
||||
'Unable to setup local API to receive events')
|
||||
|
||||
self.state = ha.CoreState.starting
|
||||
ha.create_timer(self)
|
||||
|
||||
self.bus.fire(ha.EVENT_HOMEASSISTANT_START,
|
||||
origin=ha.EventOrigin.remote)
|
||||
|
||||
# Give eventlet time to startup
|
||||
import eventlet
|
||||
eventlet.sleep(0.1)
|
||||
# Ensure local HTTP is started
|
||||
self.pool.block_till_done()
|
||||
self.state = ha.CoreState.running
|
||||
time.sleep(0.05)
|
||||
|
||||
# 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):
|
||||
raise HomeAssistantError((
|
||||
'Could not setup event forwarding from api {} to '
|
||||
|
@ -153,6 +158,7 @@ class HomeAssistant(ha.HomeAssistant):
|
|||
def stop(self):
|
||||
"""Stop Home Assistant and shuts down all threads."""
|
||||
_LOGGER.info("Stopping")
|
||||
self.state = ha.CoreState.stopping
|
||||
|
||||
self.bus.fire(ha.EVENT_HOMEASSISTANT_STOP,
|
||||
origin=ha.EventOrigin.remote)
|
||||
|
@ -161,6 +167,7 @@ class HomeAssistant(ha.HomeAssistant):
|
|||
|
||||
# Disconnect master event forwarding
|
||||
disconnect_remote_events(self.remote_api, self.config.api)
|
||||
self.state = ha.CoreState.not_running
|
||||
|
||||
|
||||
class EventBus(ha.EventBus):
|
||||
|
|
|
@ -8,7 +8,8 @@ import math
|
|||
import requests
|
||||
|
||||
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
|
||||
# Earth ellipsoid according to WGS 84
|
||||
|
@ -32,30 +33,13 @@ LocationInfo = collections.namedtuple(
|
|||
|
||||
def detect_location_info():
|
||||
"""Detect location information."""
|
||||
success = None
|
||||
data = _get_freegeoip()
|
||||
|
||||
for source in DATA_SOURCE:
|
||||
try:
|
||||
raw_info = requests.get(source, timeout=5).json()
|
||||
success = source
|
||||
break
|
||||
except (requests.RequestException, ValueError):
|
||||
success = False
|
||||
if data is None:
|
||||
data = _get_ip_api()
|
||||
|
||||
if success is False:
|
||||
if data is 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,
|
||||
# the Cayman Islands, Palau, and the United States and associated
|
||||
|
@ -73,11 +57,16 @@ def distance(lat1, lon1, lat2, lon2):
|
|||
|
||||
def elevation(latitude, longitude):
|
||||
"""Return elevation for given latitude and longitude."""
|
||||
req = requests.get(ELEVATION_URL,
|
||||
params={'locations': '{},{}'.format(latitude,
|
||||
longitude),
|
||||
'sensor': 'false'},
|
||||
timeout=10)
|
||||
try:
|
||||
req = requests.get(
|
||||
ELEVATION_URL,
|
||||
params={
|
||||
'locations': '{},{}'.format(latitude, longitude),
|
||||
'sensor': 'false',
|
||||
},
|
||||
timeout=10)
|
||||
except requests.RequestException:
|
||||
return 0
|
||||
|
||||
if req.status_code != 200:
|
||||
return 0
|
||||
|
@ -157,3 +146,45 @@ def vincenty(point1, point2, miles=False):
|
|||
s *= MILES_PER_KILOMETER # kilometers to miles
|
||||
|
||||
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
|
||||
jinja2>=2.8
|
||||
voluptuous==0.8.9
|
||||
eventlet==0.19.0
|
||||
|
||||
# homeassistant.components.isy994
|
||||
PyISY==1.0.6
|
||||
|
@ -23,7 +22,7 @@ SoCo==0.11.1
|
|||
TwitterAPI==2.4.1
|
||||
|
||||
# homeassistant.components.http
|
||||
Werkzeug==0.11.5
|
||||
Werkzeug==0.11.10
|
||||
|
||||
# homeassistant.components.apcupsd
|
||||
apcaccess==0.0.4
|
||||
|
@ -41,13 +40,16 @@ blinkstick==1.1.7
|
|||
blockchain==1.3.3
|
||||
|
||||
# homeassistant.components.thermostat.eq3btsmart
|
||||
# bluepy_devices>=0.2.0
|
||||
# bluepy_devices==0.2.0
|
||||
|
||||
# homeassistant.components.notify.aws_lambda
|
||||
# homeassistant.components.notify.aws_sns
|
||||
# homeassistant.components.notify.aws_sqs
|
||||
boto3==1.3.1
|
||||
|
||||
# homeassistant.components.http
|
||||
cherrypy==6.0.2
|
||||
|
||||
# homeassistant.components.notify.xmpp
|
||||
dnspython3==1.12.0
|
||||
|
||||
|
@ -61,9 +63,6 @@ eliqonline==1.0.12
|
|||
# homeassistant.components.enocean
|
||||
enocean==0.31
|
||||
|
||||
# homeassistant.components.http
|
||||
eventlet==0.19.0
|
||||
|
||||
# homeassistant.components.thermostat.honeywell
|
||||
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
|
||||
|
||||
# homeassistant.components.influxdb
|
||||
influxdb==2.12.0
|
||||
influxdb==3.0.0
|
||||
|
||||
# homeassistant.components.insteon_hub
|
||||
insteon_hub==0.4.5
|
||||
|
||||
# homeassistant.components.media_player.kodi
|
||||
jsonrpc-requests==0.2
|
||||
jsonrpc-requests==0.3
|
||||
|
||||
# homeassistant.components.light.lifx
|
||||
liffylights==0.9.4
|
||||
|
@ -220,6 +219,16 @@ proliphix==0.1.0
|
|||
# homeassistant.components.sensor.systemmonitor
|
||||
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
|
||||
pushbullet.py==0.10.0
|
||||
|
||||
|
@ -245,7 +254,7 @@ pyasn1==0.1.9
|
|||
pychromecast==0.7.2
|
||||
|
||||
# homeassistant.components.media_player.cmus
|
||||
pycmus>=0.1.0
|
||||
pycmus==0.1.0
|
||||
|
||||
# homeassistant.components.envisalink
|
||||
# homeassistant.components.zwave
|
||||
|
@ -258,7 +267,7 @@ pyenvisalink==1.0
|
|||
pyfttt==0.3
|
||||
|
||||
# homeassistant.components.homematic
|
||||
pyhomematic==0.1.6
|
||||
pyhomematic==0.1.8
|
||||
|
||||
# homeassistant.components.device_tracker.icloud
|
||||
pyicloud==0.8.3
|
||||
|
@ -324,13 +333,13 @@ python-twitch==1.2.0
|
|||
# homeassistant.components.rollershutter.wink
|
||||
# homeassistant.components.sensor.wink
|
||||
# homeassistant.components.switch.wink
|
||||
python-wink==0.7.7
|
||||
python-wink==0.7.8
|
||||
|
||||
# homeassistant.components.keyboard
|
||||
pyuserinput==0.1.9
|
||||
|
||||
# homeassistant.components.vera
|
||||
pyvera==0.2.10
|
||||
pyvera==0.2.12
|
||||
|
||||
# homeassistant.components.wemo
|
||||
pywemo==0.4.3
|
||||
|
@ -410,7 +419,7 @@ vsure==0.8.1
|
|||
wakeonlan==0.2.2
|
||||
|
||||
# homeassistant.components.media_player.gpmdp
|
||||
websocket-client==0.35.0
|
||||
websocket-client==0.37.0
|
||||
|
||||
# homeassistant.components.zigbee
|
||||
xbee-helper==0.0.7
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
flake8>=2.5.4
|
||||
pylint>=1.5.5
|
||||
flake8>=2.6.0
|
||||
pylint>=1.5.6
|
||||
coveralls>=1.1
|
||||
pytest>=2.9.1
|
||||
pytest-cov>=2.2.0
|
||||
pytest>=2.9.2
|
||||
pytest-cov>=2.2.1
|
||||
pytest-timeout>=1.0.0
|
||||
pytest-capturelog>=0.7
|
||||
betamax==0.7.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',
|
||||
'jinja2>=2.8',
|
||||
'voluptuous==0.8.9',
|
||||
'eventlet==0.19.0',
|
||||
]
|
||||
|
||||
setup(
|
||||
|
|
|
@ -1,27 +1,25 @@
|
|||
"""Test the initialization."""
|
||||
import betamax
|
||||
"""Setup some common test helper things."""
|
||||
import functools
|
||||
|
||||
from homeassistant import util
|
||||
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
|
||||
# so mocked by default.
|
||||
location.detect_location_info = lambda: location.LocationInfo(
|
||||
ip='1.1.1.1',
|
||||
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='2.0',
|
||||
longitude='1.0',
|
||||
use_fahrenheit=True,
|
||||
)
|
||||
def test_real(func):
|
||||
"""Force a function to require a keyword _test_real to be passed in."""
|
||||
@functools.wraps(func)
|
||||
def guard_func(*args, **kwargs):
|
||||
real = kwargs.pop('_test_real', None)
|
||||
|
||||
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'
|
||||
|
|
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.latitude = 32.87336
|
||||
hass.config.longitude = -117.22743
|
||||
hass.config.elevation = 0
|
||||
hass.config.time_zone = date_util.get_time_zone('US/Pacific')
|
||||
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))
|
||||
|
||||
|
||||
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):
|
||||
"""Mock state change envent."""
|
||||
event_data = {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"""The tests the for Locative device tracker platform."""
|
||||
import time
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import eventlet
|
||||
import requests
|
||||
|
||||
from homeassistant import bootstrap, const
|
||||
|
@ -32,12 +32,9 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||
bootstrap.setup_component(hass, http.DOMAIN, {
|
||||
http.DOMAIN: {
|
||||
http.CONF_SERVER_PORT: SERVER_PORT
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
# Set up API
|
||||
bootstrap.setup_component(hass, 'api')
|
||||
|
||||
# Set up device tracker
|
||||
bootstrap.setup_component(hass, device_tracker.DOMAIN, {
|
||||
device_tracker.DOMAIN: {
|
||||
|
@ -46,7 +43,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||
})
|
||||
|
||||
hass.start()
|
||||
eventlet.sleep(0.05)
|
||||
time.sleep(0.05)
|
||||
|
||||
|
||||
def tearDownModule(): # pylint: disable=invalid-name
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
"""The tests for the forecast.io platform."""
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import forecastio
|
||||
import httpretty
|
||||
from requests.exceptions import HTTPError
|
||||
import requests_mock
|
||||
|
||||
from homeassistant.components.sensor import forecast
|
||||
from homeassistant import core as ha
|
||||
|
||||
from tests.common import load_fixture
|
||||
|
||||
|
||||
class TestForecastSetup(unittest.TestCase):
|
||||
"""Test the forecast.io platform."""
|
||||
|
@ -48,29 +48,14 @@ class TestForecastSetup(unittest.TestCase):
|
|||
response = forecast.setup_platform(self.hass, self.config, MagicMock())
|
||||
self.assertFalse(response)
|
||||
|
||||
@httpretty.activate
|
||||
@requests_mock.Mocker()
|
||||
@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."""
|
||||
def load_fixture_from_json():
|
||||
cwd = os.path.dirname(__file__)
|
||||
fixture_path = os.path.join(cwd, '..', 'fixtures', 'forecast.json')
|
||||
with open(fixture_path) as file:
|
||||
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
|
||||
|
||||
uri = ('https://api.forecast.io\/forecast\/(\w+)\/'
|
||||
'(-?\d+\.?\d*),(-?\d+\.?\d*)')
|
||||
m.get(re.compile(uri),
|
||||
text=load_fixture('forecast.json'))
|
||||
forecast.setup_platform(self.hass, self.config, MagicMock())
|
||||
self.assertTrue(mock_get_forecast.called)
|
||||
self.assertEqual(mock_get_forecast.call_count, 1)
|
|
@ -1,39 +1,40 @@
|
|||
"""The tests for the Yr sensor platform."""
|
||||
from datetime import datetime
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import requests_mock
|
||||
|
||||
from homeassistant.bootstrap import _setup_component
|
||||
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:
|
||||
class TestSensorYr(TestCase):
|
||||
"""Test the Yr sensor."""
|
||||
|
||||
def setup_method(self, method):
|
||||
def setUp(self):
|
||||
"""Setup things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.hass.config.latitude = 32.87336
|
||||
self.hass.config.longitude = 117.22743
|
||||
|
||||
def teardown_method(self, method):
|
||||
def tearDown(self):
|
||||
"""Stop everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_default_setup(self, betamax_session):
|
||||
@requests_mock.Mocker()
|
||||
def test_default_setup(self, m):
|
||||
"""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)
|
||||
|
||||
with patch('homeassistant.components.sensor.yr.requests.Session',
|
||||
return_value=betamax_session):
|
||||
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
|
||||
return_value=now):
|
||||
assert _setup_component(self.hass, 'sensor', {
|
||||
'sensor': {'platform': 'yr',
|
||||
'elevation': 0}})
|
||||
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
|
||||
return_value=now):
|
||||
assert _setup_component(self.hass, 'sensor', {
|
||||
'sensor': {'platform': 'yr',
|
||||
'elevation': 0}})
|
||||
|
||||
state = self.hass.states.get('sensor.yr_symbol')
|
||||
|
||||
|
@ -41,23 +42,24 @@ class TestSensorYr:
|
|||
assert state.state.isnumeric()
|
||||
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."""
|
||||
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)
|
||||
|
||||
with patch('homeassistant.components.sensor.yr.requests.Session',
|
||||
return_value=betamax_session):
|
||||
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
|
||||
return_value=now):
|
||||
assert _setup_component(self.hass, 'sensor', {
|
||||
'sensor': {'platform': 'yr',
|
||||
'elevation': 0,
|
||||
'monitored_conditions': [
|
||||
'pressure',
|
||||
'windDirection',
|
||||
'humidity',
|
||||
'fog',
|
||||
'windSpeed']}})
|
||||
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
|
||||
return_value=now):
|
||||
assert _setup_component(self.hass, 'sensor', {
|
||||
'sensor': {'platform': 'yr',
|
||||
'elevation': 0,
|
||||
'monitored_conditions': [
|
||||
'pressure',
|
||||
'windDirection',
|
||||
'humidity',
|
||||
'fog',
|
||||
'windSpeed']}})
|
||||
|
||||
state = self.hass.states.get('sensor.yr_pressure')
|
||||
assert 'hPa' == state.attributes.get('unit_of_measurement')
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
"""The tests for the Alexa component."""
|
||||
# pylint: disable=protected-access,too-many-public-methods
|
||||
import unittest
|
||||
import json
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import eventlet
|
||||
import requests
|
||||
|
||||
from homeassistant import bootstrap, const
|
||||
|
@ -86,8 +86,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||
})
|
||||
|
||||
hass.start()
|
||||
|
||||
eventlet.sleep(0.1)
|
||||
time.sleep(0.05)
|
||||
|
||||
|
||||
def tearDownModule(): # pylint: disable=invalid-name
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
"""The tests for the Home Assistant API component."""
|
||||
# pylint: disable=protected-access,too-many-public-methods
|
||||
# from contextlib import closing
|
||||
from contextlib import closing
|
||||
import json
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import eventlet
|
||||
import requests
|
||||
|
||||
from homeassistant import bootstrap, const
|
||||
|
@ -48,10 +48,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||
bootstrap.setup_component(hass, 'api')
|
||||
|
||||
hass.start()
|
||||
|
||||
# To start HTTP
|
||||
# TODO fix this
|
||||
eventlet.sleep(0.05)
|
||||
time.sleep(0.05)
|
||||
|
||||
|
||||
def tearDownModule(): # pylint: disable=invalid-name
|
||||
|
@ -387,25 +384,23 @@ class TestAPI(unittest.TestCase):
|
|||
headers=HA_HEADERS)
|
||||
self.assertEqual(422, req.status_code)
|
||||
|
||||
# TODO disabled because eventlet cannot validate
|
||||
# a connection to itself, need a second instance
|
||||
# # Setup a real one
|
||||
# req = requests.post(
|
||||
# _url(const.URL_API_EVENT_FORWARD),
|
||||
# data=json.dumps({
|
||||
# 'api_password': API_PASSWORD,
|
||||
# 'host': '127.0.0.1',
|
||||
# 'port': SERVER_PORT
|
||||
# }),
|
||||
# headers=HA_HEADERS)
|
||||
# self.assertEqual(200, req.status_code)
|
||||
# Setup a real one
|
||||
req = requests.post(
|
||||
_url(const.URL_API_EVENT_FORWARD),
|
||||
data=json.dumps({
|
||||
'api_password': API_PASSWORD,
|
||||
'host': '127.0.0.1',
|
||||
'port': SERVER_PORT
|
||||
}),
|
||||
headers=HA_HEADERS)
|
||||
self.assertEqual(200, req.status_code)
|
||||
|
||||
# # Delete it again..
|
||||
# req = requests.delete(
|
||||
# _url(const.URL_API_EVENT_FORWARD),
|
||||
# data=json.dumps({}),
|
||||
# headers=HA_HEADERS)
|
||||
# self.assertEqual(400, req.status_code)
|
||||
# Delete it again..
|
||||
req = requests.delete(
|
||||
_url(const.URL_API_EVENT_FORWARD),
|
||||
data=json.dumps({}),
|
||||
headers=HA_HEADERS)
|
||||
self.assertEqual(400, req.status_code)
|
||||
|
||||
req = requests.delete(
|
||||
_url(const.URL_API_EVENT_FORWARD),
|
||||
|
@ -425,57 +420,58 @@ class TestAPI(unittest.TestCase):
|
|||
headers=HA_HEADERS)
|
||||
self.assertEqual(200, req.status_code)
|
||||
|
||||
# def test_stream(self):
|
||||
# """Test the stream."""
|
||||
# listen_count = self._listen_count()
|
||||
# with closing(requests.get(_url(const.URL_API_STREAM), timeout=3,
|
||||
# stream=True, headers=HA_HEADERS)) as req:
|
||||
def test_stream(self):
|
||||
"""Test the stream."""
|
||||
listen_count = self._listen_count()
|
||||
with closing(requests.get(_url(const.URL_API_STREAM), timeout=3,
|
||||
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):
|
||||
# """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:
|
||||
# self.assertEqual(listen_count + 1, self._listen_count())
|
||||
hass.bus.fire('test_event1')
|
||||
data = self._stream_next_event(stream)
|
||||
self.assertEqual('test_event1', data['event_type'])
|
||||
|
||||
# hass.bus.fire('test_event1')
|
||||
# data = self._stream_next_event(req)
|
||||
# self.assertEqual('test_event1', data['event_type'])
|
||||
hass.bus.fire('test_event2')
|
||||
hass.bus.fire('test_event3')
|
||||
|
||||
# hass.bus.fire('test_event2')
|
||||
# hass.bus.fire('test_event3')
|
||||
data = self._stream_next_event(stream)
|
||||
self.assertEqual('test_event3', data['event_type'])
|
||||
|
||||
# data = self._stream_next_event(req)
|
||||
# self.assertEqual('test_event3', data['event_type'])
|
||||
def _stream_next_event(self, stream):
|
||||
"""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):
|
||||
# """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:]
|
||||
|
||||
# conv = data.decode('utf-8').strip()[6:]
|
||||
if conv != 'ping':
|
||||
break
|
||||
|
||||
# if conv != 'ping':
|
||||
# break
|
||||
return json.loads(conv)
|
||||
|
||||
# return json.loads(conv)
|
||||
|
||||
# def _listen_count(self):
|
||||
# """Return number of event listeners."""
|
||||
# return sum(hass.bus.listeners.values())
|
||||
def _listen_count(self):
|
||||
"""Return number of event listeners."""
|
||||
return sum(hass.bus.listeners.values())
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
"""The tests for Home Assistant frontend."""
|
||||
# pylint: disable=protected-access,too-many-public-methods
|
||||
import re
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import eventlet
|
||||
import requests
|
||||
|
||||
import homeassistant.bootstrap as bootstrap
|
||||
|
@ -42,10 +42,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||
bootstrap.setup_component(hass, 'frontend')
|
||||
|
||||
hass.start()
|
||||
|
||||
# Give eventlet time to start
|
||||
# TODO fix this
|
||||
eventlet.sleep(0.05)
|
||||
time.sleep(0.05)
|
||||
|
||||
|
||||
def tearDownModule(): # pylint: disable=invalid-name
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"""The tests for the Home Assistant HTTP component."""
|
||||
# pylint: disable=protected-access,too-many-public-methods
|
||||
import logging
|
||||
import time
|
||||
|
||||
import eventlet
|
||||
import requests
|
||||
|
||||
from homeassistant import bootstrap, const
|
||||
|
@ -43,8 +43,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||
bootstrap.setup_component(hass, 'api')
|
||||
|
||||
hass.start()
|
||||
|
||||
eventlet.sleep(0.05)
|
||||
time.sleep(0.05)
|
||||
|
||||
|
||||
def tearDownModule(): # pylint: disable=invalid-name
|
||||
|
@ -83,7 +82,7 @@ class TestHttp:
|
|||
|
||||
logs = caplog.text()
|
||||
|
||||
assert const.URL_API in logs
|
||||
# assert const.URL_API in logs
|
||||
assert API_PASSWORD not in logs
|
||||
|
||||
def test_access_denied_with_wrong_password_in_url(self):
|
||||
|
@ -106,5 +105,5 @@ class TestHttp:
|
|||
|
||||
logs = caplog.text()
|
||||
|
||||
assert const.URL_API in logs
|
||||
# assert const.URL_API in logs
|
||||
assert API_PASSWORD not in logs
|
||||
|
|
|
@ -131,7 +131,7 @@ class TestComponentsCore(unittest.TestCase):
|
|||
assert state.attributes.get('hello') == 'world'
|
||||
|
||||
@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):
|
||||
"""Test reload core conf service."""
|
||||
with TemporaryDirectory() as conf_dir:
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"results" : [
|
||||
{
|
||||
"elevation" : 101.5,
|
||||
"location" : {
|
||||
"lat" : 32.54321,
|
||||
"lng" : -117.12345
|
||||
},
|
||||
"resolution" : 4.8
|
||||
}
|
||||
],
|
||||
"status" : "OK"
|
||||
}
|
|
@ -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"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,5 @@
|
|||
"""Test the bootstrapping."""
|
||||
# pylint: disable=too-many-public-methods,protected-access
|
||||
import os
|
||||
import tempfile
|
||||
from unittest import mock
|
||||
import threading
|
||||
|
@ -8,10 +7,7 @@ import threading
|
|||
import voluptuous as vol
|
||||
|
||||
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
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
|
||||
|
||||
from tests.common import get_test_home_assistant, MockModule, MockPlatform
|
||||
|
@ -24,23 +20,22 @@ class TestBootstrap:
|
|||
|
||||
def setup_method(self, method):
|
||||
"""Setup the test."""
|
||||
self.backup_cache = loader._COMPONENT_CACHE
|
||||
|
||||
if method == self.test_from_config_file:
|
||||
return
|
||||
|
||||
self.hass = get_test_home_assistant()
|
||||
self.backup_cache = loader._COMPONENT_CACHE
|
||||
|
||||
def teardown_method(self, method):
|
||||
"""Clean up."""
|
||||
dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE
|
||||
|
||||
if method == self.test_from_config_file:
|
||||
return
|
||||
|
||||
self.hass.stop()
|
||||
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."""
|
||||
components = ['browser', 'conversation', 'script']
|
||||
with tempfile.NamedTemporaryFile() as fp:
|
||||
|
@ -48,71 +43,10 @@ class TestBootstrap:
|
|||
fp.write('{}:\n'.format(comp).encode('utf-8'))
|
||||
fp.flush()
|
||||
|
||||
hass = bootstrap.from_config_file(fp.name)
|
||||
self.hass = bootstrap.from_config_file(fp.name)
|
||||
|
||||
components.append('group')
|
||||
|
||||
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']
|
||||
components.append('group')
|
||||
assert sorted(components) == sorted(self.hass.config.components)
|
||||
|
||||
def test_handle_setup_circular_dependency(self):
|
||||
"""Test the setup of circular dependencies."""
|
||||
|
@ -302,8 +236,7 @@ class TestBootstrap:
|
|||
assert not bootstrap._setup_component(self.hass, 'comp', None)
|
||||
assert 'comp' not in self.hass.config.components
|
||||
|
||||
@mock.patch('homeassistant.bootstrap.process_ha_core_config')
|
||||
def test_home_assistant_core_config_validation(self, mock_process):
|
||||
def test_home_assistant_core_config_validation(self):
|
||||
"""Test if we pass in wrong information for HA conf."""
|
||||
# Extensive HA conf validation testing is done in test_config.py
|
||||
assert None is bootstrap.from_config_dict({
|
||||
|
@ -311,7 +244,6 @@ class TestBootstrap:
|
|||
'latitude': 'some string'
|
||||
}
|
||||
})
|
||||
assert not mock_process.called
|
||||
|
||||
def test_component_setup_with_validation_and_dependency(self):
|
||||
"""Test all config is passed to dependencies."""
|
||||
|
|
|
@ -1,22 +1,28 @@
|
|||
"""Test config utils."""
|
||||
# pylint: disable=too-many-public-methods,protected-access
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
import unittest.mock as mock
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from voluptuous import MultipleInvalid
|
||||
|
||||
from homeassistant.core import DOMAIN, HomeAssistantError
|
||||
from homeassistant.core import DOMAIN, HomeAssistantError, Config
|
||||
import homeassistant.config as config_util
|
||||
from homeassistant.const import (
|
||||
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()
|
||||
YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE)
|
||||
ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE
|
||||
|
||||
|
||||
def create_file(path):
|
||||
|
@ -30,9 +36,14 @@ class TestConfig(unittest.TestCase):
|
|||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
"""Clean up."""
|
||||
dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE
|
||||
|
||||
if os.path.isfile(YAML_PATH):
|
||||
os.remove(YAML_PATH)
|
||||
|
||||
if hasattr(self, 'hass'):
|
||||
self.hass.stop()
|
||||
|
||||
def test_create_default_config(self):
|
||||
"""Test creation of default config."""
|
||||
config_util.create_default_config(CONFIG_DIR, False)
|
||||
|
@ -108,8 +119,15 @@ class TestConfig(unittest.TestCase):
|
|||
[('hello', 0), ('world', 1)],
|
||||
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')
|
||||
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."""
|
||||
config_util.ensure_config_exists(CONFIG_DIR)
|
||||
|
||||
|
@ -120,15 +138,16 @@ class TestConfig(unittest.TestCase):
|
|||
ha_conf = config[DOMAIN]
|
||||
|
||||
expected_values = {
|
||||
CONF_LATITUDE: 2.0,
|
||||
CONF_LONGITUDE: 1.0,
|
||||
CONF_LATITUDE: 32.8594,
|
||||
CONF_LONGITUDE: -117.2073,
|
||||
CONF_ELEVATION: 101,
|
||||
CONF_TEMPERATURE_UNIT: 'F',
|
||||
CONF_NAME: 'Home',
|
||||
CONF_TIME_ZONE: 'America/Los_Angeles'
|
||||
}
|
||||
|
||||
self.assertEqual(expected_values, ha_conf)
|
||||
self.assertTrue(mock_print.called)
|
||||
assert expected_values == ha_conf
|
||||
assert mock_print.called
|
||||
|
||||
@mock.patch('builtins.print')
|
||||
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."""
|
||||
# pylint: disable=protected-access,too-many-public-methods
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import eventlet
|
||||
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.bootstrap as bootstrap
|
||||
import homeassistant.remote as remote
|
||||
|
@ -47,10 +46,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||
bootstrap.setup_component(hass, 'api')
|
||||
|
||||
hass.start()
|
||||
|
||||
# Give eventlet time to start
|
||||
# TODO fix this
|
||||
eventlet.sleep(0.05)
|
||||
time.sleep(0.05)
|
||||
|
||||
master_api = remote.API("127.0.0.1", API_PASSWORD, MASTER_PORT)
|
||||
|
||||
|
@ -63,10 +59,6 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||
|
||||
slave.start()
|
||||
|
||||
# Give eventlet time to start
|
||||
# TODO fix this
|
||||
eventlet.sleep(0.05)
|
||||
|
||||
|
||||
def tearDownModule(): # pylint: disable=invalid-name
|
||||
"""Stop the Home Assistant server and slave."""
|
||||
|
@ -257,7 +249,6 @@ class TestRemoteClasses(unittest.TestCase):
|
|||
slave.pool.block_till_done()
|
||||
# Wait till master gives updated state
|
||||
hass.pool.block_till_done()
|
||||
eventlet.sleep(0.01)
|
||||
|
||||
self.assertEqual("remote.statemachine test",
|
||||
slave.states.get("remote.test").state)
|
||||
|
@ -266,13 +257,11 @@ class TestRemoteClasses(unittest.TestCase):
|
|||
"""Remove statemachine from master."""
|
||||
hass.states.set("remote.master_remove", "remove me!")
|
||||
hass.pool.block_till_done()
|
||||
eventlet.sleep(0.01)
|
||||
|
||||
self.assertIn('remote.master_remove', slave.states.entity_ids())
|
||||
|
||||
hass.states.remove("remote.master_remove")
|
||||
hass.pool.block_till_done()
|
||||
eventlet.sleep(0.01)
|
||||
|
||||
self.assertNotIn('remote.master_remove', slave.states.entity_ids())
|
||||
|
||||
|
@ -280,14 +269,12 @@ class TestRemoteClasses(unittest.TestCase):
|
|||
"""Remove statemachine from slave."""
|
||||
hass.states.set("remote.slave_remove", "remove me!")
|
||||
hass.pool.block_till_done()
|
||||
eventlet.sleep(0.01)
|
||||
|
||||
self.assertIn('remote.slave_remove', slave.states.entity_ids())
|
||||
|
||||
self.assertTrue(slave.states.remove("remote.slave_remove"))
|
||||
slave.pool.block_till_done()
|
||||
hass.pool.block_till_done()
|
||||
eventlet.sleep(0.01)
|
||||
|
||||
self.assertNotIn('remote.slave_remove', slave.states.entity_ids())
|
||||
|
||||
|
@ -306,6 +293,5 @@ class TestRemoteClasses(unittest.TestCase):
|
|||
slave.pool.block_till_done()
|
||||
# Wait till master gives updated event
|
||||
hass.pool.block_till_done()
|
||||
eventlet.sleep(0.01)
|
||||
|
||||
self.assertEqual(1, len(test_value))
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
"""Test Home Assistant location util 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
|
||||
|
||||
from tests.common import load_fixture
|
||||
|
||||
# Paris
|
||||
COORDINATES_PARIS = (48.864716, 2.349014)
|
||||
# New York
|
||||
|
@ -20,26 +26,124 @@ DISTANCE_KM = 5846.39
|
|||
DISTANCE_MILES = 3632.78
|
||||
|
||||
|
||||
class TestLocationUtil(unittest.TestCase):
|
||||
class TestLocationUtil(TestCase):
|
||||
"""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):
|
||||
"""Test getting the distance."""
|
||||
meters = location_util.distance(COORDINATES_PARIS[0],
|
||||
COORDINATES_PARIS[1],
|
||||
COORDINATES_NEW_YORK[0],
|
||||
COORDINATES_NEW_YORK[1])
|
||||
self.assertAlmostEqual(meters / 1000, DISTANCE_KM, places=2)
|
||||
|
||||
assert meters/1000 - DISTANCE_KM < 0.01
|
||||
|
||||
def test_get_kilometers(self):
|
||||
"""Test getting the distance between given coordinates in km."""
|
||||
kilometers = location_util.vincenty(COORDINATES_PARIS,
|
||||
COORDINATES_NEW_YORK)
|
||||
self.assertEqual(round(kilometers, 2), DISTANCE_KM)
|
||||
assert round(kilometers, 2) == DISTANCE_KM
|
||||
|
||||
def test_get_miles(self):
|
||||
"""Test getting the distance between given coordinates in miles."""
|
||||
miles = location_util.vincenty(COORDINATES_PARIS,
|
||||
COORDINATES_NEW_YORK,
|
||||
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(
|
||||
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:
|
||||
import pyhelloworld3
|
||||
|
|
Loading…
Reference in New Issue