Merge pull request #2381 from home-assistant/dev

0.23
This commit is contained in:
Paulus Schoutsen 2016-07-01 00:58:11 -07:00 committed by GitHub
commit 6bc504bfcc
131 changed files with 6169 additions and 1181 deletions

View File

@ -20,6 +20,9 @@ omit =
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
homeassistant/components/envisalink.py
homeassistant/components/*/envisalink.py
homeassistant/components/insteon_hub.py
homeassistant/components/*/insteon_hub.py
@ -81,6 +84,9 @@ omit =
homeassistant/components/netatmo.py
homeassistant/components/*/netatmo.py
homeassistant/components/homematic.py
homeassistant/components/*/homematic.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/binary_sensor/arest.py
@ -111,6 +117,8 @@ omit =
homeassistant/components/downloader.py
homeassistant/components/feedreader.py
homeassistant/components/garage_door/wink.py
homeassistant/components/garage_door/rpi_gpio.py
homeassistant/components/hdmi_cec.py
homeassistant/components/ifttt.py
homeassistant/components/keyboard.py
homeassistant/components/light/blinksticklight.py
@ -120,7 +128,9 @@ omit =
homeassistant/components/light/limitlessled.py
homeassistant/components/light/osramlightify.py
homeassistant/components/lirc.py
homeassistant/components/media_player/braviatv.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/cmus.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/gpmdp.py
@ -170,6 +180,7 @@ omit =
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
@ -180,6 +191,7 @@ omit =
homeassistant/components/sensor/nzbget.py
homeassistant/components/sensor/onewire.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/openexchangerates.py
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/sabnzbd.py

View File

@ -22,6 +22,9 @@ http:
# Set to 1 to enable development mode
# development: 1
frontend:
# enable the frontend
light:
# platform: hue

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
@ -11,22 +10,16 @@ from threading import RLock
import voluptuous as vol
import homeassistant.components as core_components
import homeassistant.components.group as group
import homeassistant.config as config_util
from homeassistant.components import group, persistent_notification
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)
from homeassistant.helpers.entity import Entity
_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)
@ -262,9 +249,10 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
if not core_components.setup(hass, config):
_LOGGER.error('Home Assistant core failed to initialize. '
'Further initialization aborted.')
return hass
persistent_notification.setup(hass, config)
_LOGGER.info('Home Assistant core initialized')
# Give event decorators access to HASS
@ -291,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
@ -355,101 +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))
for entity_id, attrs in config.get(CONF_CUSTOMIZE).items():
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
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

@ -19,6 +19,8 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config'
def is_on(hass, entity_id=None):
"""Load up the module to call the is_on method.
@ -73,6 +75,11 @@ def toggle(hass, entity_id=None, **service_data):
hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
def reload_core_config(hass):
"""Reload the core config."""
hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
def setup(hass, config):
"""Setup general services related to Home Assistant."""
def handle_turn_service(service):
@ -111,4 +118,21 @@ def setup(hass, config):
hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)
hass.services.register(ha.DOMAIN, SERVICE_TOGGLE, handle_turn_service)
def handle_reload_config(call):
"""Service handler for reloading core config."""
from homeassistant.exceptions import HomeAssistantError
from homeassistant import config as conf_util
try:
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
conf_util.process_ha_core_config(hass, conf.get(ha.DOMAIN) or {})
hass.services.register(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG,
handle_reload_config)
return True

View File

@ -0,0 +1,105 @@
"""
Support for Envisalink-based alarm control panels (Honeywell/DSC).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.envisalink/
"""
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.envisalink import (EVL_CONTROLLER,
EnvisalinkDevice,
PARTITION_SCHEMA,
CONF_CODE,
CONF_PARTITIONNAME,
SIGNAL_PARTITION_UPDATE,
SIGNAL_KEYPAD_UPDATE)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN, STATE_ALARM_TRIGGERED)
DEPENDENCIES = ['envisalink']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Perform the setup for Envisalink alarm panels."""
_configured_partitions = discovery_info['partitions']
_code = discovery_info[CONF_CODE]
for part_num in _configured_partitions:
_device_config_data = PARTITION_SCHEMA(
_configured_partitions[part_num])
_device = EnvisalinkAlarm(
part_num,
_device_config_data[CONF_PARTITIONNAME],
_code,
EVL_CONTROLLER.alarm_state['partition'][part_num],
EVL_CONTROLLER)
add_devices_callback([_device])
return True
class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
"""Represents the Envisalink-based alarm panel."""
# pylint: disable=too-many-arguments
def __init__(self, partition_number, alarm_name, code, info, controller):
"""Initialize the alarm panel."""
from pydispatch import dispatcher
self._partition_number = partition_number
self._code = code
_LOGGER.debug('Setting up alarm: ' + alarm_name)
EnvisalinkDevice.__init__(self, alarm_name, info, controller)
dispatcher.connect(self._update_callback,
signal=SIGNAL_PARTITION_UPDATE,
sender=dispatcher.Any)
dispatcher.connect(self._update_callback,
signal=SIGNAL_KEYPAD_UPDATE,
sender=dispatcher.Any)
def _update_callback(self, partition):
"""Update HA state, if needed."""
if partition is None or int(partition) == self._partition_number:
self.update_ha_state()
@property
def code_format(self):
"""The characters if code is defined."""
return self._code
@property
def state(self):
"""Return the state of the device."""
if self._info['status']['alarm']:
return STATE_ALARM_TRIGGERED
elif self._info['status']['armed_away']:
return STATE_ALARM_ARMED_AWAY
elif self._info['status']['armed_stay']:
return STATE_ALARM_ARMED_HOME
elif self._info['status']['alpha']:
return STATE_ALARM_DISARMED
else:
return STATE_UNKNOWN
def alarm_disarm(self, code=None):
"""Send disarm command."""
if self._code:
EVL_CONTROLLER.disarm_partition(str(code),
self._partition_number)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
if self._code:
EVL_CONTROLLER.arm_stay_partition(str(code),
self._partition_number)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
if self._code:
EVL_CONTROLLER.arm_away_partition(str(code),
self._partition_number)
def alarm_trigger(self, code=None):
"""Alarm trigger command. Not possible for us."""
raise NotImplementedError()

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')
@ -204,11 +189,12 @@ class APIEntityStateView(HomeAssistantView):
return self.json_message('No state specified', HTTP_BAD_REQUEST)
attributes = request.json.get('attributes')
force_update = request.json.get('force_update', False)
is_new_state = self.hass.states.get(entity_id) is None
# Write state
self.hass.states.set(entity_id, new_state, attributes)
self.hass.states.set(entity_id, new_state, attributes, force_update)
# Read the state back for our response
resp = self.json(self.hass.states.get(entity_id))

View File

@ -0,0 +1,71 @@
"""
Support for Envisalink zone states- represented as binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.envisalink/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.envisalink import (EVL_CONTROLLER,
ZONE_SCHEMA,
CONF_ZONENAME,
CONF_ZONETYPE,
EnvisalinkDevice,
SIGNAL_ZONE_UPDATE)
from homeassistant.const import ATTR_LAST_TRIP_TIME
DEPENDENCIES = ['envisalink']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Perform the setup for Envisalink sensor devices."""
_configured_zones = discovery_info['zones']
for zone_num in _configured_zones:
_device_config_data = ZONE_SCHEMA(_configured_zones[zone_num])
_device = EnvisalinkBinarySensor(
zone_num,
_device_config_data[CONF_ZONENAME],
_device_config_data[CONF_ZONETYPE],
EVL_CONTROLLER.alarm_state['zone'][zone_num],
EVL_CONTROLLER)
add_devices_callback([_device])
class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
"""Representation of an envisalink Binary Sensor."""
# pylint: disable=too-many-arguments
def __init__(self, zone_number, zone_name, zone_type, info, controller):
"""Initialize the binary_sensor."""
from pydispatch import dispatcher
self._zone_type = zone_type
self._zone_number = zone_number
_LOGGER.debug('Setting up zone: ' + zone_name)
EnvisalinkDevice.__init__(self, zone_name, info, controller)
dispatcher.connect(self._update_callback,
signal=SIGNAL_ZONE_UPDATE,
sender=dispatcher.Any)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attr = {}
attr[ATTR_LAST_TRIP_TIME] = self._info['last_fault']
return attr
@property
def is_on(self):
"""Return true if sensor is on."""
return self._info['status']['open']
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return self._zone_type
def _update_callback(self, zone):
"""Update the zone's state, if needed."""
if zone is None or int(zone) == self._zone_number:
self.update_ha_state()

View File

