Merge remote-tracking branch 'refs/remotes/home-assistant/dev' into Homematic_fix

pull/2399/head
Pascal Vizeli 2016-06-30 23:44:27 +02:00
commit d0b1619946
74 changed files with 2705 additions and 1219 deletions

View File

@ -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'))

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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."""

View File

@ -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

View File

@ -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!
"""

View File

@ -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', ''))

View File

@ -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'

View File

@ -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()

View File

@ -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()

View File

@ -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."""

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,65 @@
"""
Support for Vera locks.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/lock.vera/
"""
import logging
from homeassistant.components.lock import LockDevice
from homeassistant.const import (
ATTR_BATTERY_LEVEL, STATE_LOCKED, STATE_UNLOCKED)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Find and return Vera locks."""
add_devices_callback(
VeraLock(device, VERA_CONTROLLER) for
device in VERA_DEVICES['lock'])
class VeraLock(VeraDevice, LockDevice):
"""Representation of a Vera lock."""
def __init__(self, vera_device, controller):
"""Initialize the Vera device."""
self._state = None
VeraDevice.__init__(self, vera_device, controller)
@property
def device_state_attributes(self):
"""Return the state attributes of the device."""
attr = {}
if self.vera_device.has_battery:
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
attr['Vera Device Id'] = self.vera_device.vera_device_id
return attr
def lock(self, **kwargs):
"""Lock the device."""
self.vera_device.lock()
self._state = STATE_LOCKED
self.update_ha_state()
def unlock(self, **kwargs):
"""Unlock the device."""
self.vera_device.unlock()
self._state = STATE_UNLOCKED
self.update_ha_state()
@property
def is_locked(self):
"""Return true if device is on."""
return self._state == STATE_LOCKED
def update(self):
"""Called by the Vera device callback to update state."""
self._state = (STATE_LOCKED if self.vera_device.is_locked(True)
else STATE_UNLOCKED)

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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 | \

View File

@ -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):

View File

@ -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'

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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':

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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."""

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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):

View File

@ -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'),
}

View File

@ -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

View File

@ -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

View File

@ -17,7 +17,6 @@ REQUIRES = [
'pip>=7.0.0',
'jinja2>=2.8',
'voluptuous==0.8.9',
'eventlet==0.19.0',
]
setup(

View File

@ -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

View File

@ -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 = {

View File

@ -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

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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:

13
tests/fixtures/freegeoip.io.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"ip": "1.2.3.4",
"country_code": "US",
"country_name": "United States",
"region_code": "CA",
"region_name": "California",
"city": "San Diego",
"zip_code": "92122",
"time_zone": "America\/Los_Angeles",
"latitude": 32.8594,
"longitude": -117.2073,
"metro_code": 825
}

View File

@ -0,0 +1,13 @@
{
"results" : [
{
"elevation" : 101.5,
"location" : {
"lat" : 32.54321,
"lng" : -117.12345
},
"resolution" : 4.8
}
],
"status" : "OK"
}

16
tests/fixtures/ip-api.com.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
"as": "AS20001 Time Warner Cable Internet LLC",
"city": "San Diego",
"country": "United States",
"countryCode": "US",
"isp": "Time Warner Cable",
"lat": 32.8594,
"lon": -117.2073,
"org": "Time Warner Cable",
"query": "1.2.3.4",
"region": "CA",
"regionName": "California",
"status": "success",
"timezone": "America\/Los_Angeles",
"zip": "92122"
}

1184
tests/fixtures/yr.no.json vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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."""

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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