@ -0,0 +1,100 @@
"""
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/
"""
import logging
from homeassistant.const import STATE_UNKNOWN
from homeassistant.components.binary_sensor import BinarySensorDevice
import homeassistant.components.homematic as homematic
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['homematic']
SENSOR_TYPES_CLASS = {
"Remote": None,
"ShutterContact": "opening",
"Smoke": "smoke",
"SmokeV2": "smoke",
"Motion": "motion",
"MotionV2": "motion",
"RemoteMotion": None
}
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""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):
"""Representation of a binary Homematic device."""
@property
def is_on(self):
"""Return true if switch is on."""
if not self.available:
return False
return bool(self._hm_get_state())
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
if not self.available:
return None
# If state is MOTION (RemoteMotion works only)
if self._state == "MOTION":
return "motion"
return SENSOR_TYPES_CLASS.get(self._hmdevice.__class__.__name__, None)
def _check_hm_to_ha_object(self):
"""Check if possible to use the HM Object as this HA type."""
from pyhomematic.devicetypes.sensors import HMBinarySensor\
as pyHMBinarySensor
# Check compatibility from HMDevice
if not super()._check_hm_to_ha_object():
return False
# 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)
return False
# if exists user value?
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 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 the Homematic metadata."""
super()._init_data_struct()
# object have 1 binary
if self._state is None and len(self._hmdevice.BINARYNODE) == 1:
for value in self._hmdevice.BINARYNODE:
self._state = value
# 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})

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

@ -37,6 +37,7 @@ def setup(hass, config):
"""Setup a demo environment."""
group = loader.get_component('group')
configurator = loader.get_component('configurator')
persistent_notification = loader.get_component('persistent_notification')
config.setdefault(ha.DOMAIN, {})
config.setdefault(DOMAIN, {})
@ -59,6 +60,11 @@ def setup(hass, config):
demo_config[component] = {CONF_PLATFORM: 'demo'}
bootstrap.setup_component(hass, component, demo_config)
# Setup example persistent notification
persistent_notification.create(
hass, 'This is an example of a persistent notification.',
title='Example Notification')
# Setup room groups
lights = sorted(hass.states.entity_ids('light'))
switches = sorted(hass.states.entity_ids('switch'))

View File

@ -83,8 +83,8 @@ class AsusWrtDeviceScanner(object):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.username = str(config[CONF_USERNAME])
self.password = str(config.get(CONF_PASSWORD))
self.pub_key = str(config.get('pub_key'))
self.password = str(config.get(CONF_PASSWORD, ""))
self.pub_key = str(config.get('pub_key', ""))
self.protocol = config.get('protocol')
self.mode = config.get('mode')

View File

@ -0,0 +1,210 @@
"""
Support for Envisalink devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/envisalink/
"""
import logging
import time
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.entity import Entity
from homeassistant.components.discovery import load_platform
REQUIREMENTS = ['pyenvisalink==1.0', 'pydispatcher==2.0.5']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'envisalink'
EVL_CONTROLLER = None
CONF_EVL_HOST = 'host'
CONF_EVL_PORT = 'port'
CONF_PANEL_TYPE = 'panel_type'
CONF_EVL_VERSION = 'evl_version'
CONF_CODE = 'code'
CONF_USERNAME = 'user_name'
CONF_PASS = 'password'
CONF_EVL_KEEPALIVE = 'keepalive_interval'
CONF_ZONEDUMP_INTERVAL = 'zonedump_interval'
CONF_ZONES = 'zones'
CONF_PARTITIONS = 'partitions'
CONF_ZONENAME = 'name'
CONF_ZONETYPE = 'type'
CONF_PARTITIONNAME = 'name'
DEFAULT_PORT = 4025
DEFAULT_EVL_VERSION = 3
DEFAULT_KEEPALIVE = 60
DEFAULT_ZONEDUMP_INTERVAL = 30
DEFAULT_ZONETYPE = 'opening'
SIGNAL_ZONE_UPDATE = 'zones_updated'
SIGNAL_PARTITION_UPDATE = 'partition_updated'
SIGNAL_KEYPAD_UPDATE = 'keypad_updated'
ZONE_SCHEMA = vol.Schema({
vol.Required(CONF_ZONENAME): cv.string,
vol.Optional(CONF_ZONETYPE, default=DEFAULT_ZONETYPE): cv.string})
PARTITION_SCHEMA = vol.Schema({
vol.Required(CONF_PARTITIONNAME): cv.string})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_EVL_HOST): cv.string,
vol.Required(CONF_PANEL_TYPE):
vol.All(cv.string, vol.In(['HONEYWELL', 'DSC'])),
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASS): cv.string,
vol.Required(CONF_CODE): cv.string,
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA},
vol.Optional(CONF_EVL_PORT, default=DEFAULT_PORT):
vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)),
vol.Optional(CONF_EVL_VERSION, default=DEFAULT_EVL_VERSION):
vol.All(vol.Coerce(int), vol.Range(min=3, max=4)),
vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE):
vol.All(vol.Coerce(int), vol.Range(min=15)),
vol.Optional(CONF_ZONEDUMP_INTERVAL,
default=DEFAULT_ZONEDUMP_INTERVAL):
vol.All(vol.Coerce(int), vol.Range(min=15)),
}),
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument, too-many-function-args, too-many-locals
# pylint: disable=too-many-return-statements
def setup(hass, base_config):
"""Common setup for Envisalink devices."""
from pyenvisalink import EnvisalinkAlarmPanel
from pydispatch import dispatcher
global EVL_CONTROLLER
config = base_config.get(DOMAIN)
_host = config.get(CONF_EVL_HOST)
_port = config.get(CONF_EVL_PORT)
_code = config.get(CONF_CODE)
_panel_type = config.get(CONF_PANEL_TYPE)
_version = config.get(CONF_EVL_VERSION)
_user = config.get(CONF_USERNAME)
_pass = config.get(CONF_PASS)
_keep_alive = config.get(CONF_EVL_KEEPALIVE)
_zone_dump = config.get(CONF_ZONEDUMP_INTERVAL)
_zones = config.get(CONF_ZONES)
_partitions = config.get(CONF_PARTITIONS)
_connect_status = {}
EVL_CONTROLLER = EnvisalinkAlarmPanel(_host,
_port,
_panel_type,
_version,
_user,
_pass,
_zone_dump,
_keep_alive)
def login_fail_callback(data):
"""Callback for when the evl rejects our login."""
_LOGGER.error("The envisalink rejected your credentials.")
_connect_status['fail'] = 1
def connection_fail_callback(data):
"""Network failure callback."""
_LOGGER.error("Could not establish a connection with the envisalink.")
_connect_status['fail'] = 1
def connection_success_callback(data):
"""Callback for a successful connection."""
_LOGGER.info("Established a connection with the envisalink.")
_connect_status['success'] = 1
def zones_updated_callback(data):
"""Handle zone timer updates."""
_LOGGER.info("Envisalink sent a zone update event. Updating zones...")
dispatcher.send(signal=SIGNAL_ZONE_UPDATE,
sender=None,
zone=data)
def alarm_data_updated_callback(data):
"""Handle non-alarm based info updates."""
_LOGGER.info("Envisalink sent new alarm info. Updating alarms...")
dispatcher.send(signal=SIGNAL_KEYPAD_UPDATE,
sender=None,
partition=data)
def partition_updated_callback(data):
"""Handle partition changes thrown by evl (including alarms)."""
_LOGGER.info("The envisalink sent a partition update event.")
dispatcher.send(signal=SIGNAL_PARTITION_UPDATE,
sender=None,
partition=data)
def stop_envisalink(event):
"""Shutdown envisalink connection and thread on exit."""
_LOGGER.info("Shutting down envisalink.")
EVL_CONTROLLER.stop()
def start_envisalink(event):
"""Startup process for the envisalink."""
EVL_CONTROLLER.start()
for _ in range(10):
if 'success' in _connect_status:
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink)
return True
elif 'fail' in _connect_status:
return False
else:
time.sleep(1)
_LOGGER.error("Timeout occurred while establishing evl connection.")
return False
EVL_CONTROLLER.callback_zone_timer_dump = zones_updated_callback
EVL_CONTROLLER.callback_zone_state_change = zones_updated_callback
EVL_CONTROLLER.callback_partition_state_change = partition_updated_callback
EVL_CONTROLLER.callback_keypad_update = alarm_data_updated_callback
EVL_CONTROLLER.callback_login_failure = login_fail_callback
EVL_CONTROLLER.callback_login_timeout = connection_fail_callback
EVL_CONTROLLER.callback_login_success = connection_success_callback
_result = start_envisalink(None)
if not _result:
return False
# Load sub-components for envisalink
if _partitions:
load_platform(hass, 'alarm_control_panel', 'envisalink',
{'partitions': _partitions,
'code': _code}, config)
load_platform(hass, 'sensor', 'envisalink',
{'partitions': _partitions,
'code': _code}, config)
if _zones:
load_platform(hass, 'binary_sensor', 'envisalink',
{'zones': _zones}, config)
return True
class EnvisalinkDevice(Entity):
"""Representation of an envisalink devicetity."""
def __init__(self, name, info, controller):
"""Initialize the device."""
self._controller = controller
self._info = info
self._name = name
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def should_poll(self):
"""No polling needed."""
return False

View File

@ -1,3 +1,3 @@
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
CORE = "7962327e4a29e51d4a6f4ee6cca9acc3"
UI = "570e1b8744a58024fc4e256f5e024424"
CORE = "db0bb387f4d3bcace002d62b94baa348"
UI = "5b306b7e7d36799b7b67f592cbe94703"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 168706fdb192219d8074d6709c0ce686180d1c8a
Subproject commit 1e1a3a1c845713508d21d7c1cb87a7ecee6222aa

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -29,7 +29,7 @@
/* eslint-disable quotes, comma-spacing */
var PrecacheConfig = [["/","595e12c9755af231fd80191e4cc74d2e"],["/devEvent","595e12c9755af231fd80191e4cc74d2e"],["/devInfo","595e12c9755af231fd80191e4cc74d2e"],["/devService","595e12c9755af231fd80191e4cc74d2e"],["/devState","595e12c9755af231fd80191e4cc74d2e"],["/devTemplate","595e12c9755af231fd80191e4cc74d2e"],["/history","595e12c9755af231fd80191e4cc74d2e"],["/logbook","595e12c9755af231fd80191e4cc74d2e"],["/map","595e12c9755af231fd80191e4cc74d2e"],["/states","595e12c9755af231fd80191e4cc74d2e"],["/static/core-7962327e4a29e51d4a6f4ee6cca9acc3.js","9c07ffb3f81cfb74f8a051b80cc8f9f0"],["/static/frontend-570e1b8744a58024fc4e256f5e024424.html","595e12c9755af231fd80191e4cc74d2e"],["/static/mdi-9ee3d4466a65bef35c2c8974e91b37c0.html","9a6846935116cd29279c91e0ee0a26d0"],["static/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","b0f32ad3c7749c40d486603f31c9d8b1"]];
var PrecacheConfig = [["/","70eeeca780a5f23c7632c2876dd1795a"],["/devEvent","70eeeca780a5f23c7632c2876dd1795a"],["/devInfo","70eeeca780a5f23c7632c2876dd1795a"],["/devService","70eeeca780a5f23c7632c2876dd1795a"],["/devState","70eeeca780a5f23c7632c2876dd1795a"],["/devTemplate","70eeeca780a5f23c7632c2876dd1795a"],["/history","70eeeca780a5f23c7632c2876dd1795a"],["/logbook","70eeeca780a5f23c7632c2876dd1795a"],["/map","70eeeca780a5f23c7632c2876dd1795a"],["/states","70eeeca780a5f23c7632c2876dd1795a"],["/static/core-db0bb387f4d3bcace002d62b94baa348.js","f938163a392465dc87af3a0094376621"],["/static/frontend-5b306b7e7d36799b7b67f592cbe94703.html","70eeeca780a5f23c7632c2876dd1795a"],["/static/mdi-9ee3d4466a65bef35c2c8974e91b37c0.html","9a6846935116cd29279c91e0ee0a26d0"],["static/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","b0f32ad3c7749c40d486603f31c9d8b1"]];
/* eslint-enable quotes, comma-spacing */
var CacheNamePrefix = 'sw-precache-v1--' + (self.registration ? self.registration.scope : '') + '-';

View File

@ -0,0 +1,96 @@
"""
Support for building a Raspberry Pi garage controller in HA.
Instructions for building the controller can be found here
https://github.com/andrewshilliday/garage-door-controller
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/garage_door.rpi_gpio/
"""
import logging
from time import sleep
import voluptuous as vol
from homeassistant.components.garage_door import GarageDoorDevice
import homeassistant.components.rpi_gpio as rpi_gpio
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['rpi_gpio']
_LOGGER = logging.getLogger(__name__)
_DOORS_SCHEMA = vol.All(
cv.ensure_list,
[
vol.Schema({
'name': str,
'relay_pin': int,
'state_pin': int,
})
]
)
PLATFORM_SCHEMA = vol.Schema({
'platform': str,
vol.Required('doors'): _DOORS_SCHEMA,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the garage door platform."""
doors = []
doors_conf = config.get('doors')
for door in doors_conf:
doors.append(RPiGPIOGarageDoor(door['name'], door['relay_pin'],
door['state_pin']))
add_devices(doors)
class RPiGPIOGarageDoor(GarageDoorDevice):
"""Representation of a Raspberry garage door."""
def __init__(self, name, relay_pin, state_pin):
"""Initialize the garage door."""
self._name = name
self._state = False
self._relay_pin = relay_pin
self._state_pin = state_pin
rpi_gpio.setup_output(self._relay_pin)
rpi_gpio.setup_input(self._state_pin, 'DOWN')
rpi_gpio.write_output(self._relay_pin, True)
@property
def unique_id(self):
"""Return the ID of this garage door."""
return "{}.{}".format(self.__class__, self._name)
@property
def name(self):
"""Return the name of the garage door if any."""
return self._name
def update(self):
"""Update the state of the garage door."""
self._state = rpi_gpio.read_input(self._state_pin) is True
@property
def is_closed(self):
"""Return true if door is closed."""
return self._state
def _trigger(self):
"""Trigger the door."""
rpi_gpio.write_output(self._relay_pin, False)
sleep(0.2)
rpi_gpio.write_output(self._relay_pin, True)
def close_door(self):
"""Close the door."""
if not self.is_closed:
self._trigger()
def open_door(self):
"""Open the door."""
if self.is_closed:
self._trigger()

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

@ -0,0 +1,70 @@
"""
Support for Zwave garage door components.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/garagedoor.zwave/
"""
# Because we do not compile openzwave on CI
# pylint: disable=import-error
import logging
from homeassistant.components.garage_door import DOMAIN
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.garage_door import GarageDoorDevice
COMMAND_CLASS_SWITCH_BINARY = 0x25 # 37
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and return Z-Wave garage door device."""
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
if value.command_class != zwave.COMMAND_CLASS_SWITCH_BINARY:
return
if value.type != zwave.TYPE_BOOL:
return
if value.genre != zwave.GENRE_USER:
return
value.set_change_verified(False)
add_devices([ZwaveGarageDoor(value)])
class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, GarageDoorDevice):
"""Representation of an Zwave garage door device."""
def __init__(self, value):
"""Initialize the zwave garage door."""
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._node = value.node
self._state = value.data
dispatcher.connect(
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
def value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id:
self._state = value.data
self.update_ha_state(True)
_LOGGER.debug("Value changed on network %s", value)
@property
def is_closed(self):
"""Return the current position of Zwave garage door."""
return not self._state
def close_door(self):
"""Close the garage door."""
self._value.node.set_switch(self._value.value_id, False)
def open_door(self):
"""Open the garage door."""
self._value.node.set_switch(self._value.value_id, True)

View File

@ -0,0 +1,122 @@
"""
CEC component.
Requires libcec + Python bindings.
"""
import logging
import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_START
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
_CEC = None
DOMAIN = 'hdmi_cec'
SERVICE_SELECT_DEVICE = 'select_device'
SERVICE_POWER_ON = 'power_on'
SERVICE_STANDBY = 'standby'
CONF_DEVICES = 'devices'
ATTR_DEVICE = 'device'
MAX_DEPTH = 4
# pylint: disable=unnecessary-lambda
DEVICE_SCHEMA = vol.Schema({
vol.All(cv.positive_int): vol.Any(lambda devices: DEVICE_SCHEMA(devices),
cv.string)
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_DEVICES): DEVICE_SCHEMA
})
}, extra=vol.ALLOW_EXTRA)
def parse_mapping(mapping, parents=None):
"""Parse configuration device mapping."""
if parents is None:
parents = []
for addr, val in mapping.items():
cur = parents + [str(addr)]
if isinstance(val, dict):
yield from parse_mapping(val, cur)
elif isinstance(val, str):
yield (val, cur)
def pad_physical_address(addr):
"""Right-pad a physical address."""
return addr + ['0'] * (MAX_DEPTH - len(addr))
def setup(hass, config):
"""Setup CEC capability."""
global _CEC
# cec is only available if libcec is properly installed
# and the Python bindings are accessible.
try:
import cec
except ImportError:
_LOGGER.error("libcec must be installed")
return False
# Parse configuration into a dict of device name
# to physical address represented as a list of
# four elements.
flat = {}
for pair in parse_mapping(config[DOMAIN].get(CONF_DEVICES, {})):
flat[pair[0]] = pad_physical_address(pair[1])
# Configure libcec.
cfg = cec.libcec_configuration()
cfg.strDeviceName = 'HASS'
cfg.bActivateSource = 0
cfg.bMonitorOnly = 1
cfg.clientVersion = cec.LIBCEC_VERSION_CURRENT
# Set up CEC adapter.
_CEC = cec.ICECAdapter.Create(cfg)
def _power_on(call):
"""Power on all devices."""
_CEC.PowerOnDevices()
def _standby(call):
"""Standby all devices."""
_CEC.StandbyDevices()
def _select_device(call):
"""Select the active device."""
path = flat.get(call.data[ATTR_DEVICE])
if not path:
_LOGGER.error("Device not found: %s", call.data[ATTR_DEVICE])
cmds = []
for i in range(1, MAX_DEPTH - 1):
addr = pad_physical_address(path[:i])
cmds.append('1f:82:{}{}:{}{}'.format(*addr))
cmds.append('1f:86:{}{}:{}{}'.format(*addr))
for cmd in cmds:
_CEC.Transmit(_CEC.CommandFromString(cmd))
_LOGGER.info("Selected %s", call.data[ATTR_DEVICE])
def _start_cec(event):
"""Open CEC adapter."""
adapters = _CEC.DetectAdapters()
if len(adapters) == 0:
_LOGGER.error("No CEC adapter found")
return
if _CEC.Open(adapters[0].strComName):
hass.services.register(DOMAIN, SERVICE_POWER_ON, _power_on)
hass.services.register(DOMAIN, SERVICE_STANDBY, _standby)
hass.services.register(DOMAIN, SERVICE_SELECT_DEVICE,
_select_device)
else:
_LOGGER.error("Failed to open adapter")
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, _start_cec)
return True

View File

@ -0,0 +1,534 @@
"""
Support for Homematic devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/homematic/
"""
import time
import logging
from functools import partial
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
DOMAIN = 'homematic'
REQUIREMENTS = ['pyhomematic==0.1.8']
HOMEMATIC = None
HOMEMATIC_LINK_DELAY = 0.5
DISCOVER_SWITCHES = "homematic.switch"
DISCOVER_LIGHTS = "homematic.light"
DISCOVER_SENSORS = "homematic.sensor"
DISCOVER_BINARY_SENSORS = "homematic.binary_sensor"
DISCOVER_ROLLERSHUTTER = "homematic.rollershutter"
DISCOVER_THERMOSTATS = "homematic.thermostat"
ATTR_DISCOVER_DEVICES = "devices"
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", "WaterSensor"],
DISCOVER_THERMOSTATS: ["Thermostat", "ThermostatWall", "MAXThermostat"],
DISCOVER_BINARY_SENSORS: ["ShutterContact", "Smoke", "SmokeV2",
"Motion", "MotionV2", "RemoteMotion"],
DISCOVER_ROLLERSHUTTER: ["Blind"]
}
HM_IGNORE_DISCOVERY_NODE = [
"ACTUAL_TEMPERATURE"
]
HM_ATTRIBUTE_SUPPORT = {
"LOWBAT": ["Battery", {0: "High", 1: "Low"}],
"ERROR": ["Sabotage", {0: "No", 1: "Yes"}],
"RSSI_DEVICE": ["RSSI", {}],
"VALVE_STATE": ["Valve", {}],
"BATTERY_STATE": ["Battery", {}],
"CONTROL_MODE": ["Mode", {0: "Auto", 1: "Manual", 2: "Away", 3: "Boost"}],
"POWER": ["Power", {}],
"CURRENT": ["Current", {}],
"VOLTAGE": ["Voltage", {}]
}
HM_PRESS_EVENTS = [
"PRESS_SHORT",
"PRESS_LONG",
"PRESS_CONT",
"PRESS_LONG_RELEASE"
]
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup(hass, config):
"""Setup the Homematic component."""
global HOMEMATIC, HOMEMATIC_LINK_DELAY
from pyhomematic import HMConnection
local_ip = config[DOMAIN].get("local_ip", None)
local_port = config[DOMAIN].get("local_port", 8943)
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:
_LOGGER.error("Missing remote CCU/Homegear or local address")
return False
# Create server thread
bound_system_callback = partial(system_callback_handler, hass, config)
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
HOMEMATIC.start()
# Stops server when Homeassistant is shutting down
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, HOMEMATIC.stop)
hass.config.components.append(DOMAIN)
return True
# pylint: disable=too-many-branches
def system_callback_handler(hass, config, src, *args):
"""Callback handler."""
if src == 'newDevices':
_LOGGER.debug("newDevices with: %s", str(args))
# pylint: disable=unused-variable
(interface_id, dev_descriptions) = args
key_dict = {}
# Get list of all keys of the devices (ignoring channels)
for dev in dev_descriptions:
key_dict[dev['ADDRESS'].split(':')[0]] = True
# 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 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.
if key_dict:
for component_name, discovery_type in (
('switch', DISCOVER_SWITCHES),
('light', DISCOVER_LIGHTS),
('rollershutter', DISCOVER_ROLLERSHUTTER),
('binary_sensor', DISCOVER_BINARY_SENSORS),
('sensor', DISCOVER_SENSORS),
('thermostat', DISCOVER_THERMOSTATS)):
# Get all devices of a specific type
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
if found_devices:
# Fire discovery event
discovery.load_platform(hass, component_name, DOMAIN, {
ATTR_DISCOVER_DEVICES: found_devices
}, config)
def _get_devices(device_type, keys):
"""Get the Homematic devices."""
# run
device_arr = []
for key in keys:
device = HOMEMATIC.devices[key]
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)
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):
_LOGGER.debug("Handling %s:%i", key, channel)
if channel in params:
for param in params[channel]:
name = _create_ha_name(name=device.NAME,
channel=channel,
param=param)
device_dict = dict(platform="homematic",
address=key,
name=name,
channel=channel)
if param is not None:
device_dict[ATTR_PARAM] = param
# Add new device
device_arr.append(device_dict)
else:
_LOGGER.debug("Channel %i not in params", channel)
else:
_LOGGER.debug("Got no params for %s", key)
_LOGGER.debug("%s autodiscovery: %s",
device_type, str(device_arr))
return device_arr
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 = []
for node, meta_chan in metadata.items():
try:
# Is this attribute ignored?
if node in HM_IGNORE_DISCOVERY_NODE:
continue
if meta_chan == 'c' or meta_chan is None:
# Only channel linked data
param_chan.append(node)
elif channel == 1:
# First channel can have other data channel
param_chan.append(node)
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
if len(param_chan) > 0:
params.update({channel: param_chan})
_LOGGER.debug("Create param list for %s with: %s", hmdevice.ADDRESS,
str(params))
return params
def _create_ha_name(name, channel, param):
"""Generate a unique object name."""
# HMDevice is a simple device
if channel == 1 and param is None:
return name
# Has multiple elements/channels
if channel > 1 and param is None:
return "{} {}".format(name, channel)
# With multiple param first elements
if channel == 1 and param is not None:
return "{} {}".format(name, param)
# Multiple param on object with multiple elements
if channel > 1 and param is not None:
return "{} {} {}".format(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[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 _hm_event_handler(hass, device, caller, attribute, value):
"""Handle all pyhomematic device events."""
try:
channel = int(device.split(":")[1])
address = device.split(":")[0]
hmdevice = HOMEMATIC.devices.get(address)
except (TypeError, ValueError):
_LOGGER.error("Event handling channel convert error!")
return
# is not a event?
if attribute not in hmdevice.EVENTNODE:
return
_LOGGER.debug("Event %s for %s channel %i", attribute,
hmdevice.NAME, channel)
# 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
_LOGGER.warning("Event is unknown and not forwarded to HA")
class HMDevice(Entity):
"""The Homematic device base object."""
# pylint: disable=too-many-instance-attributes
def __init__(self, config):
"""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
self._available = False
# Set param to uppercase
if self._state:
self._state = self._state.upper()
# Generate name
if not self._name:
self._name = _create_ha_name(name=self._address,
channel=self._channel,
param=self._state)
@property
def should_poll(self):
"""Return false. Homematic states are pushed by the XML RPC Server."""
return False
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def assumed_state(self):
"""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 self._available
@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
if node in self._data:
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."""
# device is already linked
if self._connected:
return True
# Does a HMDevice from pyhomematic exist?
if self._address in HOMEMATIC.devices:
# Init
self._hmdevice = HOMEMATIC.devices[self._address]
self._connected = True
# 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:
# Init datapoints of this object
self._init_data_struct()
if HOMEMATIC_LINK_DELAY:
# We delay / pause loading of data to avoid overloading
# of CCU / Homegear when doing auto detection
time.sleep(HOMEMATIC_LINK_DELAY)
self._load_init_data_from_hm()
_LOGGER.debug("%s datastruct: %s",
self._name, str(self._data))
# Link events from pyhomatic
self._subscribe_homematic_events()
self._available = not self._hmdevice.UNREACH
# pylint: disable=broad-except
except Exception as err:
self._connected = False
_LOGGER.error("Exception while linking %s: %s",
self._address, str(err))
else:
_LOGGER.critical("Delink %s object from HM", self._name)
self._connected = False
# Update HA
_LOGGER.debug("%s linking done, send update_ha_state", self._name)
self.update_ha_state()
else:
_LOGGER.debug("%s not found in HOMEMATIC.devices", self._address)
def _hm_event_callback(self, device, caller, attribute, value):
"""Handle all pyhomematic device events."""
_LOGGER.debug("%s received event '%s' value: %s", self._name,
attribute, value)
have_change = False
# Is data needed for this instance?
if attribute in self._data:
# Did data change?
if self._data[attribute] != value:
self._data[attribute] = value
have_change = True
# If available it has changed
if attribute is "UNREACH":
self._available = bool(value)
have_change = True
# 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()
def _subscribe_homematic_events(self):
"""Subscribe all required events to handle job."""
channels_to_sub = {}
# Push data to channels_to_sub from hmdevice metadata
for metadata in (self._hmdevice.SENSORNODE, self._hmdevice.BINARYNODE,
self._hmdevice.ATTRIBUTENODE,
self._hmdevice.WRITENODE, self._hmdevice.EVENTNODE,
self._hmdevice.ACTIONNODE):
for node, channel in metadata.items():
# Data is needed for this instance
if node in self._data:
# chan is current channel
if channel == 'c' or channel is None:
channel = self._channel
# Prepare for subscription
try:
if int(channel) > 0:
channels_to_sub.update({int(channel): True})
except (ValueError, TypeError):
_LOGGER("Invalid channel in metadata from %s",
self._name)
# Set callbacks
for channel in channels_to_sub:
_LOGGER.debug("Subscribe channel %s from %s",
str(channel), self._name)
self._hmdevice.setEventCallback(callback=self._hm_event_callback,
bequeath=False,
channel=channel)
def _load_init_data_from_hm(self):
"""Load first value from pyhomematic."""
if not self._connected:
return False
# Read data from pyhomematic
for metadata, funct in (
(self._hmdevice.ATTRIBUTENODE,
self._hmdevice.getAttributeData),
(self._hmdevice.WRITENODE, self._hmdevice.getWriteData),
(self._hmdevice.SENSORNODE, self._hmdevice.getSensorData),
(self._hmdevice.BINARYNODE, self._hmdevice.getBinaryData)):
for node in metadata:
if node in self._data:
self._data[node] = funct(name=node, channel=self._channel)
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 Homematic object as this HA type.
NEEDS overwrite by inherit!
"""
if not self._connected or self._hmdevice is None:
_LOGGER.error("HA object is not linked to homematic.")
return False
# Check if button option is correctly set for this object
if self._channel > self._hmdevice.ELEMENT:
_LOGGER.critical("Button option is not correct for this object!")
return False
return True
def _init_data_struct(self):
"""Generate a data dict (self._data) from the Homematic metadata.
NEEDS overwrite by inherit!
"""
# Add all attributes to data dict
for data_note in self._hmdevice.ATTRIBUTENODE:
self._data.update({data_note: STATE_UNKNOWN})

View File

@ -1,25 +1,31 @@
"""This module provides WSGI application to serve the Home Assistant API."""
"""
This module provides WSGI application to serve the Home Assistant API.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/http/
"""
import hmac
import json
import logging
import mimetypes
import threading
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"
@ -31,6 +37,25 @@ CONF_CORS_ORIGINS = 'cors_allowed_origins'
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_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:" \
"DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:" \
"ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:" \
"ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:" \
"ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:" \
"ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:" \
"DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:" \
"DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:" \
"ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:" \
"AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:" \
"AES256-SHA:DES-CBC3-SHA:!DSS"
_FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
_LOGGER = logging.getLogger(__name__)
@ -93,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'
@ -216,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.
@ -283,14 +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)
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."""
@ -337,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', ''))
@ -395,7 +451,12 @@ class HomeAssistantView(object):
self.hass.wsgi.api_password):
authenticated = True
if self.requires_auth and not authenticated:
if authenticated:
_LOGGER.info('Successful login/request from %s',
request.remote_addr)
elif self.requires_auth and not authenticated:
_LOGGER.warning('Login attempt or request with an invalid'
'password from %s', request.remote_addr)
raise Unauthorized()
request.authenticated = authenticated
@ -437,7 +498,7 @@ class HomeAssistantView(object):
mimetype = mimetypes.guess_type(fil)[0]
try:
fil = open(fil)
fil = open(fil, mode='br')
except IOError:
raise NotFound()

View File

@ -425,39 +425,39 @@ class HvacDevice(Entity):
def set_temperature(self, temperature):
"""Set new target temperature."""
pass
raise NotImplementedError()
def set_humidity(self, humidity):
"""Set new target humidity."""
pass
raise NotImplementedError()
def set_fan_mode(self, fan):
"""Set new target fan mode."""
pass
raise NotImplementedError()
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
pass
raise NotImplementedError()
def set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
pass
raise NotImplementedError()
def turn_away_mode_on(self):
"""Turn away mode on."""
pass
raise NotImplementedError()
def turn_away_mode_off(self):
"""Turn away mode off."""
pass
raise NotImplementedError()
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
pass
raise NotImplementedError()
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
pass
raise NotImplementedError()
@property
def min_temp(self):

View File

@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
discovery_info, zwave.NETWORK)
# pylint: disable=too-many-arguments
# pylint: disable=too-many-arguments, abstract-method
class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
"""Represents a HeatControl hvac."""
@ -98,7 +98,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
def value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.node == value.node:
if self._value.value_id == value.value_id:
self.update_properties()
self.update_ha_state(True)
_LOGGER.debug("Value changed on network %s", value)
@ -211,6 +211,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
value.data = int(round(temperature, 0))
else:
value.data = int(temperature)
break
def set_fan_mode(self, fan):
"""Set new target fan mode."""
@ -218,6 +219,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
if value.command_class == 68 and value.index == 0:
value.data = bytes(fan, 'utf-8')
break
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
@ -225,6 +227,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == 64 and value.index == 0:
value.data = bytes(operation_mode, 'utf-8')
break
def set_swing_mode(self, swing_mode):
"""Set new target swing mode."""
@ -233,3 +236,4 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
class_id=COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
value.data = int(swing_mode)
break

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

@ -248,7 +248,8 @@ def setup(hass, config):
class Light(ToggleEntity):
"""Representation of a light."""
# pylint: disable=no-self-use
# pylint: disable=no-self-use, abstract-method
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""

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

@ -0,0 +1,102 @@
"""
Support for Homematic lighs.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.homematic/
"""
import logging
from homeassistant.components.light import (ATTR_BRIGHTNESS, Light)
from homeassistant.const import STATE_UNKNOWN
import homeassistant.components.homematic as homematic
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['homematic']
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""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):
"""Representation of a Homematic light."""
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
if not self.available:
return None
# Is dimmer?
if self._state is "LEVEL":
return int(self._hm_get_state() * 255)
else:
return None
@property
def is_on(self):
"""Return true if light is on."""
try:
return self._hm_get_state() > 0
except TypeError:
return False
def turn_on(self, **kwargs):
"""Turn the light on."""
if not self.available:
return
if ATTR_BRIGHTNESS in kwargs and self._state is "LEVEL":
percent_bright = float(kwargs[ATTR_BRIGHTNESS]) / 255
self._hmdevice.set_level(percent_bright, self._channel)
else:
self._hmdevice.on(self._channel)
def turn_off(self, **kwargs):
"""Turn the light off."""
if self.available:
self._hmdevice.off(self._channel)
def _check_hm_to_ha_object(self):
"""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
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)
return False
def _init_data_struct(self):
"""Generate a data dict (self._data) from the Homematic metadata."""
from pyhomematic.devicetypes.actors import Dimmer, Switch
super()._init_data_struct()
# Use STATE
if isinstance(self._hmdevice, Switch):
self._state = "STATE"
# Use LEVEL
if isinstance(self._hmdevice, Dimmer):
self._state = "LEVEL"
# Add state to data dict
if self._state:
_LOGGER.debug("%s init datadict with main node '%s'", self._name,
self._state)
self._data.update({self._state: STATE_UNKNOWN})
else:
_LOGGER.critical("Can't correctly init light %s.", self._name)

View File

@ -4,6 +4,7 @@ Support for LimitlessLED bulbs.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.limitlessled/
"""
# pylint: disable=abstract-method
import logging
from homeassistant.components.light import (

View File

@ -4,6 +4,7 @@ Support for MySensors lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.mysensors/
"""
# pylint: disable=abstract-method
import logging
from homeassistant.components import mysensors

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,13 +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):
@ -29,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):
@ -50,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
@ -60,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:
@ -67,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."""
@ -108,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

@ -0,0 +1,370 @@
"""
Support for interface with a Sony Bravia TV.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.braviatv/
"""
import logging
import os
import json
import re
from homeassistant.loader import get_component
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
SUPPORT_VOLUME_SET, SUPPORT_SELECT_SOURCE, MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON)
REQUIREMENTS = [
'https://github.com/aparraga/braviarc/archive/0.3.2.zip'
'#braviarc==0.3.2']
BRAVIA_CONFIG_FILE = 'bravia.conf'
CLIENTID_PREFIX = 'HomeAssistant'
NICKNAME = 'Home Assistant'
# Map ip to request id for configuring
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
SUPPORT_BRAVIA = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE
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)
pid_component = pid.communicate()[0]
mac = re.search(r"(([a-f\d]{1,2}\:){5}[a-f\d]{1,2})".encode('UTF-8'),
pid_component).groups()[0]
return mac
def _config_from_file(filename, config=None):
"""Create the configuration from a file."""
if config:
# We're writing configuration
bravia_config = _config_from_file(filename)
if bravia_config is None:
bravia_config = {}
new_config = bravia_config.copy()
new_config.update(config)
try:
with open(filename, 'w') as fdesc:
fdesc.write(json.dumps(new_config))
except IOError as error:
_LOGGER.error('Saving config file failed: %s', error)
return False
return True
else:
# We're reading config
if os.path.isfile(filename):
try:
with open(filename, 'r') as fdesc:
return json.loads(fdesc.read())
except ValueError as error:
return {}
except IOError as error:
_LOGGER.error('Reading config file failed: %s', error)
# This won't work yet
return False
else:
return {}
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the Sony Bravia TV platform."""
host = config.get(CONF_HOST)
if host is None:
return # if no host configured, do not continue
pin = None
bravia_config = _config_from_file(hass.config.path(BRAVIA_CONFIG_FILE))
while len(bravia_config):
# Setup a configured TV
host_ip, host_config = bravia_config.popitem()
if host_ip == host:
pin = host_config['pin']
mac = host_config['mac']
name = config.get(CONF_NAME)
add_devices_callback([BraviaTVDevice(host, mac, name, pin)])
return
setup_bravia(config, pin, hass, add_devices_callback)
# pylint: disable=too-many-branches
def setup_bravia(config, pin, hass, add_devices_callback):
"""Setup a Sony Bravia TV based on host parameter."""
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
if name is None:
name = "Sony Bravia TV"
if pin is None:
request_configuration(config, hass, add_devices_callback)
return
else:
mac = _get_mac_address(host)
if mac is not None:
mac = mac.decode('utf8')
# If we came here and configuring this host, mark as done
if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator')
configurator.request_done(request_id)
_LOGGER.info('Discovery configuration done!')
# Save config
if not _config_from_file(
hass.config.path(BRAVIA_CONFIG_FILE),
{host: {'pin': pin, 'host': host, 'mac': mac}}):
_LOGGER.error('failed to save config file')
add_devices_callback([BraviaTVDevice(host, mac, name, pin)])
def request_configuration(config, hass, add_devices_callback):
"""Request configuration steps from the user."""
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
if name is None:
name = "Sony Bravia"
configurator = get_component('configurator')
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING[host], "Failed to register, please try again.")
return
def bravia_configuration_callback(data):
"""Callback after user enter PIN."""
from braviarc import braviarc
pin = data.get('pin')
braviarc = braviarc.BraviaRC(host)
braviarc.connect(pin, CLIENTID_PREFIX, NICKNAME)
if braviarc.is_connected():
setup_bravia(config, pin, hass, add_devices_callback)
else:
request_configuration(config, hass, add_devices_callback)
_CONFIGURING[host] = configurator.request_config(
hass, name, bravia_configuration_callback,
description='Enter the Pin shown on your Sony Bravia TV.' +
'If no Pin is shown, enter 0000 to let TV show you a Pin.',
description_image="/static/images/smart-tv.png",
submit_caption="Confirm",
fields=[{'id': 'pin', 'name': 'Enter the pin', 'type': ''}]
)
# pylint: disable=abstract-method, too-many-public-methods,
# pylint: disable=too-many-instance-attributes, too-many-arguments
class BraviaTVDevice(MediaPlayerDevice):
"""Representation of a Sony Bravia TV."""
def __init__(self, host, mac, name, pin):
"""Initialize the Sony Bravia device."""
from braviarc import braviarc
self._pin = pin
self._braviarc = braviarc.BraviaRC(host, mac)
self._name = name
self._state = STATE_OFF
self._muted = False
self._program_name = None
self._channel_name = None
self._channel_number = None
self._source = None
self._source_list = []
self._original_content_list = []
self._content_mapping = {}
self._duration = None
self._content_uri = None
self._id = None
self._playing = False
self._start_date_time = None
self._program_media_type = None
self._min_volume = None
self._max_volume = None
self._volume = None
self._braviarc.connect(pin, CLIENTID_PREFIX, NICKNAME)
if self._braviarc.is_connected():
self.update()
else:
self._state = STATE_OFF
def update(self):
"""Update TV info."""
if not self._braviarc.is_connected():
self._braviarc.connect(self._pin, CLIENTID_PREFIX, NICKNAME)
if not self._braviarc.is_connected():
return
# Retrieve the latest data.
try:
if self._state == STATE_ON:
# refresh volume info:
self._refresh_volume()
self._refresh_channels()
playing_info = self._braviarc.get_playing_info()
if playing_info is None or len(playing_info) == 0:
self._state = STATE_OFF
else:
self._state = STATE_ON
self._program_name = playing_info.get('programTitle')
self._channel_name = playing_info.get('title')
self._program_media_type = playing_info.get(
'programMediaType')
self._channel_number = playing_info.get('dispNum')
self._source = playing_info.get('source')
self._content_uri = playing_info.get('uri')
self._duration = playing_info.get('durationSec')
self._start_date_time = playing_info.get('startDateTime')
except Exception as exception_instance: # pylint: disable=broad-except
_LOGGER.error(exception_instance)
self._state = STATE_OFF
def _refresh_volume(self):
"""Refresh volume information."""
volume_info = self._braviarc.get_volume_info()
if volume_info is not None:
self._volume = volume_info.get('volume')
self._min_volume = volume_info.get('minVolume')
self._max_volume = volume_info.get('maxVolume')
self._muted = volume_info.get('mute')
def _refresh_channels(self):
if len(self._source_list) == 0:
self._content_mapping = self._braviarc. \
load_source_list()
self._source_list = []
for key in self._content_mapping:
self._source_list.append(key)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def source(self):
"""Return the current input source."""
return self._source
@property
def source_list(self):
"""List of available input sources."""
return self._source_list
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
if self._volume is not None:
return self._volume / 100
else:
return None
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
return self._muted
@property
def supported_media_commands(self):
"""Flag of media commands that are supported."""
return SUPPORT_BRAVIA
@property
def media_title(self):
"""Title of current playing media."""
return_value = None
if self._channel_name is not None:
return_value = self._channel_name
if self._program_name is not None:
return_value = return_value + ': ' + self._program_name
return return_value
@property
def media_content_id(self):
"""Content ID of current playing media."""
return self._channel_name
@property
def media_duration(self):
"""Duration of current playing media in seconds."""
return self._duration
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
self._braviarc.set_volume_level(volume)
def turn_on(self):
"""Turn the media player on."""
self._braviarc.turn_on()
def turn_off(self):
"""Turn off media player."""
self._braviarc.turn_off()
def volume_up(self):
"""Volume up the media player."""
self._braviarc.volume_up()
def volume_down(self):
"""Volume down media player."""
self._braviarc.volume_down()
def mute_volume(self, mute):
"""Send mute command."""
self._braviarc.mute_volume(mute)
def select_source(self, source):
"""Set the input source."""
if source in self._content_mapping:
uri = self._content_mapping[source]
self._braviarc.play_content(uri)
def media_play_pause(self):
"""Simulate play pause media player."""
if self._playing:
self.media_pause()
else:
self.media_play()
def media_play(self):
"""Send play command."""
self._playing = True
self._braviarc.media_play()
def media_pause(self):
"""Send media pause command to media player."""
self._playing = False
self._braviarc.media_pause()
def media_next_track(self):
"""Send next track command."""
self._braviarc.media_next_track()
def media_previous_track(self):
"""Send the previous track command."""
self._braviarc.media_previous_track()

View File

@ -0,0 +1,213 @@
"""
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.cmus/
"""
import logging
from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
SUPPORT_VOLUME_SET, SUPPORT_PLAY_MEDIA, SUPPORT_SEEK,
MediaPlayerDevice)
from homeassistant.const import (STATE_OFF, STATE_PAUSED, STATE_PLAYING,
CONF_HOST, CONF_NAME, CONF_PASSWORD,
CONF_PORT)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pycmus==0.1.0']
SUPPORT_CMUS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_PLAY_MEDIA | SUPPORT_SEEK
def setup_platform(hass, config, add_devices, discover_info=None):
"""Setup the CMUS platform."""
from pycmus import exceptions
host = config.get(CONF_HOST, None)
password = config.get(CONF_PASSWORD, None)
port = config.get(CONF_PORT, None)
name = config.get(CONF_NAME, None)
if host and not password:
_LOGGER.error("A password must be set if using a remote cmus server")
return False
try:
cmus_remote = CmusDevice(host, password, port, name)
except exceptions.InvalidPassword:
_LOGGER.error("The provided password was rejected by cmus")
return False
add_devices([cmus_remote])
class CmusDevice(MediaPlayerDevice):
"""Representation of a running CMUS."""
# pylint: disable=no-member, too-many-public-methods, abstract-method
def __init__(self, server, password, port, name):
"""Initialize the CMUS device."""
from pycmus import remote
if server:
port = port or 3000
self.cmus = remote.PyCmus(server=server, password=password,
port=port)
auto_name = "cmus-%s" % server
else:
self.cmus = remote.PyCmus()
auto_name = "cmus-local"
self._name = name or auto_name
self.status = {}
self.update()
def update(self):
"""Get the latest data and update the state."""
status = self.cmus.get_status_dict()
if not status:
_LOGGER.warning("Recieved no status from cmus")
else:
self.status = status
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the media state."""
if 'status' not in self.status:
self.update()
if self.status['status'] == 'playing':
return STATE_PLAYING
elif self.status['status'] == 'paused':
return STATE_PAUSED
else:
return STATE_OFF
@property
def media_content_id(self):
"""Content ID of current playing media."""
return self.status.get('file')
@property
def content_type(self):
"""Content type of the current playing media."""
return MEDIA_TYPE_MUSIC
@property
def media_duration(self):
"""Duration of current playing media in seconds."""
return self.status.get('duration')
@property
def media_title(self):
"""Title of current playing media."""
return self.status['tag'].get('title')
@property
def media_artist(self):
"""Artist of current playing media, music track only."""
return self.status['tag'].get('artist')
@property
def media_track(self):
"""Track number of current playing media, music track only."""
return self.status['tag'].get('tracknumber')
@property
def media_album_name(self):
"""Album name of current playing media, music track only."""
return self.status['tag'].get('album')
@property
def media_album_artist(self):
"""Album artist of current playing media, music track only."""
return self.status['tag'].get('albumartist')
@property
def volume_level(self):
"""Return the volume level."""
left = self.status['set'].get('vol_left')[0]
right = self.status['set'].get('vol_right')[0]
if left != right:
volume = float(left + right) / 2
else:
volume = left
return int(volume)/100
@property
def supported_media_commands(self):
"""Flag of media commands that are supported."""
return SUPPORT_CMUS
def turn_off(self):
"""Service to send the CMUS the command to stop playing."""
self.cmus.player_stop()
def turn_on(self):
"""Service to send the CMUS the command to start playing."""
self.cmus.player_play()
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
self.cmus.set_volume(int(volume * 100))
def volume_up(self):
"""Function to send CMUS the command for volume up."""
left = self.status['set'].get('vol_left')
right = self.status['set'].get('vol_right')
if left != right:
current_volume = float(left + right) / 2
else:
current_volume = left
if current_volume <= 100:
self.cmus.set_volume(int(current_volume) + 5)
def volume_down(self):
"""Function to send CMUS the command for volume down."""
left = self.status['set'].get('vol_left')
right = self.status['set'].get('vol_right')
if left != right:
current_volume = float(left + right) / 2
else:
current_volume = left
if current_volume <= 100:
self.cmus.set_volume(int(current_volume) - 5)
def play_media(self, media_type, media_id, **kwargs):
"""Send the play command."""
if media_type in [MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST]:
self.cmus.player_play_file(media_id)
else:
_LOGGER.error(
"Invalid media type %s. Only %s and %s are supported",
media_type, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST)
def media_pause(self):
"""Send the pause command."""
self.cmus.player_pause()
def media_next_track(self):
"""Send next track command."""
self.cmus.player_next()
def media_previous_track(self):
"""Send next track command."""
self.cmus.player_prev()
def media_seek(self, position):
"""Send seek command."""
self.cmus.seek(position)
def media_play(self):
"""Send the play command."""
self.cmus.player_play()
def media_stop(self):
"""Send the stop command."""
self.cmus.stop()

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 | \
@ -301,4 +301,4 @@ class KodiDevice(MediaPlayerDevice):
def play_media(self, media_type, media_id, **kwargs):
"""Send the play_media command to the media player."""
self._server.Player.Open({media_type: media_id}, {})
self._server.Player.Open({"item": {"file": str(media_id)}})

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

@ -29,7 +29,7 @@ MQTT_CLIENT = None
SERVICE_PUBLISH = 'publish'
EVENT_MQTT_MESSAGE_RECEIVED = 'mqtt_message_received'
REQUIREMENTS = ['paho-mqtt==1.1']
REQUIREMENTS = ['paho-mqtt==1.2']
CONF_EMBEDDED = 'embedded'
CONF_BROKER = 'broker'

View File

@ -7,7 +7,7 @@ https://home-assistant.io/components/notify.pushover/
import logging
from homeassistant.components.notify import (
ATTR_TITLE, DOMAIN, BaseNotificationService)
ATTR_TITLE, ATTR_TARGET, ATTR_DATA, DOMAIN, BaseNotificationService)
from homeassistant.const import CONF_API_KEY
from homeassistant.helpers import validate_config
@ -51,7 +51,18 @@ class PushoverNotificationService(BaseNotificationService):
"""Send a message to a user."""
from pushover import RequestError
# Make a copy and use empty dict if necessary
data = dict(kwargs.get(ATTR_DATA) or {})
data['title'] = kwargs.get(ATTR_TITLE)
target = kwargs.get(ATTR_TARGET)
if target is not None:
data['device'] = target
try:
self.pushover.send_message(message, title=kwargs.get(ATTR_TITLE))
self.pushover.send_message(message, **data)
except ValueError as val_err:
_LOGGER.error(str(val_err))
except RequestError:
_LOGGER.exception("Could not send pushover notification")

View File

@ -10,7 +10,7 @@ from homeassistant.components.notify import DOMAIN, BaseNotificationService
from homeassistant.const import CONF_API_KEY
from homeassistant.helpers import validate_config
REQUIREMENTS = ['slacker==0.9.16']
REQUIREMENTS = ['slacker==0.9.17']
_LOGGER = logging.getLogger(__name__)

View File

@ -0,0 +1,80 @@
"""
A component which is collecting configuration errors.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/persistent_notification/
"""
import logging
import voluptuous as vol
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template, config_validation as cv
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.util import slugify
DOMAIN = 'persistent_notification'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
SERVICE_CREATE = 'create'
ATTR_TITLE = 'title'
ATTR_MESSAGE = 'message'
ATTR_NOTIFICATION_ID = 'notification_id'
SCHEMA_SERVICE_CREATE = vol.Schema({
vol.Required(ATTR_MESSAGE): cv.template,
vol.Optional(ATTR_TITLE): cv.template,
vol.Optional(ATTR_NOTIFICATION_ID): cv.string,
})
DEFAULT_OBJECT_ID = 'notification'
_LOGGER = logging.getLogger(__name__)
def create(hass, message, title=None, notification_id=None):
"""Turn all or specified light off."""
data = {
key: value for key, value in [
(ATTR_TITLE, title),
(ATTR_MESSAGE, message),
(ATTR_NOTIFICATION_ID, notification_id),
] if value is not None
}
hass.services.call(DOMAIN, SERVICE_CREATE, data)
def setup(hass, config):
"""Setup the persistent notification component."""
def create_service(call):
"""Handle a create notification service call."""
title = call.data.get(ATTR_TITLE)
message = call.data.get(ATTR_MESSAGE)
notification_id = call.data.get(ATTR_NOTIFICATION_ID)
if notification_id is not None:
entity_id = ENTITY_ID_FORMAT.format(slugify(notification_id))
else:
entity_id = generate_entity_id(ENTITY_ID_FORMAT, DEFAULT_OBJECT_ID,
hass=hass)
attr = {}
if title is not None:
try:
title = template.render(hass, title)
except TemplateError as ex:
_LOGGER.error('Error rendering title %s: %s', title, ex)
attr[ATTR_TITLE] = title
try:
message = template.render(hass, message)
except TemplateError as ex:
_LOGGER.error('Error rendering message %s: %s', message, ex)
hass.states.set(entity_id, message, attr)
hass.services.register(DOMAIN, SERVICE_CREATE, create_service, {},
SCHEMA_SERVICE_CREATE)
return True

View File

@ -0,0 +1,101 @@
"""
The homematic rollershutter platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/rollershutter.homematic/
Important: For this platform to work the homematic component has to be
properly configured.
"""
import logging
from homeassistant.const import (STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN)
from homeassistant.components.rollershutter import RollershutterDevice,\
ATTR_CURRENT_POSITION
import homeassistant.components.homematic as homematic
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['homematic']
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""Setup the platform."""
if discovery_info is None:
return
return homematic.setup_hmdevice_discovery_helper(HMRollershutter,
discovery_info,
add_callback_devices)
class HMRollershutter(homematic.HMDevice, RollershutterDevice):
"""Represents a Homematic Rollershutter in Home Assistant."""
@property
def current_position(self):
"""
Return current position of rollershutter.
None is unknown, 0 is closed, 100 is fully open.
"""
if self.available:
return int((1 - self._hm_get_state()) * 100)
return None
def position(self, **kwargs):
"""Move to a defined position: 0 (closed) and 100 (open)."""
if self.available:
if ATTR_CURRENT_POSITION in kwargs:
position = float(kwargs[ATTR_CURRENT_POSITION])
position = min(100, max(0, position))
level = (100 - position) / 100.0
self._hmdevice.set_level(level, self._channel)
@property
def state(self):
"""Return the state of the rollershutter."""
current = self.current_position
if current is None:
return STATE_UNKNOWN
return STATE_CLOSED if current == 100 else STATE_OPEN
def move_up(self, **kwargs):
"""Move the rollershutter up."""
if self.available:
self._hmdevice.move_up(self._channel)
def move_down(self, **kwargs):
"""Move the rollershutter down."""
if self.available:
self._hmdevice.move_down(self._channel)
def stop(self, **kwargs):
"""Stop the device if in motion."""
if self.available:
self._hmdevice.stop(self._channel)
def _check_hm_to_ha_object(self):
"""Check if possible to use the HM Object as this HA type."""
from pyhomematic.devicetypes.actors import Blind
# 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
if isinstance(self._hmdevice, Blind):
return True
_LOGGER.critical("This %s can't be use as rollershutter!", self._name)
return False
def _init_data_struct(self):
"""Generate a data dict (self._data) from hm metadata."""
super()._init_data_struct()
# Add state to data dict
self._state = "LEVEL"
self._data.update({self._state: STATE_UNKNOWN})

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

@ -0,0 +1,76 @@
"""
Support for Zwave roller shutter components.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/rollershutter.zwave/
"""
# Because we do not compile openzwave on CI
# pylint: disable=import-error
import logging
from homeassistant.components.rollershutter import DOMAIN
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.rollershutter import RollershutterDevice
COMMAND_CLASS_SWITCH_MULTILEVEL = 0x26 # 38
COMMAND_CLASS_SWITCH_BINARY = 0x25 # 37
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and return Z-Wave roller shutters."""
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
if value.command_class != zwave.COMMAND_CLASS_SWITCH_MULTILEVEL:
return
if value.index != 0:
return
value.set_change_verified(False)
add_devices([ZwaveRollershutter(value)])
class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice):
"""Representation of an Zwave roller shutter."""
def __init__(self, value):
"""Initialize the zwave rollershutter."""
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._node = value.node
dispatcher.connect(
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
def value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.value_id == value.value_id:
self.update_ha_state(True)
_LOGGER.debug("Value changed on network %s", value)
@property
def current_position(self):
"""Return the current position of Zwave roller shutter."""
return self._value.data
def move_up(self, **kwargs):
"""Move the roller shutter up."""
self._node.set_dimmer(self._value.value_id, 100)
def move_down(self, **kwargs):
"""Move the roller shutter down."""
self._node.set_dimmer(self._value.value_id, 0)
def stop(self, **kwargs):
"""Stop the roller shutter."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_BINARY).values():
# Rollershutter will toggle between UP (True), DOWN (False).
# It also stops the shutter if the same value is sent while moving.
value.data = value.data
break

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

@ -0,0 +1,68 @@
"""
Support for Envisalink sensors (shows panel info).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.envisalink/
"""
import logging
from homeassistant.components.envisalink import (EVL_CONTROLLER,
PARTITION_SCHEMA,
CONF_PARTITIONNAME,
EnvisalinkDevice,
SIGNAL_KEYPAD_UPDATE)
DEPENDENCIES = ['envisalink']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Perform the setup for Envisalink sensor devices."""
_configured_partitions = discovery_info['partitions']
for part_num in _configured_partitions:
_device_config_data = PARTITION_SCHEMA(
_configured_partitions[part_num])
_device = EnvisalinkSensor(
_device_config_data[CONF_PARTITIONNAME],
part_num,
EVL_CONTROLLER.alarm_state['partition'][part_num],
EVL_CONTROLLER)
add_devices_callback([_device])
class EnvisalinkSensor(EnvisalinkDevice):
"""Representation of an envisalink keypad."""
def __init__(self, partition_name, partition_number, info, controller):
"""Initialize the sensor."""
from pydispatch import dispatcher
self._icon = 'mdi:alarm'
self._partition_number = partition_number
_LOGGER.debug('Setting up sensor for partition: ' + partition_name)
EnvisalinkDevice.__init__(self,
partition_name + ' Keypad',
info,
controller)
dispatcher.connect(self._update_callback,
signal=SIGNAL_KEYPAD_UPDATE,
sender=dispatcher.Any)
@property
def icon(self):
"""Return the icon if any."""
return self._icon
@property
def state(self):
"""Return the overall state."""
return self._info['status']['alpha']
@property
def device_state_attributes(self):
"""Return the state attributes."""
return self._info['status']
def _update_callback(self, partition):
"""Update the partition state in HA, if needed."""
if partition is None or int(partition) == self._partition_number:
self.update_ha_state()

View File

@ -0,0 +1,125 @@
"""
Currency exchange rate support that comes from fixer.io.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.fixer/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.const import (CONF_PLATFORM, CONF_NAME)
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['fixerio==0.1.1']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "Exchange rate"
ICON = 'mdi:currency'
CONF_BASE = 'base'
CONF_TARGET = 'target'
STATE_ATTR_BASE = 'Base currency'
STATE_ATTR_TARGET = 'Target currency'
STATE_ATTR_EXCHANGE_RATE = 'Exchange rate'
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'fixer',
vol.Optional(CONF_BASE): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_TARGET): cv.string,
})
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(days=1)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Fixer.io sensor."""
from fixerio import (Fixerio, exceptions)
name = config.get(CONF_NAME, DEFAULT_NAME)
base = config.get(CONF_BASE, 'USD')
target = config.get(CONF_TARGET)
try:
Fixerio(base=base, symbols=[target], secure=True).latest()
except exceptions.FixerioException:
_LOGGER.error('One of the given currencies is not supported')
return False
data = ExchangeData(base, target)
add_devices([ExchangeRateSensor(data, name, target)])
# pylint: disable=too-few-public-methods
class ExchangeRateSensor(Entity):
"""Representation of a Exchange sensor."""
def __init__(self, data, name, target):
"""Initialize the sensor."""
self.data = data
self._target = target
self._name = name
self._state = None
self.update()
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._target
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self.data.rate is not None:
return {
STATE_ATTR_BASE: self.data.rate['base'],
STATE_ATTR_TARGET: self._target,
STATE_ATTR_EXCHANGE_RATE: self.data.rate['rates'][self._target]
}
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return ICON
def update(self):
"""Get the latest data and updates the states."""
self.data.update()
self._state = round(self.data.rate['rates'][self._target], 3)
class ExchangeData(object):
"""Get the latest data and update the states."""
def __init__(self, base_currency, target_currency):
"""Initialize the data object."""
from fixerio import Fixerio
self.rate = None
self.base_currency = base_currency
self.target_currency = target_currency
self.exchange = Fixerio(base=self.base_currency,
symbols=[self.target_currency],
secure=True)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from Fixer.io."""
self.rate = self.exchange.latest()

View File

@ -0,0 +1,115 @@
"""
The homematic sensor platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.homematic/
Important: For this platform to work the homematic component has to be
properly configured.
"""
import logging
from homeassistant.const import STATE_UNKNOWN
import homeassistant.components.homematic as homematic
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['homematic']
HM_STATE_HA_CAST = {
"RotaryHandleSensor": {0: "closed", 1: "tilted", 2: "open"},
"WaterSensor": {0: "dry", 1: "wet", 2: "water"}
}
HM_UNIT_HA_CAST = {
"HUMIDITY": "%",
"TEMPERATURE": "°C",
"BRIGHTNESS": "#",
"POWER": "W",
"CURRENT": "mA",
"VOLTAGE": "V",
"ENERGY_COUNTER": "Wh"
}
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""Setup the platform."""
if discovery_info is None:
return
return homematic.setup_hmdevice_discovery_helper(HMSensor,
discovery_info,
add_callback_devices)
class HMSensor(homematic.HMDevice):
"""Represents various Homematic sensors in Home Assistant."""
@property
def state(self):
"""Return the state of the sensor."""
if not self.available:
return STATE_UNKNOWN
# Does a cast exist for this class?
name = self._hmdevice.__class__.__name__
if name in HM_STATE_HA_CAST:
return HM_STATE_HA_CAST[name].get(self._hm_get_state(), None)
# No cast, return original value
return self._hm_get_state()
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
if not self.available:
return None
return HM_UNIT_HA_CAST.get(self._state, None)
def _check_hm_to_ha_object(self):
"""Check if possible to use the HM Object as this HA type."""
from pyhomematic.devicetypes.sensors import HMSensor as pyHMSensor
# 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
if not isinstance(self._hmdevice, pyHMSensor):
_LOGGER.critical("This %s can't be use as sensor!", self._name)
return False
# Does user defined value exist?
if self._state and self._state not in self._hmdevice.SENSORNODE:
# pylint: disable=logging-too-many-args
_LOGGER.critical("This %s have no sensor with %s! Values are",
self._name, self._state,
str(self._hmdevice.SENSORNODE.keys()))
return False
# No param is set and more than 1 sensor nodes are present
if self._state is None and len(self._hmdevice.SENSORNODE) > 1:
_LOGGER.critical("This %s has multiple sensor nodes. " +
"Please us param. Values are: %s", self._name,
str(self._hmdevice.SENSORNODE.keys()))
return False
_LOGGER.debug("%s is okay for linking", self._name)
return True
def _init_data_struct(self):
"""Generate a data dict (self._data) from hm metadata."""
super()._init_data_struct()
if self._state is None and len(self._hmdevice.SENSORNODE) == 1:
for value in self._hmdevice.SENSORNODE:
self._state = value
# Add state to data dict
if self._state:
_LOGGER.debug("%s init datadict with main node '%s'", self._name,
self._state)
self._data.update({self._state: STATE_UNKNOWN})
else:
_LOGGER.critical("Can't correctly init sensor %s.", self._name)

View File

@ -11,19 +11,29 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
from homeassistant.loader import get_component
DEPENDENCIES = ["netatmo"]
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'],
'co2': ['CO2', 'ppm', 'mdi:cloud'],
'pressure': ['Pressure', 'mbar', 'mdi:gauge'],
'noise': ['Noise', 'dB', 'mdi:volume-high'],
'humidity': ['Humidity', '%', 'mdi:water-percent'],
'rain': ['Rain', 'mm', 'mdi:weather-rainy'],
'sum_rain_1': ['sum_rain_1', 'mm', 'mdi:weather-rainy'],
'sum_rain_24': ['sum_rain_24', 'mm', 'mdi:weather-rainy'],
'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'],
'co2': ['CO2', 'ppm', 'mdi:cloud'],
'pressure': ['Pressure', 'mbar', 'mdi:gauge'],
'noise': ['Noise', 'dB', 'mdi:volume-high'],
'humidity': ['Humidity', '%', 'mdi:water-percent'],
'rain': ['Rain', 'mm', 'mdi:weather-rainy'],
'sum_rain_1': ['sum_rain_1', 'mm', 'mdi:weather-rainy'],
'sum_rain_24': ['sum_rain_24', 'mm', 'mdi:weather-rainy'],
'battery_vp': ['Battery', '', 'mdi:battery'],
'min_temp': ['Min Temp.', TEMP_CELSIUS, 'mdi:thermometer'],
'max_temp': ['Max Temp.', TEMP_CELSIUS, 'mdi:thermometer'],
'WindAngle': ['Angle', '', 'mdi:compass'],
'WindStrength': ['Strength', 'km/h', 'mdi:weather-windy'],
'GustAngle': ['Gust Angle', '', 'mdi:compass'],
'GustStrength': ['Gust Strength', 'km/h', 'mdi:weather-windy'],
'rf_status': ['Radio', '', 'mdi:signal'],
'wifi_status': ['Wifi', '', 'mdi:wifi']
}
CONF_STATION = 'station'
@ -97,6 +107,8 @@ class NetAtmoSensor(Entity):
return self._unit_of_measurement
# pylint: disable=too-many-branches
# Fix for pylint too many statements error
# pylint: disable=too-many-statements
def update(self):
"""Get the latest data from NetAtmo API and updates the states."""
self.netatmo_data.update()
@ -118,6 +130,79 @@ class NetAtmoSensor(Entity):
self._state = data['CO2']
elif self.type == 'pressure':
self._state = round(data['Pressure'], 1)
elif self.type == 'battery_vp':
if data['battery_vp'] >= 5500:
self._state = "Full"
elif data['battery_vp'] >= 5100:
self._state = "High"
elif data['battery_vp'] >= 4600:
self._state = "Medium"
elif data['battery_vp'] >= 4100:
self._state = "Low"
elif data['battery_vp'] < 4100:
self._state = "Very Low"
elif self.type == 'min_temp':
self._state = data['min_temp']
elif self.type == 'max_temp':
self._state = data['max_temp']
elif self.type == 'WindAngle':
if data['WindAngle'] >= 330:
self._state = "North (%d\xb0)" % data['WindAngle']
elif data['WindAngle'] >= 300:
self._state = "North-West (%d\xb0)" % data['WindAngle']
elif data['WindAngle'] >= 240:
self._state = "West (%d\xb0)" % data['WindAngle']
elif data['WindAngle'] >= 210:
self._state = "South-West (%d\xb0)" % data['WindAngle']
elif data['WindAngle'] >= 150:
self._state = "South (%d\xb0)" % data['WindAngle']
elif data['WindAngle'] >= 120:
self._state = "South-East (%d\xb0)" % data['WindAngle']
elif data['WindAngle'] >= 60:
self._state = "East (%d\xb0)" % data['WindAngle']
elif data['WindAngle'] >= 30:
self._state = "North-East (%d\xb0)" % data['WindAngle']
elif data['WindAngle'] >= 0:
self._state = "North (%d\xb0)" % data['WindAngle']
elif self.type == 'WindStrength':
self._state = data['WindStrength']
elif self.type == 'GustAngle':
if data['GustAngle'] >= 330:
self._state = "North (%d\xb0)" % data['GustAngle']
elif data['GustAngle'] >= 300:
self._state = "North-West (%d\xb0)" % data['GustAngle']
elif data['GustAngle'] >= 240:
self._state = "West (%d\xb0)" % data['GustAngle']
elif data['GustAngle'] >= 210:
self._state = "South-West (%d\xb0)" % data['GustAngle']
elif data['GustAngle'] >= 150:
self._state = "South (%d\xb0)" % data['GustAngle']
elif data['GustAngle'] >= 120:
self._state = "South-East (%d\xb0)" % data['GustAngle']
elif data['GustAngle'] >= 60:
self._state = "East (%d\xb0)" % data['GustAngle']
elif data['GustAngle'] >= 30:
self._state = "North-East (%d\xb0)" % data['GustAngle']
elif data['GustAngle'] >= 0:
self._state = "North (%d\xb0)" % data['GustAngle']
elif self.type == 'GustStrength':
self._state = data['GustStrength']
elif self.type == 'rf_status':
if data['rf_status'] >= 90:
self._state = "Low"
elif data['rf_status'] >= 76:
self._state = "Medium"
elif data['rf_status'] >= 60:
self._state = "High"
elif data['rf_status'] <= 59:
self._state = "Full"
elif self.type == 'wifi_status':
if data['wifi_status'] >= 86:
self._state = "Bad"
elif data['wifi_status'] >= 71:
self._state = "Middle"
elif data['wifi_status'] <= 70:
self._state = "Good"
class NetAtmoData(object):

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

@ -0,0 +1,105 @@
"""
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
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
from homeassistant.const import CONF_API_KEY
_RESOURCE = 'https://openexchangerates.org/api/latest.json'
_LOGGER = logging.getLogger(__name__)
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=100)
CONF_BASE = 'base'
CONF_QUOTE = 'quote'
CONF_NAME = 'name'
DEFAULT_NAME = 'Exchange Rate Sensor'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Openexchangerates sensor."""
payload = config.get('payload', None)
rest = OpenexchangeratesData(
_RESOURCE,
config.get(CONF_API_KEY),
config.get(CONF_BASE, 'USD'),
config.get(CONF_QUOTE),
payload
)
response = requests.get(_RESOURCE, params={'base': config.get(CONF_BASE,
'USD'),
'app_id':
config.get(CONF_API_KEY)},
timeout=10)
if response.status_code != 200:
_LOGGER.error("Check your OpenExchangeRates API")
return False
rest.update()
add_devices([OpenexchangeratesSensor(rest, config.get(CONF_NAME,
DEFAULT_NAME),
config.get(CONF_QUOTE))])
class OpenexchangeratesSensor(Entity):
"""Representation of an Openexchangerates sensor."""
def __init__(self, rest, name, quote):
"""Initialize the sensor."""
self.rest = rest
self._name = name
self._quote = quote
self.update()
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def device_state_attributes(self):
"""Return other attributes of the sensor."""
return self.rest.data
def update(self):
"""Update current conditions."""
self.rest.update()
value = self.rest.data
self._state = round(value[str(self._quote)], 4)
# pylint: disable=too-few-public-methods
class OpenexchangeratesData(object):
"""Get data from Openexchangerates.org."""
# pylint: disable=too-many-arguments
def __init__(self, resource, api_key, base, quote, data):
"""Initialize the data object."""
self._resource = resource
self._api_key = api_key
self._base = base
self._quote = quote
self.data = None
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from openexchangerates.org."""
try:
result = requests.get(self._resource, params={'base': self._base,
'app_id':
self._api_key},
timeout=10)
self.data = result.json()['rates']
except requests.exceptions.HTTPError:
_LOGGER.error("Check Openexchangerates API Key")
self.data = None
return False

View File

@ -16,7 +16,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
REQUIREMENTS = ['beautifulsoup4==4.4.1', 'lxml==3.6.0']
REQUIREMENTS = ['beautifulsoup4==4.4.1']
_LOGGER = logging.getLogger(__name__)
_RESOURCE = 'http://www.hydrodaten.admin.ch/en/'
@ -47,7 +47,7 @@ HydroData = collections.namedtuple(
'temperature_max'])
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30)
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -148,7 +148,7 @@ class HydrologicalData(object):
try:
tables = BeautifulSoup(response.content,
'lxml').findChildren('table')
'html.parser').findChildren('table')
rows = tables[0].findChildren(['th', 'tr'])
details = []

View File

@ -1,5 +1,5 @@
"""
Support for monitoring the local system..
Support for monitoring the local system.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.systemmonitor/
@ -10,7 +10,7 @@ import homeassistant.util.dt as dt_util
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['psutil==4.2.0']
REQUIREMENTS = ['psutil==4.3.0']
SENSOR_TYPES = {
'disk_use_percent': ['Disk Use', '%', 'mdi:harddisk'],
'disk_use': ['Disk Use', 'GiB', 'mdi:harddisk'],

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

@ -6,6 +6,7 @@ https://home-assistant.io/components/shell_command/
"""
import logging
import subprocess
import shlex
import voluptuous as vol
@ -23,8 +24,6 @@ CONFIG_SCHEMA = vol.Schema({
}),
}, extra=vol.ALLOW_EXTRA)
SHELL_COMMAND_SCHEMA = vol.Schema({})
def setup(hass, config):
"""Setup the shell_command component."""
@ -44,8 +43,7 @@ def setup(hass, config):
_LOGGER.exception('Error running command: %s', cmd)
for name in conf.keys():
hass.services.register(DOMAIN, name, service_handler,
schema=SHELL_COMMAND_SCHEMA)
hass.services.register(DOMAIN, name, service_handler)
return True
@ -64,6 +62,6 @@ def _parse_command(hass, cmd, variables):
shell = True
else:
# template used. Must break into list and use shell=False for security
cmd = [prog] + rendered_args.split()
cmd = [prog] + shlex.split(rendered_args)
shell = False
return cmd, shell

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

@ -108,7 +108,7 @@ def setup(hass, config):
class SwitchDevice(ToggleEntity):
"""Representation of a switch."""
# pylint: disable=no-self-use
# pylint: disable=no-self-use, abstract-method
@property
def current_power_mwh(self):
"""Return the current power usage in mWh."""

View File

@ -4,7 +4,6 @@ Use serial protocol of acer projector to obtain state of the projector.
This component allows to control almost all projectors from acer using
their RS232 serial communication protocol.
"""
import logging
import re
@ -61,7 +60,8 @@ class AcerSwitch(SwitchDevice):
write_timeout=write_timeout, **kwargs)
self._serial_port = serial_port
self._name = name
self._state = STATE_UNKNOWN
self._state = False
self._available = False
self._attributes = {
LAMP_HOURS: STATE_UNKNOWN,
INPUT_SOURCE: STATE_UNKNOWN,
@ -100,14 +100,19 @@ class AcerSwitch(SwitchDevice):
return match.group(1)
return STATE_UNKNOWN
@property
def available(self):
"""Return if projector is available."""
return self._available
@property
def name(self):
"""Return name of the projector."""
return self._name
@property
def state(self):
"""Return the current state of the projector."""
def is_on(self):
"""Return if the projector is turned on."""
return self._state
@property
@ -120,11 +125,13 @@ class AcerSwitch(SwitchDevice):
msg = CMD_DICT[LAMP]
awns = self._write_read_format(msg)
if awns == 'Lamp 1':
self._state = STATE_ON
self._state = True
self._available = True
elif awns == 'Lamp 0':
self._state = STATE_OFF
self._state = False
self._available = True
else:
self._state = STATE_UNKNOWN
self._available = False
for key in self._attributes.keys():
msg = CMD_DICT.get(key, None)

View File

@ -4,6 +4,7 @@ Support for device running with the aREST RESTful framework.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.arest/
"""
# pylint: disable=abstract-method
import logging
import requests

View File

@ -0,0 +1,102 @@
"""
Support for Homematic switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.homematic/
"""
import logging
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import STATE_UNKNOWN
import homeassistant.components.homematic as homematic
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['homematic']
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""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):
"""Representation of a Homematic switch."""
@property
def is_on(self):
"""Return True if switch is on."""
try:
return self._hm_get_state() > 0
except TypeError:
return False
@property
def current_power_mwh(self):
"""Return the current power usage in mWh."""
if "ENERGY_COUNTER" in self._data:
try:
return self._data["ENERGY_COUNTER"] / 1000
except ZeroDivisionError:
return 0
return None
def turn_on(self, **kwargs):
"""Turn the switch on."""
if self.available:
self._hmdevice.on(self._channel)
def turn_off(self, **kwargs):
"""Turn the switch off."""
if self.available:
self._hmdevice.off(self._channel)
def _check_hm_to_ha_object(self):
"""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
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)
return False
def _init_data_struct(self):
"""Generate a data dict (self._data) from the Homematic metadata."""
from pyhomematic.devicetypes.actors import Dimmer,\
Switch, SwitchPowermeter
super()._init_data_struct()
# Use STATE
if isinstance(self._hmdevice, Switch):
self._state = "STATE"
# Use LEVEL
if isinstance(self._hmdevice, Dimmer):
self._state = "LEVEL"
# Need sensor values for SwitchPowermeter
if isinstance(self._hmdevice, SwitchPowermeter):
for node in self._hmdevice.SENSORNODE:
self._data.update({node: STATE_UNKNOWN})
# Add state to data dict
if self._state:
_LOGGER.debug("%s init data dict with main node '%s'", self._name,
self._state)
self._data.update({self._state: STATE_UNKNOWN})
else:
_LOGGER.critical("Can't correctly init light %s.", self._name)

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
@ -65,6 +65,10 @@ class WOLSwitch(SwitchDevice):
self._wol.send_magic_packet(self._mac_address)
self.update_ha_state()
def turn_off(self):
"""Do nothing."""
pass
def update(self):
"""Check if device is on and update the state."""
if platform.system().lower() == "windows":

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

@ -273,29 +273,29 @@ class ThermostatDevice(Entity):
"""Return true if the fan is on."""
return None
def set_temperate(self, temperature):
def set_temperature(self, temperature):
"""Set new target temperature."""
pass
raise NotImplementedError()
def set_hvac_mode(self, hvac_mode):
"""Set hvac mode."""
pass
raise NotImplementedError()
def turn_away_mode_on(self):
"""Turn away mode on."""
pass
raise NotImplementedError()
def turn_away_mode_off(self):
"""Turn away mode off."""
pass
raise NotImplementedError()
def turn_fan_on(self):
"""Turn fan on."""
pass
raise NotImplementedError()
def turn_fan_off(self):
"""Turn fan off."""
pass
raise NotImplementedError()
@property
def min_temp(self):

View File

@ -16,7 +16,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
])
# pylint: disable=too-many-arguments
# pylint: disable=too-many-arguments, abstract-method
class DemoThermostat(ThermostatDevice):
"""Representation of a demo thermostat."""

View File

@ -68,7 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
schema=SET_FAN_MIN_ON_TIME_SCHEMA)
# pylint: disable=too-many-public-methods
# pylint: disable=too-many-public-methods, abstract-method
class Thermostat(ThermostatDevice):
"""A thermostat class for Ecobee."""

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'
@ -31,7 +31,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return True
# pylint: disable=too-many-instance-attributes, import-error
# pylint: disable=too-many-instance-attributes, import-error, abstract-method
class EQ3BTSmartThermostat(ThermostatDevice):
"""Representation of a EQ3 Bluetooth Smart thermostat."""

View File

@ -41,7 +41,6 @@ PLATFORM_SCHEMA = vol.Schema({
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the heat control thermostat."""
name = config.get(CONF_NAME)
@ -55,7 +54,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
min_temp, max_temp, target_temp)])
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-instance-attributes, abstract-method
class HeatControl(ThermostatDevice):
"""Representation of a HeatControl device."""

View File

@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class HeatmiserV3Thermostat(ThermostatDevice):
"""Representation of a HeatmiserV3 thermostat."""
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-instance-attributes, abstract-method
def __init__(self, heatmiser, device, name, serport):
"""Initialize the thermostat."""
self.heatmiser = heatmiser

View File

@ -1,122 +1,34 @@
"""
Support for Homematic (HM-TC-IT-WM-W-EU, HM-CC-RT-DN) thermostats.
Support for Homematic thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/thermostat.homematic/
"""
import logging
import socket
from xmlrpc.client import ServerProxy
from xmlrpc.client import Error
from collections import namedtuple
import homeassistant.components.homematic as homematic
from homeassistant.components.thermostat import ThermostatDevice
from homeassistant.const import TEMP_CELSIUS
from homeassistant.helpers.temperature import convert
from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN
REQUIREMENTS = []
DEPENDENCIES = ['homematic']
_LOGGER = logging.getLogger(__name__)
CONF_ADDRESS = 'address'
CONF_DEVICES = 'devices'
CONF_ID = 'id'
PROPERTY_SET_TEMPERATURE = 'SET_TEMPERATURE'
PROPERTY_VALVE_STATE = 'VALVE_STATE'
PROPERTY_ACTUAL_TEMPERATURE = 'ACTUAL_TEMPERATURE'
PROPERTY_BATTERY_STATE = 'BATTERY_STATE'
PROPERTY_LOWBAT = 'LOWBAT'
PROPERTY_CONTROL_MODE = 'CONTROL_MODE'
PROPERTY_BURST_MODE = 'BURST_RX'
TYPE_HM_THERMOSTAT = 'HOMEMATIC_THERMOSTAT'
TYPE_HM_WALLTHERMOSTAT = 'HOMEMATIC_WALLTHERMOSTAT'
TYPE_MAX_THERMOSTAT = 'MAX_THERMOSTAT'
HomematicConfig = namedtuple('HomematicConfig',
['device_type',
'platform_type',
'channel',
'maint_channel'])
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""Setup the Homematic thermostat platform."""
if discovery_info is None:
return
HM_TYPE_MAPPING = {
'HM-CC-RT-DN': HomematicConfig('HM-CC-RT-DN',
TYPE_HM_THERMOSTAT,
4, 4),
'HM-CC-RT-DN-BoM': HomematicConfig('HM-CC-RT-DN-BoM',
TYPE_HM_THERMOSTAT,
4, 4),
'HM-TC-IT-WM-W-EU': HomematicConfig('HM-TC-IT-WM-W-EU',
TYPE_HM_WALLTHERMOSTAT,
2, 2),
'BC-RT-TRX-CyG': HomematicConfig('BC-RT-TRX-CyG',
TYPE_MAX_THERMOSTAT,
1, 0),
'BC-RT-TRX-CyG-2': HomematicConfig('BC-RT-TRX-CyG-2',
TYPE_MAX_THERMOSTAT,
1, 0),
'BC-RT-TRX-CyG-3': HomematicConfig('BC-RT-TRX-CyG-3',
TYPE_MAX_THERMOSTAT,
1, 0)
}
return homematic.setup_hmdevice_discovery_helper(HMThermostat,
discovery_info,
add_callback_devices)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Homematic thermostat."""
devices = []
try:
address = config[CONF_ADDRESS]
homegear = ServerProxy(address)
for name, device_cfg in config[CONF_DEVICES].items():
# get device description to detect the type
device_type = homegear.getDeviceDescription(
device_cfg[CONF_ID] + ':-1')['TYPE']
if device_type in HM_TYPE_MAPPING.keys():
devices.append(HomematicThermostat(
HM_TYPE_MAPPING[device_type],
address,
device_cfg[CONF_ID],
name))
else:
raise ValueError(
"Device Type '{}' currently not supported".format(
device_type))
except socket.error:
_LOGGER.exception("Connection error to homematic web service")
return False
add_devices(devices)
return True
# pylint: disable=too-many-instance-attributes
class HomematicThermostat(ThermostatDevice):
# pylint: disable=abstract-method
class HMThermostat(homematic.HMDevice, ThermostatDevice):
"""Representation of a Homematic thermostat."""
def __init__(self, hm_config, address, _id, name):
"""Initialize the thermostat."""
self._hm_config = hm_config
self.address = address
self._id = _id
self._name = name
self._full_device_name = '{}:{}'.format(self._id,
self._hm_config.channel)
self._maint_device_name = '{}:{}'.format(self._id,
self._hm_config.maint_channel)
self._current_temperature = None
self._target_temperature = None
self._valve = None
self._battery = None
self._mode = None
self.update()
@property
def name(self):
"""Return the name of the Homematic device."""
return self._name
@property
def unit_of_measurement(self):
"""Return the unit of measurement that is used."""
@ -125,26 +37,22 @@ class HomematicThermostat(ThermostatDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature
if not self.available:
return None
return self._data["ACTUAL_TEMPERATURE"]
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
"""Return the target temperature."""
if not self.available:
return None
return self._data["SET_TEMPERATURE"]
def set_temperature(self, temperature):
"""Set new target temperature."""
device = ServerProxy(self.address)
device.setValue(self._full_device_name,
PROPERTY_SET_TEMPERATURE,
temperature)
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {"valve": self._valve,
"battery": self._battery,
"mode": self._mode}
if not self.available:
return None
self._hmdevice.set_temperature(temperature)
@property
def min_temp(self):
@ -156,39 +64,27 @@ class HomematicThermostat(ThermostatDevice):
"""Return the maximum temperature - 30.5 means on."""
return convert(30.5, TEMP_CELSIUS, self.unit_of_measurement)
def update(self):
"""Update the data from the thermostat."""
try:
device = ServerProxy(self.address)
self._current_temperature = device.getValue(
self._full_device_name,
PROPERTY_ACTUAL_TEMPERATURE)
self._target_temperature = device.getValue(
self._full_device_name,
PROPERTY_SET_TEMPERATURE)
self._valve = device.getValue(
self._full_device_name,
PROPERTY_VALVE_STATE)
self._mode = device.getValue(
self._full_device_name,
PROPERTY_CONTROL_MODE)
def _check_hm_to_ha_object(self):
"""Check if possible to use the Homematic object as this HA type."""
from pyhomematic.devicetypes.thermostats import HMThermostat\
as pyHMThermostat
if self._hm_config.platform_type in [TYPE_HM_THERMOSTAT,
TYPE_HM_WALLTHERMOSTAT]:
self._battery = device.getValue(self._maint_device_name,
PROPERTY_BATTERY_STATE)
elif self._hm_config.platform_type == TYPE_MAX_THERMOSTAT:
# emulate homematic battery voltage,
# max reports lowbat if voltage < 2.2V
# while homematic battery_state should
# be between 1.5V and 4.6V
lowbat = device.getValue(self._maint_device_name,
PROPERTY_LOWBAT)
if lowbat:
self._battery = 1.5
else:
self._battery = 4.6
# Check compatibility from HMDevice
if not super()._check_hm_to_ha_object():
return False
except Error:
_LOGGER.exception("Did not receive any temperature data from the "
"homematic API.")
# Check if the Homematic device correct for this HA device
if isinstance(self._hmdevice, pyHMThermostat):
return True
_LOGGER.critical("This %s can't be use as thermostat", self._name)
return False
def _init_data_struct(self):
"""Generate a data dict (self._data) from the Homematic metadata."""
super()._init_data_struct()
# Add state to data dict
self._data.update({"CONTROL_MODE": STATE_UNKNOWN,
"SET_TEMPERATURE": STATE_UNKNOWN,
"ACTUAL_TEMPERATURE": STATE_UNKNOWN})

View File

@ -49,7 +49,6 @@ def _setup_round(username, password, config, add_devices):
# config will be used later
# pylint: disable=unused-argument
def _setup_us(username, password, config, add_devices):
"""Setup user."""
import somecomfort
@ -74,7 +73,6 @@ def _setup_us(username, password, config, add_devices):
return True
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the honeywel thermostat."""
username = config.get(CONF_USERNAME)
@ -98,7 +96,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class RoundThermostat(ThermostatDevice):
"""Representation of a Honeywell Round Connected thermostat."""
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-instance-attributes, abstract-method
def __init__(self, device, zone_id, master, away_temp):
"""Initialize the thermostat."""
self.device = device
@ -182,6 +180,7 @@ class RoundThermostat(ThermostatDevice):
self._is_dhw = False
# pylint: disable=abstract-method
class HoneywellUSThermostat(ThermostatDevice):
"""Representation of a Honeywell US Thermostat."""

View File

@ -26,6 +26,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for structure, device in nest.devices()])
# pylint: disable=abstract-method
class NestThermostat(ThermostatDevice):
"""Representation of a Nest thermostat."""

View File

@ -27,6 +27,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
])
# pylint: disable=abstract-method
class ProliphixThermostat(ThermostatDevice):
"""Representation a Proliphix thermostat."""

View File

@ -45,6 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(tstats)
# pylint: disable=abstract-method
class RadioThermostat(ThermostatDevice):
"""Representation of a Radio Thermostat."""

View File

@ -58,6 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class ZWaveThermostat(zwave.ZWaveDeviceEntity, ThermostatDevice):
"""Represents a HeatControl thermostat."""
@ -80,7 +81,7 @@ class ZWaveThermostat(zwave.ZWaveDeviceEntity, ThermostatDevice):
def value_changed(self, value):
"""Called when a value has changed on the network."""
if self._value.node == value.node:
if self._value.value_id == value.value_id:
self.update_properties()
self.update_ha_state()
@ -156,3 +157,4 @@ class ZWaveThermostat(zwave.ZWaveDeviceEntity, ThermostatDevice):
COMMAND_CLASS_THERMOSTAT_SETPOINT).items():
if int(value.data) != 0 and value.index == self._index:
value.data = temperature
break

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

@ -39,16 +39,41 @@ SERVICE_TEST_NETWORK = "test_network"
EVENT_SCENE_ACTIVATED = "zwave.scene_activated"
COMMAND_CLASS_SWITCH_MULTILEVEL = 38
COMMAND_CLASS_DOOR_LOCK = 98
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
COMMAND_CLASS_SENSOR_BINARY = 48
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
COMMAND_CLASS_METER = 50
COMMAND_CLASS_SWITCH_MULTILEVEL = 38
COMMAND_CLASS_DOOR_LOCK = 98
COMMAND_CLASS_THERMOSTAT_SETPOINT = 67
COMMAND_CLASS_THERMOSTAT_FAN_MODE = 68
COMMAND_CLASS_BATTERY = 128
COMMAND_CLASS_ALARM = 113 # 0x71
COMMAND_CLASS_THERMOSTAT_SETPOINT = 67 # 0x43
COMMAND_CLASS_THERMOSTAT_FAN_MODE = 68 # 0x44
GENERIC_COMMAND_CLASS_WHATEVER = None
GENERIC_COMMAND_CLASS_MULTILEVEL_SWITCH = 17
GENERIC_COMMAND_CLASS_BINARY_SWITCH = 16
GENERIC_COMMAND_CLASS_ENTRY_CONTROL = 64
GENERIC_COMMAND_CLASS_BINARY_SENSOR = 32
GENERIC_COMMAND_CLASS_MULTILEVEL_SENSOR = 33
GENERIC_COMMAND_CLASS_METER = 49
GENERIC_COMMAND_CLASS_ALARM_SENSOR = 161
GENERIC_COMMAND_CLASS_THERMOSTAT = 8
SPECIFIC_DEVICE_CLASS_WHATEVER = None
SPECIFIC_DEVICE_CLASS_NOT_USED = 0
SPECIFIC_DEVICE_CLASS_MULTILEVEL_POWER_SWITCH = 1
SPECIFIC_DEVICE_CLASS_ADVANCED_DOOR_LOCK = 2
SPECIFIC_DEVICE_CLASS_MULTIPOSITION_MOTOR = 3
SPECIFIC_DEVICE_CLASS_SECURE_KEYPAD_DOOR_LOCK = 3
SPECIFIC_DEVICE_CLASS_MULTILEVEL_SCENE = 4
SPECIFIC_DEVICE_CLASS_SECURE_DOOR = 5
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_A = 5
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_B = 6
SPECIFIC_DEVICE_CLASS_SECURE_BARRIER_ADD_ON = 7
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_C = 7
GENRE_WHATEVER = None
GENRE_USER = "User"
@ -60,38 +85,71 @@ TYPE_DECIMAL = "Decimal"
# List of tuple (DOMAIN, discovered service, supported command classes,
# value type).
# value type, genre type, specific device class).
DISCOVERY_COMPONENTS = [
('sensor',
[GENERIC_COMMAND_CLASS_WHATEVER],
[SPECIFIC_DEVICE_CLASS_WHATEVER],
[COMMAND_CLASS_SENSOR_MULTILEVEL,
COMMAND_CLASS_METER,
COMMAND_CLASS_ALARM],
TYPE_WHATEVER,
GENRE_USER),
('light',
[GENERIC_COMMAND_CLASS_MULTILEVEL_SWITCH],
[SPECIFIC_DEVICE_CLASS_MULTILEVEL_POWER_SWITCH,
SPECIFIC_DEVICE_CLASS_MULTILEVEL_SCENE],
[COMMAND_CLASS_SWITCH_MULTILEVEL],
TYPE_BYTE,
GENRE_USER),
('switch',
[GENERIC_COMMAND_CLASS_BINARY_SWITCH],
[SPECIFIC_DEVICE_CLASS_WHATEVER],
[COMMAND_CLASS_SWITCH_BINARY],
TYPE_BOOL,
GENRE_USER),
('binary_sensor',
[GENERIC_COMMAND_CLASS_BINARY_SENSOR,
GENERIC_COMMAND_CLASS_MULTILEVEL_SENSOR],
[SPECIFIC_DEVICE_CLASS_WHATEVER],
[COMMAND_CLASS_SENSOR_BINARY],
TYPE_BOOL,
GENRE_USER),
('thermostat',
[GENERIC_COMMAND_CLASS_THERMOSTAT],
[SPECIFIC_DEVICE_CLASS_WHATEVER],
[COMMAND_CLASS_THERMOSTAT_SETPOINT],
TYPE_WHATEVER,
GENRE_WHATEVER),
('hvac',
[GENERIC_COMMAND_CLASS_THERMOSTAT],
[SPECIFIC_DEVICE_CLASS_WHATEVER],
[COMMAND_CLASS_THERMOSTAT_FAN_MODE],
TYPE_WHATEVER,
GENRE_WHATEVER),
('lock',
[GENERIC_COMMAND_CLASS_ENTRY_CONTROL],
[SPECIFIC_DEVICE_CLASS_ADVANCED_DOOR_LOCK,
SPECIFIC_DEVICE_CLASS_SECURE_KEYPAD_DOOR_LOCK],
[COMMAND_CLASS_DOOR_LOCK],
TYPE_BOOL,
GENRE_USER),
('rollershutter',
[GENERIC_COMMAND_CLASS_MULTILEVEL_SWITCH],
[SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_A,
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_B,
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_C,
SPECIFIC_DEVICE_CLASS_MULTIPOSITION_MOTOR],
[COMMAND_CLASS_WHATEVER],
TYPE_WHATEVER,
GENRE_USER),
('garage_door',
[GENERIC_COMMAND_CLASS_ENTRY_CONTROL],
[SPECIFIC_DEVICE_CLASS_SECURE_BARRIER_ADD_ON,
SPECIFIC_DEVICE_CLASS_SECURE_DOOR],
[COMMAND_CLASS_SWITCH_BINARY],
TYPE_BOOL,
GENRE_USER)
]
@ -220,18 +278,49 @@ def setup(hass, config):
def value_added(node, value):
"""Called when a value is added to a node on the network."""
for (component,
command_ids,
generic_device_class,
specific_device_class,
command_class,
value_type,
value_genre) in DISCOVERY_COMPONENTS:
if value.command_class not in command_ids:
_LOGGER.debug("Component=%s Node_id=%s query start",
component, node.node_id)
if node.generic not in generic_device_class and \
None not in generic_device_class:
_LOGGER.debug("node.generic %s not None and in \
generic_device_class %s",
node.generic, generic_device_class)
continue
if value_type is not None and value_type != value.type:
if node.specific not in specific_device_class and \
None not in specific_device_class:
_LOGGER.debug("node.specific %s is not None and in \
specific_device_class %s", node.specific,
specific_device_class)
continue
if value_genre is not None and value_genre != value.genre:
if value.command_class not in command_class and \
None not in command_class:
_LOGGER.debug("value.command_class %s is not None \
and in command_class %s",
value.command_class, command_class)
continue
if value_type != value.type and value_type is not None:
_LOGGER.debug("value.type %s != value_type %s",
value.type, value_type)
continue
if value_genre != value.genre and value_genre is not None:
_LOGGER.debug("value.genre %s != value_genre %s",
value.genre, value_genre)
continue
# Configure node
_LOGGER.debug("Adding Node_id=%s Generic_command_class=%s, \
Specific_command_class=%s, \
Command_class=%s, Value type=%s, \
Genre=%s", node.node_id,
node.generic, node.specific,
value.command_class, value.type,
value.genre)
name = "{}.{}".format(component, _object_id(value))
node_config = customize.get(name, {})

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:
@ -149,9 +162,118 @@ def load_yaml_config_file(config_path):
conf_dict = load_yaml(config_path)
if not isinstance(conf_dict, dict):
_LOGGER.error(
'The configuration file %s does not contain a dictionary',
msg = 'The configuration file {} does not contain a dictionary'.format(
os.path.basename(config_path))
raise HomeAssistantError()
_LOGGER.error(msg)
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

@ -1,7 +1,7 @@
# coding: utf-8
"""Constants used by Home Assistant components."""
__version__ = "0.22.1"
__version__ = "0.23.0"
REQUIRED_PYTHON_VER = (3, 4)
PLATFORM_FORMAT = '{}.{}'

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):
@ -456,7 +480,7 @@ class StateMachine(object):
return True
def set(self, entity_id, new_state, attributes=None):
def set(self, entity_id, new_state, attributes=None, force_update=False):
"""Set the state of an entity, add entity if it does not exist.
Attributes is an optional dict to specify attributes of this state.
@ -472,7 +496,8 @@ class StateMachine(object):
old_state = self._states.get(entity_id)
is_existing = old_state is not None
same_state = is_existing and old_state.state == new_state
same_state = (is_existing and old_state.state == new_state and
not force_update)
same_attr = is_existing and old_state.attributes == attributes
if same_state and same_attr:
@ -680,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

@ -1,6 +1,6 @@
"""An abstract class for entities."""
import logging
import re
from collections import defaultdict
from homeassistant.const import (
ATTR_ASSUMED_STATE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ICON,
@ -10,8 +10,10 @@ from homeassistant.const import (
from homeassistant.exceptions import NoEntitySpecifiedError
from homeassistant.util import ensure_unique_string, slugify
# Dict mapping entity_id to a boolean that overwrites the hidden property
_OVERWRITE = defaultdict(dict)
# Entity attributes that we will overwrite
_OVERWRITE = {}
_LOGGER = logging.getLogger(__name__)
# Pattern for validating entity IDs (format: <domain>.<entity>)
ENTITY_ID_PATTERN = re.compile(r"^(\w+)\.(\w+)$")
@ -22,7 +24,7 @@ def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
name = (name or DEVICE_DEFAULT_NAME).lower()
if current_ids is None:
if hass is None:
raise RuntimeError("Missing required parameter currentids or hass")
raise ValueError("Missing required parameter currentids or hass")
current_ids = hass.states.entity_ids()
@ -30,6 +32,13 @@ def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
entity_id_format.format(slugify(name)), current_ids)
def set_customize(customize):
"""Overwrite all current customize settings."""
global _OVERWRITE
_OVERWRITE = {key.lower(): val for key, val in customize.items()}
def split_entity_id(entity_id):
"""Split a state entity_id into domain, object_id."""
return entity_id.split(".", 1)
@ -116,6 +125,15 @@ class Entity(object):
"""Return True if unable to access real state of the entity."""
return False
@property
def force_update(self):
"""Return True if state updates should be forced.
If True, a state change will be triggered anytime the state property is
updated, not just when the value changes.
"""
return False
def update(self):
"""Retrieve latest state."""
pass
@ -181,7 +199,8 @@ class Entity(object):
state, attr[ATTR_UNIT_OF_MEASUREMENT])
state = str(state)
return self.hass.states.set(self.entity_id, state, attr)
return self.hass.states.set(
self.entity_id, state, attr, self.force_update)
def _attr_setter(self, name, typ, attr, attrs):
"""Helper method to populate attributes based on properties."""
@ -207,20 +226,6 @@ class Entity(object):
"""Return the representation."""
return "<Entity {}: {}>".format(self.name, self.state)
@staticmethod
def overwrite_attribute(entity_id, attrs, vals):
"""Overwrite any attribute of an entity.
This function should receive a list of attributes and a
list of values. Set attribute to None to remove any overwritten
value in place.
"""
for attr, val in zip(attrs, vals):
if val is None:
_OVERWRITE[entity_id.lower()].pop(attr, None)
else:
_OVERWRITE[entity_id.lower()][attr] = val
class ToggleEntity(Entity):
"""An abstract class for entities that can be turned on and off."""
@ -234,15 +239,15 @@ class ToggleEntity(Entity):
@property
def is_on(self):
"""Return True if entity is on."""
return False
raise NotImplementedError()
def turn_on(self, **kwargs):
"""Turn the entity on."""
pass
raise NotImplementedError()
def turn_off(self, **kwargs):
"""Turn the entity off."""
pass
raise NotImplementedError()
def toggle(self, **kwargs):
"""Toggle the entity off."""

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):
@ -259,9 +266,9 @@ class StateMachine(ha.StateMachine):
"""
return remove_state(self._api, entity_id)
def set(self, entity_id, new_state, attributes=None):
def set(self, entity_id, new_state, attributes=None, force_update=False):
"""Call set_state on remote API."""
set_state(self._api, entity_id, new_state, attributes)
set_state(self._api, entity_id, new_state, attributes, force_update)
def mirror(self):
"""Discard current data and mirrors the remote state machine."""
@ -450,7 +457,7 @@ def remove_state(api, entity_id):
return False
def set_state(api, entity_id, new_state, attributes=None):
def set_state(api, entity_id, new_state, attributes=None, force_update=False):
"""Tell API to update state for entity_id.
Return True if success.
@ -458,7 +465,8 @@ def set_state(api, entity_id, new_state, attributes=None):
attributes = attributes or {}
data = {'state': new_state,
'attributes': attributes}
'attributes': attributes,
'force_update': force_update}
try:
req = api(METHOD_POST,

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

Some files were not shown because too many files have changed in this diff Show More