Merge pull request #10898 from home-assistant/release-0-59

0.59
This commit is contained in:
Fabian Affolter 2017-12-03 13:24:22 +01:00 committed by GitHub
commit 850a20a626
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
170 changed files with 5160 additions and 1470 deletions

View File

@ -53,6 +53,8 @@ omit =
homeassistant/components/digital_ocean.py
homeassistant/components/*/digital_ocean.py
homeassistant/components/dominos.py
homeassistant/components/doorbird.py
homeassistant/components/*/doorbird.py
@ -80,6 +82,9 @@ omit =
homeassistant/components/hdmi_cec.py
homeassistant/components/*/hdmi_cec.py
homeassistant/components/hive.py
homeassistant/components/*/hive.py
homeassistant/components/homematic.py
homeassistant/components/*/homematic.py
@ -182,6 +187,9 @@ omit =
homeassistant/components/tado.py
homeassistant/components/*/tado.py
homeassistant/components/tahoma.py
homeassistant/components/*/tahoma.py
homeassistant/components/tellduslive.py
homeassistant/components/*/tellduslive.py
@ -426,7 +434,6 @@ omit =
homeassistant/components/notify/clicksend.py
homeassistant/components/notify/clicksend_tts.py
homeassistant/components/notify/discord.py
homeassistant/components/notify/facebook.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/group.py
@ -589,7 +596,6 @@ omit =
homeassistant/components/sensor/worldtidesinfo.py
homeassistant/components/sensor/worxlandroid.py
homeassistant/components/sensor/xbox_live.py
homeassistant/components/sensor/yweather.py
homeassistant/components/sensor/zamg.py
homeassistant/components/shiftr.py
homeassistant/components/spc.py
@ -622,6 +628,7 @@ omit =
homeassistant/components/telegram_bot/*
homeassistant/components/thingspeak.py
homeassistant/components/tts/amazon_polly.py
homeassistant/components/tts/baidu.py
homeassistant/components/tts/microsoft.py
homeassistant/components/tts/picotts.py
homeassistant/components/vacuum/roomba.py
@ -635,7 +642,6 @@ omit =
homeassistant/components/zwave/util.py
homeassistant/components/vacuum/mqtt.py
[report]
# Regexes for lines to exclude from consideration
exclude_lines =

2
.gitignore vendored
View File

@ -96,4 +96,4 @@ docs/build
desktop.ini
/home-assistant.pyproj
/home-assistant.sln
/.vs/home-assistant/v14
/.vs/*

View File

@ -8,18 +8,18 @@ matrix:
include:
- python: "3.4.2"
env: TOXENV=lint
- python: "3.4.2"
env: TOXENV=pylint
- python: "3.4.2"
env: TOXENV=py34
# - python: "3.5"
# env: TOXENV=typing
- python: "3.5"
- python: "3.5.3"
env: TOXENV=py35
- python: "3.6"
env: TOXENV=py36
# - python: "3.6-dev"
# env: TOXENV=py36
- python: "3.4.2"
env: TOXENV=requirements
# allow_failures:
# - python: "3.5"
# env: TOXENV=typing
@ -29,5 +29,5 @@ cache:
- $HOME/.cache/pip
install: pip install -U tox coveralls
language: python
script: travis_wait tox
script: travis_wait 30 tox --develop
after_success: coveralls

View File

@ -46,6 +46,7 @@ homeassistant/components/climate/eq3btsmart.py @rytilahti
homeassistant/components/climate/sensibo.py @andrey-git
homeassistant/components/cover/template.py @PhracturedBlue
homeassistant/components/device_tracker/automatic.py @armills
homeassistant/components/device_tracker/tile.py @bachya
homeassistant/components/history_graph.py @andrey-git
homeassistant/components/light/tplink.py @rytilahti
homeassistant/components/light/yeelight.py @rytilahti
@ -63,13 +64,19 @@ homeassistant/components/switch/tplink.py @rytilahti
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/broadlink.py @danielhiversen
homeassistant/components/hive.py @Rendili @KJonline
homeassistant/components/*/hive.py @Rendili @KJonline
homeassistant/components/*/rfxtrx.py @danielhiversen
homeassistant/components/velux.py @Julius2342
homeassistant/components/*/velux.py @Julius2342
homeassistant/components/knx.py @Julius2342
homeassistant/components/*/knx.py @Julius2342
homeassistant/components/tahoma.py @philklei
homeassistant/components/*/tahoma.py @philklei
homeassistant/components/tesla.py @zabuldon
homeassistant/components/*/tesla.py @zabuldon
homeassistant/components/tellduslive.py @molobrakos @fredrike
homeassistant/components/*/tellduslive.py @molobrakos @fredrike
homeassistant/components/*/tradfri.py @ggravlingen
homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -14,7 +14,7 @@ import voluptuous as vol
from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_NIGHT)
SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_CUSTOM_BYPASS)
from homeassistant.config import load_yaml_config_file
from homeassistant.loader import bind_hass
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
@ -33,6 +33,7 @@ SERVICE_TO_METHOD = {
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away',
SERVICE_ALARM_ARM_NIGHT: 'alarm_arm_night',
SERVICE_ALARM_ARM_CUSTOM_BYPASS: 'alarm_arm_custom_bypass',
SERVICE_ALARM_TRIGGER: 'alarm_trigger'
}
@ -107,6 +108,18 @@ def alarm_trigger(hass, code=None, entity_id=None):
hass.services.call(DOMAIN, SERVICE_ALARM_TRIGGER, data)
@bind_hass
def alarm_arm_custom_bypass(hass, code=None, entity_id=None):
"""Send the alarm the command for arm custom bypass."""
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_CUSTOM_BYPASS, data)
@asyncio.coroutine
def async_setup(hass, config):
"""Track states and offer events for sensors."""
@ -216,6 +229,17 @@ class AlarmControlPanel(Entity):
"""
return self.hass.async_add_job(self.alarm_trigger, code)
def alarm_arm_custom_bypass(self, code=None):
"""Send arm custom bypass command."""
raise NotImplementedError()
def async_alarm_arm_custom_bypass(self, code=None):
"""Send arm custom bypass command.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.alarm_arm_custom_bypass, code)
@property
def state_attributes(self):
"""Return the state attributes."""

View File

@ -22,6 +22,7 @@ _LOGGER = logging.getLogger(__name__)
ARMED = 'armed'
CONF_HOME_MODE_NAME = 'home_mode_name'
CONF_AWAY_MODE_NAME = 'away_mode_name'
DEPENDENCIES = ['arlo']
@ -31,6 +32,7 @@ ICON = 'mdi:security'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOME_MODE_NAME, default=ARMED): cv.string,
vol.Optional(CONF_AWAY_MODE_NAME, default=ARMED): cv.string,
})
@ -43,19 +45,22 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
return
home_mode_name = config.get(CONF_HOME_MODE_NAME)
away_mode_name = config.get(CONF_AWAY_MODE_NAME)
base_stations = []
for base_station in data.base_stations:
base_stations.append(ArloBaseStation(base_station, home_mode_name))
base_stations.append(ArloBaseStation(base_station, home_mode_name,
away_mode_name))
async_add_devices(base_stations, True)
class ArloBaseStation(AlarmControlPanel):
"""Representation of an Arlo Alarm Control Panel."""
def __init__(self, data, home_mode_name):
def __init__(self, data, home_mode_name, away_mode_name):
"""Initialize the alarm control panel."""
self._base_station = data
self._home_mode_name = home_mode_name
self._away_mode_name = away_mode_name
self._state = None
@property
@ -89,8 +94,8 @@ class ArloBaseStation(AlarmControlPanel):
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
self._base_station.mode = ARMED
"""Send arm away command. Uses custom mode."""
self._base_station.mode = self._away_mode_name
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
@ -118,4 +123,6 @@ class ArloBaseStation(AlarmControlPanel):
return STATE_ALARM_DISARMED
elif mode == self._home_mode_name:
return STATE_ALARM_ARMED_HOME
elif mode == self._away_mode_name:
return STATE_ALARM_ARMED_AWAY
return None

View File

@ -7,7 +7,7 @@ https://home-assistant.io/components/demo/
import homeassistant.components.alarm_control_panel.manual as manual
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_TRIGGERED, CONF_PENDING_TIME)
STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_TRIGGERED, CONF_PENDING_TIME)
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -23,6 +23,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
STATE_ALARM_ARMED_NIGHT: {
CONF_PENDING_TIME: 5
},
STATE_ALARM_ARMED_CUSTOM_BYPASS: {
CONF_PENDING_TIME: 5
},
STATE_ALARM_TRIGGERED: {
CONF_PENDING_TIME: 5
},

View File

@ -14,9 +14,9 @@ import homeassistant.components.alarm_control_panel as alarm
import homeassistant.util.dt as dt_util
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED,
CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
CONF_DISARM_AFTER_TRIGGER)
STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_DISARMED, STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME, CONF_CODE,
CONF_PENDING_TIME, CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_time
@ -26,7 +26,8 @@ DEFAULT_TRIGGER_TIME = 120
DEFAULT_DISARM_AFTER_TRIGGER = False
SUPPORTED_PENDING_STATES = [STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_TRIGGERED]
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_TRIGGERED,
STATE_ALARM_ARMED_CUSTOM_BYPASS]
ATTR_POST_PENDING_STATE = 'post_pending_state'
@ -59,6 +60,8 @@ PLATFORM_SCHEMA = vol.Schema(vol.All({
vol.Optional(STATE_ALARM_ARMED_AWAY, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_ARMED_HOME, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_ARMED_NIGHT, default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_ARMED_CUSTOM_BYPASS,
default={}): STATE_SETTING_SCHEMA,
vol.Optional(STATE_ALARM_TRIGGERED, default={}): STATE_SETTING_SCHEMA,
}, _state_validator))
@ -174,6 +177,13 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._update_state(STATE_ALARM_ARMED_NIGHT)
def alarm_arm_custom_bypass(self, code=None):
"""Send arm custom bypass command."""
if not self._validate_code(code, STATE_ALARM_ARMED_CUSTOM_BYPASS):
return
self._update_state(STATE_ALARM_ARMED_CUSTOM_BYPASS)
def alarm_trigger(self, code=None):
"""Send alarm trigger command. No code needed."""
self._pre_trigger_state = self._state

View File

@ -16,7 +16,7 @@ from homeassistant.const import (
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME)
REQUIREMENTS = ['total_connect_client==0.13']
REQUIREMENTS = ['total_connect_client==0.16']
_LOGGER = logging.getLogger(__name__)

View File

@ -171,7 +171,7 @@ def async_api_discovery(hass, config, request):
# Required description as per Amazon Scene docs
if entity.domain == scene.DOMAIN:
scene_fmt = '%s (Scene connected via Home Assistant)'
scene_fmt = '{} (Scene connected via Home Assistant)'
description = scene_fmt.format(description)
cat_key = ATTR_ALEXA_DISPLAY_CATEGORIES

View File

@ -89,6 +89,7 @@ def setup(hass, config):
"""Set up the Amcrest IP Camera component."""
from amcrest import AmcrestCamera
hass.data[DATA_AMCREST] = {}
amcrest_cams = config[DOMAIN]
for device in amcrest_cams:
@ -126,22 +127,34 @@ def setup(hass, config):
else:
authentication = None
hass.data[DATA_AMCREST][name] = AmcrestDevice(
camera, name, authentication, ffmpeg_arguments, stream_source,
resolution)
discovery.load_platform(
hass, 'camera', DOMAIN, {
'device': camera,
CONF_AUTHENTICATION: authentication,
CONF_FFMPEG_ARGUMENTS: ffmpeg_arguments,
CONF_NAME: name,
CONF_RESOLUTION: resolution,
CONF_STREAM_SOURCE: stream_source,
}, config)
if sensors:
discovery.load_platform(
hass, 'sensor', DOMAIN, {
'device': camera,
CONF_NAME: name,
CONF_SENSORS: sensors,
}, config)
return True
class AmcrestDevice(object):
"""Representation of a base Amcrest discovery device."""
def __init__(self, camera, name, authentication, ffmpeg_arguments,
stream_source, resolution):
"""Initialize the entity."""
self.device = camera
self.name = name
self.authentication = authentication
self.ffmpeg_arguments = ffmpeg_arguments
self.stream_source = stream_source
self.resolution = resolution

View File

@ -37,8 +37,8 @@ def async_trigger(hass, config, action):
above = config.get(CONF_ABOVE)
time_delta = config.get(CONF_FOR)
value_template = config.get(CONF_VALUE_TEMPLATE)
async_remove_track_same = None
already_triggered = False
unsub_track_same = {}
entities_triggered = set()
if value_template is not None:
value_template.hass = hass
@ -63,8 +63,6 @@ def async_trigger(hass, config, action):
@callback
def state_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal already_triggered, async_remove_track_same
@callback
def call_action():
"""Call action with right context."""
@ -81,16 +79,18 @@ def async_trigger(hass, config, action):
matching = check_numeric_state(entity, from_s, to_s)
if matching and not already_triggered:
if not matching:
entities_triggered.discard(entity)
elif entity not in entities_triggered:
entities_triggered.add(entity)
if time_delta:
async_remove_track_same = async_track_same_state(
unsub_track_same[entity] = async_track_same_state(
hass, time_delta, call_action, entity_ids=entity_id,
async_check_same_func=check_numeric_state)
else:
call_action()
already_triggered = matching
unsub = async_track_state_change(
hass, entity_id, state_automation_listener)
@ -98,7 +98,8 @@ def async_trigger(hass, config, action):
def async_remove():
"""Remove state listeners async."""
unsub()
if async_remove_track_same:
async_remove_track_same() # pylint: disable=not-callable
for async_remove in unsub_track_same.values():
async_remove()
unsub_track_same.clear()
return async_remove

View File

@ -35,13 +35,11 @@ def async_trigger(hass, config, action):
to_state = config.get(CONF_TO, MATCH_ALL)
time_delta = config.get(CONF_FOR)
match_all = (from_state == MATCH_ALL and to_state == MATCH_ALL)
async_remove_track_same = None
unsub_track_same = {}
@callback
def state_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal async_remove_track_same
@callback
def call_action():
"""Call action with right context."""
@ -64,7 +62,7 @@ def async_trigger(hass, config, action):
call_action()
return
async_remove_track_same = async_track_same_state(
unsub_track_same[entity] = async_track_same_state(
hass, time_delta, call_action,
lambda _, _2, to_state: to_state.state == to_s.state,
entity_ids=entity_id)
@ -76,7 +74,8 @@ def async_trigger(hass, config, action):
def async_remove():
"""Remove state listeners async."""
unsub()
if async_remove_track_same:
async_remove_track_same() # pylint: disable=not-callable
for async_remove in unsub_track_same.values():
async_remove()
unsub_track_same.clear()
return async_remove

View File

@ -5,7 +5,6 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/axis/
"""
import json
import logging
import os
@ -22,6 +21,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.entity import Entity
from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['axis==14']
@ -103,9 +103,9 @@ def request_configuration(hass, config, name, host, serialnumber):
return False
if setup_device(hass, config, device_config):
config_file = _read_config(hass)
config_file = load_json(hass.config.path(CONFIG_FILE))
config_file[serialnumber] = dict(device_config)
_write_config(hass, config_file)
save_json(hass.config.path(CONFIG_FILE), config_file)
configurator.request_done(request_id)
else:
configurator.notify_errors(request_id,
@ -163,7 +163,7 @@ def setup(hass, config):
serialnumber = discovery_info['properties']['macaddress']
if serialnumber not in AXIS_DEVICES:
config_file = _read_config(hass)
config_file = load_json(hass.config.path(CONFIG_FILE))
if serialnumber in config_file:
# Device config previously saved to file
try:
@ -274,25 +274,6 @@ def setup_device(hass, config, device_config):
return True
def _read_config(hass):
"""Read Axis config."""
path = hass.config.path(CONFIG_FILE)
if not os.path.isfile(path):
return {}
with open(path) as f_handle:
# Guard against empty file
return json.loads(f_handle.read() or '{}')
def _write_config(hass, config):
"""Write Axis config."""
data = json.dumps(config)
with open(hass.config.path(CONFIG_FILE), 'w', encoding='utf-8') as outfile:
outfile.write(data)
class AxisDeviceEvent(Entity):
"""Representation of a Axis device event."""

View File

@ -20,6 +20,7 @@ SCAN_INTERVAL = timedelta(seconds=30)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DEVICE_CLASSES = [
'battery', # On means low, Off means normal
'cold', # On means cold (or too cold)
'connectivity', # On means connection present, Off = no connection
'gas', # CO, CO2, etc.
@ -32,6 +33,7 @@ DEVICE_CLASSES = [
'opening', # Door, window, etc.
'plug', # On means plugged in, Off means unplugged
'power', # Power, over-current, etc
'presence', # On means home, Off means away
'safety', # Generic on=unsafe, off=safe
'smoke', # Smoke detector
'sound', # On means sound detected, Off means no sound

View File

@ -0,0 +1,63 @@
"""
Support for the Hive devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.hive/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.hive import DATA_HIVE
DEPENDENCIES = ['hive']
DEVICETYPE_DEVICE_CLASS = {'motionsensor': 'motion',
'contactsensor': 'opening'}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Hive sensor devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
add_devices([HiveBinarySensorEntity(session, discovery_info)])
class HiveBinarySensorEntity(BinarySensorDevice):
"""Representation of a Hive binary sensor."""
def __init__(self, hivesession, hivedevice):
"""Initialize the hive sensor."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.node_device_type = hivedevice["Hive_DeviceType"]
self.session = hivesession
self.data_updatesource = '{}.{}'.format(self.device_type,
self.node_id)
self.session.entities.append(self)
def handle_update(self, updatesource):
"""Handle the new update request."""
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
self.schedule_update_ha_state()
@property
def device_class(self):
"""Return the class of this sensor."""
return DEVICETYPE_DEVICE_CLASS.get(self.node_device_type)
@property
def name(self):
"""Return the name of the binary sensor."""
return self.node_name
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.session.sensor.get_state(self.node_id,
self.node_device_type)
def update(self):
"""Update all Node data frome Hive."""
self.session.core.update_data(self.node_id)

View File

@ -25,6 +25,7 @@ SENSOR_TYPES_CLASS = {
'RemoteMotion': None,
'WeatherSensor': None,
'TiltSensor': None,
'PresenceIP': 'motion',
}

View File

@ -8,9 +8,10 @@ import asyncio
import logging
from homeassistant.components.amcrest import (
STREAM_SOURCE_LIST, TIMEOUT)
DATA_AMCREST, STREAM_SOURCE_LIST, TIMEOUT)
from homeassistant.components.camera import Camera
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import CONF_NAME
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_web,
async_aiohttp_proxy_stream)
@ -26,21 +27,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
if discovery_info is None:
return
device = discovery_info['device']
authentication = discovery_info['authentication']
ffmpeg_arguments = discovery_info['ffmpeg_arguments']
name = discovery_info['name']
resolution = discovery_info['resolution']
stream_source = discovery_info['stream_source']
device_name = discovery_info[CONF_NAME]
amcrest = hass.data[DATA_AMCREST][device_name]
async_add_devices([
AmcrestCam(hass,
name,
device,
authentication,
ffmpeg_arguments,
stream_source,
resolution)], True)
async_add_devices([AmcrestCam(hass, amcrest)], True)
return True
@ -48,18 +38,17 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class AmcrestCam(Camera):
"""An implementation of an Amcrest IP camera."""
def __init__(self, hass, name, camera, authentication,
ffmpeg_arguments, stream_source, resolution):
def __init__(self, hass, amcrest):
"""Initialize an Amcrest camera."""
super(AmcrestCam, self).__init__()
self._name = name
self._camera = camera
self._name = amcrest.name
self._camera = amcrest.device
self._base_url = self._camera.get_base_url()
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = ffmpeg_arguments
self._stream_source = stream_source
self._resolution = resolution
self._token = self._auth = authentication
self._ffmpeg_arguments = amcrest.ffmpeg_arguments
self._stream_source = amcrest.stream_source
self._resolution = amcrest.resolution
self._token = self._auth = amcrest.authentication
def camera_image(self):
"""Return a still image response from the camera."""

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -12,7 +12,8 @@ from datetime import timedelta
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.components.ring import DATA_RING, CONF_ATTRIBUTION
from homeassistant.components.ring import (
DATA_RING, CONF_ATTRIBUTION, NOTIFICATION_ID)
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import ATTR_ATTRIBUTION, CONF_SCAN_INTERVAL
@ -27,6 +28,8 @@ FORCE_REFRESH_INTERVAL = timedelta(minutes=45)
_LOGGER = logging.getLogger(__name__)
NOTIFICATION_TITLE = 'Ring Camera Setup'
SCAN_INTERVAL = timedelta(seconds=90)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -42,11 +45,33 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
ring = hass.data[DATA_RING]
cams = []
cams_no_plan = []
for camera in ring.doorbells:
if camera.has_subscription:
cams.append(RingCam(hass, camera, config))
else:
cams_no_plan.append(camera)
for camera in ring.stickup_cams:
if camera.has_subscription:
cams.append(RingCam(hass, camera, config))
else:
cams_no_plan.append(camera)
# show notification for all cameras without an active subscription
if cams_no_plan:
cameras = str(', '.join([camera.name for camera in cams_no_plan]))
err_msg = '''A Ring Protect Plan is required for the''' \
''' following cameras: {}.'''.format(cameras)
_LOGGER.error(err_msg)
hass.components.persistent_notification.async_create(
'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(err_msg),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
async_add_devices(cams, True)
return True
@ -84,7 +109,6 @@ class RingCam(Camera):
'timezone': self._camera.timezone,
'type': self._camera.family,
'video_url': self._video_url,
'video_id': self._last_video_id
}
@asyncio.coroutine

View File

@ -51,6 +51,19 @@ STATE_HIGH_DEMAND = 'high_demand'
STATE_HEAT_PUMP = 'heat_pump'
STATE_GAS = 'gas'
SUPPORT_TARGET_TEMPERATURE = 1
SUPPORT_TARGET_TEMPERATURE_HIGH = 2
SUPPORT_TARGET_TEMPERATURE_LOW = 4
SUPPORT_TARGET_HUMIDITY = 8
SUPPORT_TARGET_HUMIDITY_HIGH = 16
SUPPORT_TARGET_HUMIDITY_LOW = 32
SUPPORT_FAN_MODE = 64
SUPPORT_OPERATION_MODE = 128
SUPPORT_HOLD_MODE = 256
SUPPORT_SWING_MODE = 512
SUPPORT_AWAY_MODE = 1024
SUPPORT_AUX_HEAT = 2048
ATTR_CURRENT_TEMPERATURE = 'current_temperature'
ATTR_MAX_TEMP = 'max_temp'
ATTR_MIN_TEMP = 'min_temp'
@ -717,6 +730,11 @@ class ClimateDevice(Entity):
"""
return self.hass.async_add_job(self.turn_aux_heat_off)
@property
def supported_features(self):
"""Return the list of supported features."""
raise NotImplementedError()
@property
def min_temp(self):
"""Return the minimum temperature."""

View File

@ -5,9 +5,19 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
from homeassistant.components.climate import (
ClimateDevice, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW)
ClimateDevice, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY,
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_AUX_HEAT, SUPPORT_SWING_MODE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_HUMIDITY |
SUPPORT_AWAY_MODE | SUPPORT_HOLD_MODE | SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE | SUPPORT_AUX_HEAT |
SUPPORT_SWING_MODE | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Demo climate devices."""
@ -47,6 +57,11 @@ class DemoClimate(ClimateDevice):
self._target_temperature_high = target_temp_high
self._target_temperature_low = target_temp_low
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def should_poll(self):
"""Return the polling state."""

View File

@ -12,7 +12,9 @@ import voluptuous as vol
from homeassistant.components import ecobee
from homeassistant.components.climate import (
DOMAIN, STATE_COOL, STATE_HEAT, STATE_AUTO, STATE_IDLE, ClimateDevice,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH)
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH)
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
from homeassistant.config import load_yaml_config_file
@ -44,6 +46,10 @@ RESUME_PROGRAM_SCHEMA = vol.Schema({
vol.Optional(ATTR_RESUME_ALL, default=DEFAULT_RESUME_ALL): cv.boolean,
})
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE |
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Ecobee Thermostat Platform."""
@ -132,6 +138,11 @@ class Thermostat(ClimateDevice):
self.thermostat = self.data.ecobee.get_thermostat(
self.thermostat_index)
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def name(self):
"""Return the name of the Ecobee Thermostat."""
@ -318,8 +329,21 @@ class Thermostat(ClimateDevice):
def set_auto_temp_hold(self, heat_temp, cool_temp):
"""Set temperature hold in auto mode."""
self.data.ecobee.set_hold_temp(self.thermostat_index, cool_temp,
heat_temp, self.hold_preference())
if cool_temp is not None:
cool_temp_setpoint = cool_temp
else:
cool_temp_setpoint = (
self.thermostat['runtime']['desiredCool'] / 10.0)
if heat_temp is not None:
heat_temp_setpoint = heat_temp
else:
heat_temp_setpoint = (
self.thermostat['runtime']['desiredCool'] / 10.0)
self.data.ecobee.set_hold_temp(self.thermostat_index,
cool_temp_setpoint, heat_temp_setpoint,
self.hold_preference())
_LOGGER.debug("Setting ecobee hold_temp to: heat=%s, is=%s, "
"cool=%s, is=%s", heat_temp, isinstance(
heat_temp, (int, float)), cool_temp,
@ -348,8 +372,8 @@ class Thermostat(ClimateDevice):
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
temp = kwargs.get(ATTR_TEMPERATURE)
if self.current_operation == STATE_AUTO and low_temp is not None \
and high_temp is not None:
if self.current_operation == STATE_AUTO and (low_temp is not None or
high_temp is not None):
self.set_auto_temp_hold(low_temp, high_temp)
elif temp is not None:
self.set_temp_hold(temp)
@ -357,6 +381,10 @@ class Thermostat(ClimateDevice):
_LOGGER.error(
"Missing valid arguments for set_temperature in %s", kwargs)
def set_humidity(self, humidity):
"""Set the humidity level."""
self.data.ecobee.set_humidity(self.thermostat_index, humidity)
def set_operation_mode(self, operation_mode):
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
self.data.ecobee.set_hvac_mode(self.thermostat_index, operation_mode)

View File

@ -9,7 +9,7 @@ from datetime import timedelta
import voluptuous as vol
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE)
ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT)
from homeassistant.const import (
TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD)
import homeassistant.helpers.config_validation as cv
@ -56,6 +56,11 @@ class EphEmberThermostat(ClimateDevice):
self._zone = zone
self._hot_water = zone['isHotWater']
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_AUX_HEAT
@property
def name(self):
"""Return the name of the thermostat, if any."""

View File

@ -9,7 +9,8 @@ import logging
import voluptuous as vol
from homeassistant.components.climate import (
STATE_ON, STATE_OFF, STATE_AUTO, PLATFORM_SCHEMA, ClimateDevice)
STATE_ON, STATE_OFF, STATE_AUTO, PLATFORM_SCHEMA, ClimateDevice,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE)
from homeassistant.const import (
CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES)
import homeassistant.helpers.config_validation as cv
@ -37,6 +38,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Schema({cv.string: DEVICE_SCHEMA}),
})
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_AWAY_MODE)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the eQ-3 BLE thermostats."""
@ -72,6 +76,11 @@ class EQ3BTSmartThermostat(ClimateDevice):
self._name = _name
self._thermostat = eq3.Thermostat(_mac)
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def available(self) -> bool:
"""Return if thermostat is available."""

View File

@ -17,7 +17,9 @@ import voluptuous as vol
from homeassistant.const import (
CONF_NAME, CONF_SLAVE, TEMP_CELSIUS,
ATTR_TEMPERATURE, DEVICE_DEFAULT_NAME)
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_FAN_MODE)
import homeassistant.components.modbus as modbus
import homeassistant.helpers.config_validation as cv
@ -31,6 +33,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Flexit Platform."""
@ -62,6 +66,11 @@ class Flexit(ClimateDevice):
self._alarm = False
self.unit = pyflexit.pyflexit(modbus.HUB, modbus_slave)
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
def update(self):
"""Update unit attributes."""
if not self.unit.update():

View File

@ -10,17 +10,18 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components import switch
from homeassistant.core import DOMAIN as HA_DOMAIN
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA,
STATE_AUTO)
STATE_AUTO, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
CONF_NAME)
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
from homeassistant.helpers import condition
from homeassistant.helpers.event import (
async_track_state_change, async_track_time_interval)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.restore_state import async_get_last_state
_LOGGER = logging.getLogger(__name__)
@ -40,6 +41,7 @@ CONF_COLD_TOLERANCE = 'cold_tolerance'
CONF_HOT_TOLERANCE = 'hot_tolerance'
CONF_KEEP_ALIVE = 'keep_alive'
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HEATER): cv.entity_id,
@ -117,6 +119,17 @@ class GenericThermostat(ClimateDevice):
if sensor_state:
self._async_update_temp(sensor_state)
@asyncio.coroutine
def async_added_to_hass(self):
"""Run when entity about to be added."""
# If we have an old state and no target temp, restore
if self._target_temp is None:
old_state = yield from async_get_last_state(self.hass,
self.entity_id)
if old_state is not None:
self._target_temp = float(
old_state.attributes[ATTR_TEMPERATURE])
@property
def should_poll(self):
"""Return the polling state."""
@ -167,7 +180,7 @@ class GenericThermostat(ClimateDevice):
elif operation_mode == STATE_OFF:
self._enabled = False
if self._is_device_active:
switch.async_turn_off(self.hass, self.heater_entity_id)
self._heater_turn_off()
else:
_LOGGER.error('Unrecognized operation mode: %s', operation_mode)
return
@ -225,9 +238,9 @@ class GenericThermostat(ClimateDevice):
def _async_keep_alive(self, time):
"""Call at constant intervals for keep-alive purposes."""
if self.current_operation in [STATE_COOL, STATE_HEAT]:
switch.async_turn_on(self.hass, self.heater_entity_id)
self._heater_turn_on()
else:
switch.async_turn_off(self.hass, self.heater_entity_id)
self._heater_turn_off()
@callback
def _async_update_temp(self, state):
@ -273,13 +286,13 @@ class GenericThermostat(ClimateDevice):
self._cold_tolerance
if too_cold:
_LOGGER.info('Turning off AC %s', self.heater_entity_id)
switch.async_turn_off(self.hass, self.heater_entity_id)
self._heater_turn_off()
else:
too_hot = self._cur_temp - self._target_temp >= \
self._hot_tolerance
if too_hot:
_LOGGER.info('Turning on AC %s', self.heater_entity_id)
switch.async_turn_on(self.hass, self.heater_entity_id)
self._heater_turn_on()
else:
is_heating = self._is_device_active
if is_heating:
@ -288,15 +301,34 @@ class GenericThermostat(ClimateDevice):
if too_hot:
_LOGGER.info('Turning off heater %s',
self.heater_entity_id)
switch.async_turn_off(self.hass, self.heater_entity_id)
self._heater_turn_off()
else:
too_cold = self._target_temp - self._cur_temp >= \
self._cold_tolerance
if too_cold:
_LOGGER.info('Turning on heater %s', self.heater_entity_id)
switch.async_turn_on(self.hass, self.heater_entity_id)
self._heater_turn_on()
@property
def _is_device_active(self):
"""If the toggleable device is currently active."""
return switch.is_on(self.hass, self.heater_entity_id)
return self.hass.states.is_state(self.heater_entity_id, STATE_ON)
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@callback
def _heater_turn_on(self):
"""Turn heater toggleable device on."""
data = {ATTR_ENTITY_ID: self.heater_entity_id}
self.hass.async_add_job(
self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_ON, data))
@callback
def _heater_turn_off(self):
"""Turn heater toggleable device off."""
data = {ATTR_ENTITY_ID: self.heater_entity_id}
self.hass.async_add_job(
self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_OFF, data))

View File

@ -8,7 +8,8 @@ import logging
import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_PORT, CONF_NAME, CONF_ID)
import homeassistant.helpers.config_validation as cv
@ -68,6 +69,11 @@ class HeatmiserV3Thermostat(ClimateDevice):
self.update()
self._target_temperature = int(self.dcb.get('roomset'))
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE
@property
def name(self):
"""Return the name of the thermostat, if any."""

View File

@ -0,0 +1,139 @@
"""
Support for the Hive devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.hive/
"""
from homeassistant.components.climate import (
ClimateDevice, STATE_AUTO, STATE_HEAT, STATE_OFF, STATE_ON,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.components.hive import DATA_HIVE
DEPENDENCIES = ['hive']
HIVE_TO_HASS_STATE = {'SCHEDULE': STATE_AUTO, 'MANUAL': STATE_HEAT,
'ON': STATE_ON, 'OFF': STATE_OFF}
HASS_TO_HIVE_STATE = {STATE_AUTO: 'SCHEDULE', STATE_HEAT: 'MANUAL',
STATE_ON: 'ON', STATE_OFF: 'OFF'}
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Hive climate devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
add_devices([HiveClimateEntity(session, discovery_info)])
class HiveClimateEntity(ClimateDevice):
"""Hive Climate Device."""
def __init__(self, hivesession, hivedevice):
"""Initialize the Climate device."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.session = hivesession
self.data_updatesource = '{}.{}'.format(self.device_type,
self.node_id)
if self.device_type == "Heating":
self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF]
elif self.device_type == "HotWater":
self.modes = [STATE_AUTO, STATE_ON, STATE_OFF]
self.session.entities.append(self)
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
def handle_update(self, updatesource):
"""Handle the new update request."""
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
self.schedule_update_ha_state()
@property
def name(self):
"""Return the name of the Climate device."""
friendly_name = "Climate Device"
if self.device_type == "Heating":
friendly_name = "Heating"
if self.node_name is not None:
friendly_name = '{} {}'.format(self.node_name, friendly_name)
elif self.device_type == "HotWater":
friendly_name = "Hot Water"
return friendly_name
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
if self.device_type == "Heating":
return self.session.heating.current_temperature(self.node_id)
@property
def target_temperature(self):
"""Return the target temperature."""
if self.device_type == "Heating":
return self.session.heating.get_target_temperature(self.node_id)
@property
def min_temp(self):
"""Return minimum temperature."""
if self.device_type == "Heating":
return self.session.heating.min_temperature(self.node_id)
@property
def max_temp(self):
"""Return the maximum temperature."""
if self.device_type == "Heating":
return self.session.heating.max_temperature(self.node_id)
@property
def operation_list(self):
"""List of the operation modes."""
return self.modes
@property
def current_operation(self):
"""Return current mode."""
if self.device_type == "Heating":
currentmode = self.session.heating.get_mode(self.node_id)
elif self.device_type == "HotWater":
currentmode = self.session.hotwater.get_mode(self.node_id)
return HIVE_TO_HASS_STATE.get(currentmode)
def set_operation_mode(self, operation_mode):
"""Set new Heating mode."""
new_mode = HASS_TO_HIVE_STATE.get(operation_mode)
if self.device_type == "Heating":
self.session.heating.set_mode(self.node_id, new_mode)
elif self.device_type == "HotWater":
self.session.hotwater.set_mode(self.node_id, new_mode)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def set_temperature(self, **kwargs):
"""Set new target temperature."""
new_temperature = kwargs.get(ATTR_TEMPERATURE)
if new_temperature is not None:
if self.device_type == "Heating":
self.session.heating.set_target_temperature(self.node_id,
new_temperature)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def update(self):
"""Update all Node data frome Hive."""
self.session.core.update_data(self.node_id)

View File

@ -5,7 +5,9 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.homematic/
"""
import logging
from homeassistant.components.climate import ClimateDevice, STATE_AUTO
from homeassistant.components.climate import (
ClimateDevice, STATE_AUTO, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE)
from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN, ATTR_TEMPERATURE
@ -38,6 +40,8 @@ HM_HUMI_MAP = [
HM_CONTROL_MODE = 'CONTROL_MODE'
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Homematic thermostat platform."""
@ -55,6 +59,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class HMThermostat(HMDevice, ClimateDevice):
"""Representation of a Homematic thermostat."""
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def temperature_unit(self):
"""Return the unit of measurement that is used."""

View File

@ -14,12 +14,13 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, ATTR_FAN_MODE, ATTR_FAN_LIST,
ATTR_OPERATION_MODE, ATTR_OPERATION_LIST)
ATTR_OPERATION_MODE, ATTR_OPERATION_LIST, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE)
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE, CONF_REGION)
REQUIREMENTS = ['evohomeclient==0.2.5', 'somecomfort==0.4.1']
REQUIREMENTS = ['evohomeclient==0.2.5', 'somecomfort==0.5.0']
_LOGGER = logging.getLogger(__name__)
@ -126,6 +127,14 @@ class RoundThermostat(ClimateDevice):
self._away_temp = away_temp
self._away = False
@property
def supported_features(self):
"""Return the list of supported features."""
supported = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE)
if hasattr(self.client, ATTR_SYSTEM_MODE):
supported |= SUPPORT_OPERATION_MODE
return supported
@property
def name(self):
"""Return the name of the honeywell, if any."""
@ -234,6 +243,14 @@ class HoneywellUSThermostat(ClimateDevice):
self._username = username
self._password = password
@property
def supported_features(self):
"""Return the list of supported features."""
supported = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE)
if hasattr(self._device, ATTR_SYSTEM_MODE):
supported |= SUPPORT_OPERATION_MODE
return supported
@property
def is_fan_on(self):
"""Return true if fan is on."""

View File

@ -8,7 +8,9 @@ import asyncio
import voluptuous as vol
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice
from homeassistant.components.climate import (
PLATFORM_SCHEMA, ClimateDevice, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE)
from homeassistant.const import CONF_NAME, TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
@ -135,6 +137,14 @@ class KNXClimate(ClimateDevice):
self._unit_of_measurement = TEMP_CELSIUS
@property
def supported_features(self):
"""Return the list of supported features."""
support = SUPPORT_TARGET_TEMPERATURE
if self.device.supports_operation_mode:
support |= SUPPORT_OPERATION_MODE
return support
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
@asyncio.coroutine

View File

@ -7,7 +7,9 @@ https://home-assistant.io/components/maxcube/
import socket
import logging
from homeassistant.components.climate import ClimateDevice, STATE_AUTO
from homeassistant.components.climate import (
ClimateDevice, STATE_AUTO, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE)
from homeassistant.components.maxcube import MAXCUBE_HANDLE
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
@ -17,6 +19,8 @@ STATE_MANUAL = 'manual'
STATE_BOOST = 'boost'
STATE_VACATION = 'vacation'
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Iterate through all MAX! Devices and add thermostats."""
@ -47,6 +51,11 @@ class MaxCubeClimate(ClimateDevice):
self._rf_address = rf_address
self._cubehandle = hass.data[MAXCUBE_HANDLE]
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def should_poll(self):
"""Return the polling state."""

View File

@ -15,7 +15,9 @@ import homeassistant.components.mqtt as mqtt
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, ClimateDevice,
PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, STATE_AUTO,
ATTR_OPERATION_MODE)
ATTR_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE,
SUPPORT_AUX_HEAT)
from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME)
from homeassistant.components.mqtt import (CONF_QOS, CONF_RETAIN,
@ -483,3 +485,38 @@ class MqttClimate(ClimateDevice):
if self._topic[CONF_AUX_STATE_TOPIC] is None:
self._aux = False
self.async_schedule_update_ha_state()
@property
def supported_features(self):
"""Return the list of supported features."""
support = 0
if (self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None) or \
(self._topic[CONF_TEMPERATURE_COMMAND_TOPIC] is not None):
support |= SUPPORT_TARGET_TEMPERATURE
if (self._topic[CONF_MODE_COMMAND_TOPIC] is not None) or \
(self._topic[CONF_MODE_STATE_TOPIC] is not None):
support |= SUPPORT_OPERATION_MODE
if (self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None) or \
(self._topic[CONF_FAN_MODE_COMMAND_TOPIC] is not None):
support |= SUPPORT_FAN_MODE
if (self._topic[CONF_SWING_MODE_STATE_TOPIC] is not None) or \
(self._topic[CONF_SWING_MODE_COMMAND_TOPIC] is not None):
support |= SUPPORT_SWING_MODE
if (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) or \
(self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None):
support |= SUPPORT_AWAY_MODE
if (self._topic[CONF_HOLD_STATE_TOPIC] is not None) or \
(self._topic[CONF_HOLD_COMMAND_TOPIC] is not None):
support |= SUPPORT_HOLD_MODE
if (self._topic[CONF_AUX_STATE_TOPIC] is not None) or \
(self._topic[CONF_AUX_COMMAND_TOPIC] is not None):
support |= SUPPORT_AUX_HEAT
return support

View File

@ -7,7 +7,9 @@ https://home-assistant.io/components/climate.mysensors/
from homeassistant.components import mysensors
from homeassistant.components.climate import (
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO,
STATE_COOL, STATE_HEAT, STATE_OFF, ClimateDevice)
STATE_COOL, STATE_HEAT, STATE_OFF, ClimateDevice,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
DICT_HA_TO_MYS = {
@ -23,6 +25,10 @@ DICT_MYS_TO_HA = {
'Off': STATE_OFF,
}
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors climate."""
@ -33,6 +39,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
"""Representation of a MySensors HVAC."""
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def assumed_state(self):
"""Return True if unable to access real state of entity."""

View File

@ -12,7 +12,9 @@ from homeassistant.components.nest import DATA_NEST
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice,
PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE)
ATTR_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE)
from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT,
CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF, STATE_UNKNOWN)
@ -28,6 +30,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
STATE_ECO = 'eco'
STATE_HEAT_COOL = 'heat-cool'
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_OPERATION_MODE |
SUPPORT_AWAY_MODE | SUPPORT_FAN_MODE)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Nest thermostat."""
@ -87,6 +93,11 @@ class NestThermostat(ClimateDevice):
self._min_temperature = None
self._max_temperature = None
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def name(self):
"""Return the name of the nest, if any."""

View File

@ -10,7 +10,8 @@ import voluptuous as vol
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.components.climate import (
STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE)
from homeassistant.util import Throttle
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
@ -35,6 +36,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.All(cv.ensure_list, [cv.string]),
})
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_AWAY_MODE)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the NetAtmo Thermostat."""
@ -65,6 +69,11 @@ class NetatmoThermostat(ClimateDevice):
self._target_temperature = None
self._away = None
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def name(self):
"""Return the name of the sensor."""

View File

@ -14,7 +14,8 @@ import voluptuous as vol
# Import the device class from the component that you want to support
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE, ATTR_TEMPERATURE)
ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE, ATTR_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_AWAY_MODE)
from homeassistant.const import (CONF_HOST, CONF_USERNAME, CONF_PASSWORD,
CONF_PORT, TEMP_CELSIUS, CONF_NAME)
import homeassistant.helpers.config_validation as cv
@ -34,6 +35,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_AWAY_TEMP, default=14): vol.Coerce(float)
})
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the oemthermostat platform."""
@ -77,6 +80,11 @@ class ThermostatDevice(ClimateDevice):
self._temperature = None
self._setpoint = None
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def name(self):
"""Return the name of this Thermostat."""

View File

@ -8,7 +8,7 @@ import voluptuous as vol
from homeassistant.components.climate import (
PRECISION_TENTHS, STATE_COOL, STATE_HEAT, STATE_IDLE,
ClimateDevice, PLATFORM_SCHEMA)
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv
@ -46,6 +46,11 @@ class ProliphixThermostat(ClimateDevice):
self._pdp.update()
self._name = self._pdp.name
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE
@property
def should_poll(self):
"""Set up polling needed for thermostat."""

View File

@ -4,15 +4,18 @@ Support for Radio Thermostat wifi-enabled home thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.radiotherm/
"""
import asyncio
import datetime
import logging
import voluptuous as vol
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, STATE_OFF,
ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, STATE_ON, STATE_OFF,
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE)
from homeassistant.const import (
CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE, PRECISION_HALVES)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['radiotherm==1.3']
@ -29,15 +32,56 @@ CONF_AWAY_TEMPERATURE_COOL = 'away_temperature_cool'
DEFAULT_AWAY_TEMPERATURE_HEAT = 60
DEFAULT_AWAY_TEMPERATURE_COOL = 85
STATE_CIRCULATE = "circulate"
OPERATION_LIST = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF]
CT30_FAN_OPERATION_LIST = [STATE_ON, STATE_AUTO]
CT80_FAN_OPERATION_LIST = [STATE_ON, STATE_CIRCULATE, STATE_AUTO]
# Mappings from radiotherm json data codes to and from HASS state
# flags. CODE is the thermostat integer code and these map to and
# from HASS state flags.
# Programmed temperature mode of the thermostat.
CODE_TO_TEMP_MODE = {0: STATE_OFF, 1: STATE_HEAT, 2: STATE_COOL, 3: STATE_AUTO}
TEMP_MODE_TO_CODE = {v: k for k, v in CODE_TO_TEMP_MODE.items()}
# Programmed fan mode (circulate is supported by CT80 models)
CODE_TO_FAN_MODE = {0: STATE_AUTO, 1: STATE_CIRCULATE, 2: STATE_ON}
FAN_MODE_TO_CODE = {v: k for k, v in CODE_TO_FAN_MODE.items()}
# Active thermostat state (is it heating or cooling?). In the future
# this should probably made into heat and cool binary sensors.
CODE_TO_TEMP_STATE = {0: STATE_IDLE, 1: STATE_HEAT, 2: STATE_COOL}
# Active fan state. This is if the fan is actually on or not. In the
# future this should probably made into a binary sensor for the fan.
CODE_TO_FAN_STATE = {0: STATE_OFF, 1: STATE_ON}
def round_temp(temperature):
"""Round a temperature to the resolution of the thermostat.
RadioThermostats can handle 0.5 degree temps so the input
temperature is rounded to that value and returned.
"""
return round(temperature * 2.0) / 2.0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean,
vol.Optional(CONF_AWAY_TEMPERATURE_HEAT,
default=DEFAULT_AWAY_TEMPERATURE_HEAT): vol.Coerce(float),
default=DEFAULT_AWAY_TEMPERATURE_HEAT):
vol.All(vol.Coerce(float), round_temp),
vol.Optional(CONF_AWAY_TEMPERATURE_COOL,
default=DEFAULT_AWAY_TEMPERATURE_COOL): vol.Coerce(float),
default=DEFAULT_AWAY_TEMPERATURE_COOL):
vol.All(vol.Coerce(float), round_temp),
})
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_FAN_MODE | SUPPORT_AWAY_MODE)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Radio Thermostat."""
@ -77,19 +121,39 @@ class RadioThermostat(ClimateDevice):
def __init__(self, device, hold_temp, away_temps):
"""Initialize the thermostat."""
self.device = device
self.set_time()
self._target_temperature = None
self._current_temperature = None
self._current_operation = STATE_IDLE
self._name = None
self._fmode = None
self._fstate = None
self._tmode = None
self._tstate = None
self._hold_temp = hold_temp
self._hold_set = False
self._away = False
self._away_temps = away_temps
self._prev_temp = None
self._operation_list = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF]
# Fan circulate mode is only supported by the CT80 models.
import radiotherm
self._is_model_ct80 = isinstance(self.device,
radiotherm.thermostat.CT80)
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
# Set the time on the device. This shouldn't be in the
# constructor because it's a network call. We can't put it in
# update() because calling it will clear any temporary mode or
# temperature in the thermostat. So add it as a future job
# for the event loop to run.
self.hass.async_add_job(self.set_time)
@property
def name(self):
@ -101,6 +165,11 @@ class RadioThermostat(ClimateDevice):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT
@property
def precision(self):
"""Return the precision of the system."""
return PRECISION_HALVES
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
@ -109,6 +178,25 @@ class RadioThermostat(ClimateDevice):
ATTR_MODE: self._tmode,
}
@property
def fan_list(self):
"""List of available fan modes."""
if self._is_model_ct80:
return CT80_FAN_OPERATION_LIST
else:
return CT30_FAN_OPERATION_LIST
@property
def current_fan_mode(self):
"""Return whether the fan is on."""
return self._fmode
def set_fan_mode(self, fan):
"""Turn fan on/off."""
code = FAN_MODE_TO_CODE.get(fan, None)
if code is not None:
self.device.fmode = code
@property
def current_temperature(self):
"""Return the current temperature."""
@ -122,7 +210,7 @@ class RadioThermostat(ClimateDevice):
@property
def operation_list(self):
"""Return the operation modes list."""
return self._operation_list
return OPERATION_LIST
@property
def target_temperature(self):
@ -136,53 +224,48 @@ class RadioThermostat(ClimateDevice):
def update(self):
"""Update and validate the data from the thermostat."""
current_temp = self.device.temp['raw']
if current_temp == -1:
_LOGGER.error("Couldn't get valid temperature reading")
return
self._current_temperature = current_temp
self._name = self.device.name['raw']
try:
self._fmode = self.device.fmode['human']
except AttributeError:
_LOGGER.error("Couldn't get valid fan mode reading")
try:
self._tmode = self.device.tmode['human']
except AttributeError:
_LOGGER.error("Couldn't get valid thermostat mode reading")
try:
self._tstate = self.device.tstate['human']
except AttributeError:
_LOGGER.error("Couldn't get valid thermostat state reading")
# Radio thermostats are very slow, and sometimes don't respond
# very quickly. So we need to keep the number of calls to them
# to a bare minimum or we'll hit the HASS 10 sec warning. We
# have to make one call to /tstat to get temps but we'll try and
# keep the other calls to a minimum. Even with this, these
# thermostats tend to time out sometimes when they're actively
# heating or cooling.
if self._tmode == 'Cool':
target_temp = self.device.t_cool['raw']
if target_temp == -1:
_LOGGER.error("Couldn't get target reading")
# First time - get the name from the thermostat. This is
# normally set in the radio thermostat web app.
if self._name is None:
self._name = self.device.name['raw']
# Request the current state from the thermostat.
data = self.device.tstat['raw']
current_temp = data['temp']
if current_temp == -1:
_LOGGER.error('%s (%s) was busy (temp == -1)', self._name,
self.device.host)
return
self._target_temperature = target_temp
self._current_operation = STATE_COOL
elif self._tmode == 'Heat':
target_temp = self.device.t_heat['raw']
if target_temp == -1:
_LOGGER.error("Couldn't get valid target reading")
return
self._target_temperature = target_temp
self._current_operation = STATE_HEAT
elif self._tmode == 'Auto':
if self._tstate == 'Cool':
target_temp = self.device.t_cool['raw']
if target_temp == -1:
_LOGGER.error("Couldn't get valid target reading")
return
self._target_temperature = target_temp
elif self._tstate == 'Heat':
target_temp = self.device.t_heat['raw']
if target_temp == -1:
_LOGGER.error("Couldn't get valid target reading")
return
self._target_temperature = target_temp
self._current_operation = STATE_AUTO
# Map thermostat values into various STATE_ flags.
self._current_temperature = current_temp
self._fmode = CODE_TO_FAN_MODE[data['fmode']]
self._fstate = CODE_TO_FAN_STATE[data['fstate']]
self._tmode = CODE_TO_TEMP_MODE[data['tmode']]
self._tstate = CODE_TO_TEMP_STATE[data['tstate']]
self._current_operation = self._tmode
if self._tmode == STATE_COOL:
self._target_temperature = data['t_cool']
elif self._tmode == STATE_HEAT:
self._target_temperature = data['t_heat']
elif self._tmode == STATE_AUTO:
# This doesn't really work - tstate is only set if the HVAC is
# active. If it's idle, we don't know what to do with the target
# temperature.
if self._tstate == STATE_COOL:
self._target_temperature = data['t_cool']
elif self._tstate == STATE_HEAT:
self._target_temperature = data['t_heat']
else:
self._current_operation = STATE_IDLE
@ -191,23 +274,32 @@ class RadioThermostat(ClimateDevice):
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
if self._current_operation == STATE_COOL:
self.device.t_cool = round(temperature * 2.0) / 2.0
elif self._current_operation == STATE_HEAT:
self.device.t_heat = round(temperature * 2.0) / 2.0
elif self._current_operation == STATE_AUTO:
if self._tstate == 'Cool':
self.device.t_cool = round(temperature * 2.0) / 2.0
elif self._tstate == 'Heat':
self.device.t_heat = round(temperature * 2.0) / 2.0
temperature = round_temp(temperature)
if self._current_operation == STATE_COOL:
self.device.t_cool = temperature
elif self._current_operation == STATE_HEAT:
self.device.t_heat = temperature
elif self._current_operation == STATE_AUTO:
if self._tstate == STATE_COOL:
self.device.t_cool = temperature
elif self._tstate == STATE_HEAT:
self.device.t_heat = temperature
# Only change the hold if requested or if hold mode was turned
# on and we haven't set it yet.
if kwargs.get('hold_changed', False) or not self._hold_set:
if self._hold_temp or self._away:
self.device.hold = 1
self._hold_set = True
else:
self.device.hold = 0
def set_time(self):
"""Set device time."""
# Calling this clears any local temperature override and
# reverts to the scheduled temperature.
now = datetime.datetime.now()
self.device.time = {
'day': now.weekday(),
@ -217,14 +309,14 @@ class RadioThermostat(ClimateDevice):
def set_operation_mode(self, operation_mode):
"""Set operation mode (auto, cool, heat, off)."""
if operation_mode == STATE_OFF:
self.device.tmode = 0
elif operation_mode == STATE_AUTO:
self.device.tmode = 3
if operation_mode == STATE_OFF or operation_mode == STATE_AUTO:
self.device.tmode = TEMP_MODE_TO_CODE[operation_mode]
# Setting t_cool or t_heat automatically changes tmode.
elif operation_mode == STATE_COOL:
self.device.t_cool = round(self._target_temperature * 2.0) / 2.0
self.device.t_cool = self._target_temperature
elif operation_mode == STATE_HEAT:
self.device.t_heat = round(self._target_temperature * 2.0) / 2.0
self.device.t_heat = self._target_temperature
def turn_away_mode_on(self):
"""Turn away on.
@ -238,10 +330,11 @@ class RadioThermostat(ClimateDevice):
away_temp = self._away_temps[0]
elif self._current_operation == STATE_COOL:
away_temp = self._away_temps[1]
self._away = True
self.set_temperature(temperature=away_temp)
self.set_temperature(temperature=away_temp, hold_changed=True)
def turn_away_mode_off(self):
"""Turn away off."""
self._away = False
self.set_temperature(temperature=self._prev_temp)
self.set_temperature(temperature=self._prev_temp, hold_changed=True)

View File

@ -15,7 +15,10 @@ import voluptuous as vol
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_API_KEY, CONF_ID, TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.components.climate import (
ATTR_CURRENT_HUMIDITY, ClimateDevice, PLATFORM_SCHEMA)
ATTR_CURRENT_HUMIDITY, ClimateDevice, PLATFORM_SCHEMA,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_SWING_MODE,
SUPPORT_AUX_HEAT)
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -35,9 +38,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
_FETCH_FIELDS = ','.join([
'room{name}', 'measurements', 'remoteCapabilities',
'acState', 'connectionStatus{isAlive}'])
'acState', 'connectionStatus{isAlive}', 'temperatureUnit'])
_INITIAL_FETCH_FIELDS = 'id,' + _FETCH_FIELDS
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_FAN_MODE | SUPPORT_AWAY_MODE | SUPPORT_SWING_MODE |
SUPPORT_AUX_HEAT)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
@ -55,7 +62,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
devices.append(SensiboClimate(client, dev))
except (aiohttp.client_exceptions.ClientConnectorError,
asyncio.TimeoutError):
_LOGGER.exception('Failed to connct to Sensibo servers.')
_LOGGER.exception('Failed to connect to Sensibo servers.')
raise PlatformNotReady
if devices:
@ -63,7 +70,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class SensiboClimate(ClimateDevice):
"""Representation os a Sensibo device."""
"""Representation of a Sensibo device."""
def __init__(self, client, data):
"""Build SensiboClimate.
@ -75,6 +82,11 @@ class SensiboClimate(ClimateDevice):
self._id = data['id']
self._do_update(data)
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
def _do_update(self, data):
self._name = data['room']['name']
self._measurements = data['measurements']
@ -84,11 +96,16 @@ class SensiboClimate(ClimateDevice):
self._operations = sorted(capabilities['modes'].keys())
self._current_capabilities = capabilities[
'modes'][self.current_operation]
temperature_unit_key = self._ac_states['temperatureUnit']
self._temperature_unit = \
TEMP_CELSIUS if temperature_unit_key == 'C' else TEMP_FAHRENHEIT
temperature_unit_key = data.get('temperatureUnit') or \
self._ac_states.get('temperatureUnit')
if temperature_unit_key:
self._temperature_unit = TEMP_CELSIUS if \
temperature_unit_key == 'C' else TEMP_FAHRENHEIT
self._temperatures_list = self._current_capabilities[
'temperatures'][temperature_unit_key]['values']
'temperatures'].get(temperature_unit_key, {}).get('values', [])
else:
self._temperature_unit = self.unit_of_measurement
self._temperatures_list = []
@property
def device_state_attributes(self):
@ -108,7 +125,7 @@ class SensiboClimate(ClimateDevice):
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._ac_states['targetTemperature']
return self._ac_states.get('targetTemperature')
@property
def target_temperature_step(self):
@ -133,10 +150,8 @@ class SensiboClimate(ClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
# This field is not affected by temperature_unit.
# It is always in C / nativeTemperatureUnit
if 'nativeTemperatureUnit' not in self._ac_states:
return self._measurements['temperature']
# This field is not affected by temperatureUnit.
# It is always in C
return convert_temperature(
self._measurements['temperature'],
TEMP_CELSIUS,
@ -180,12 +195,14 @@ class SensiboClimate(ClimateDevice):
@property
def min_temp(self):
"""Return the minimum temperature."""
return self._temperatures_list[0]
return self._temperatures_list[0] \
if len(self._temperatures_list) else super.min_temp()
@property
def max_temp(self):
"""Return the maximum temperature."""
return self._temperatures_list[-1]
return self._temperatures_list[-1] \
if len(self._temperatures_list) else super.max_temp()
@asyncio.coroutine
def async_set_temperature(self, **kwargs):

View File

@ -7,7 +7,8 @@ https://home-assistant.io/components/climate.tado/
import logging
from homeassistant.const import TEMP_CELSIUS
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate import (
ClimateDevice, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.components.tado import DATA_TADO
@ -43,6 +44,8 @@ OPERATION_LIST = {
CONST_MODE_OFF: 'Off',
}
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Tado climate platform."""
@ -127,6 +130,11 @@ class TadoClimate(ClimateDevice):
self._current_operation = CONST_MODE_SMART_SCHEDULE
self._overlay_mode = CONST_MODE_SMART_SCHEDULE
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def name(self):
"""Return the name of the device."""

View File

@ -7,7 +7,9 @@ https://home-assistant.io/components/climate.tesla/
import logging
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.components.climate import ClimateDevice, ENTITY_ID_FORMAT
from homeassistant.components.climate import (
ClimateDevice, ENTITY_ID_FORMAT, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE)
from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN, TeslaDevice
from homeassistant.const import (
TEMP_FAHRENHEIT, TEMP_CELSIUS, ATTR_TEMPERATURE)
@ -18,6 +20,8 @@ DEPENDENCIES = ['tesla']
OPERATION_LIST = [STATE_ON, STATE_OFF]
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Tesla climate platform."""
@ -36,6 +40,11 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
self._target_temperature = None
self._temperature = None
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def current_operation(self):
"""Return current operation ie. On or Off."""

View File

@ -10,9 +10,11 @@ https://home-assistant.io/components/climate.toon/
import homeassistant.components.toon as toon_main
from homeassistant.components.climate import (
ClimateDevice, ATTR_TEMPERATURE, STATE_PERFORMANCE, STATE_HEAT, STATE_ECO,
STATE_COOL)
STATE_COOL, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
from homeassistant.const import TEMP_CELSIUS
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Toon thermostat."""
@ -38,6 +40,11 @@ class ThermostatDevice(ClimateDevice):
STATE_COOL,
]
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def name(self):
"""Name of this Thermostat."""

View File

@ -7,7 +7,9 @@ https://home-assistant.io/components/switch.vera/
import logging
from homeassistant.util import convert
from homeassistant.components.climate import ClimateDevice, ENTITY_ID_FORMAT
from homeassistant.components.climate import (
ClimateDevice, ENTITY_ID_FORMAT, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE)
from homeassistant.const import (
TEMP_FAHRENHEIT,
TEMP_CELSIUS,
@ -23,6 +25,9 @@ _LOGGER = logging.getLogger(__name__)
OPERATION_LIST = ['Heat', 'Cool', 'Auto Changeover', 'Off']
FAN_OPERATION_LIST = ['On', 'Auto', 'Cycle']
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_FAN_MODE)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Set up of Vera thermostats."""
@ -39,6 +44,11 @@ class VeraThermostat(VeraDevice, ClimateDevice):
VeraDevice.__init__(self, vera_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""

View File

@ -11,7 +11,10 @@ from homeassistant.components.climate import (
STATE_ECO, STATE_GAS, STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ELECTRIC,
STATE_FAN_ONLY, STATE_HEAT_PUMP, ATTR_TEMPERATURE, STATE_HIGH_DEMAND,
STATE_PERFORMANCE, ATTR_TARGET_TEMP_LOW, ATTR_CURRENT_HUMIDITY,
ATTR_TARGET_TEMP_HIGH, ClimateDevice)
ATTR_TARGET_TEMP_HIGH, ClimateDevice, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
SUPPORT_AUX_HEAT)
from homeassistant.components.wink import DOMAIN, WinkDevice
from homeassistant.const import (
STATE_ON, STATE_OFF, TEMP_CELSIUS, STATE_UNKNOWN, PRECISION_TENTHS)
@ -50,6 +53,17 @@ HA_STATE_TO_WINK = {
WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()}
SUPPORT_FLAGS_THERMOSTAT = (
SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_OPERATION_MODE |
SUPPORT_AWAY_MODE | SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT)
SUPPORT_FLAGS_AC = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_FAN_MODE)
SUPPORT_FLAGS_HEATER = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_AWAY_MODE)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Wink climate devices."""
@ -72,6 +86,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkThermostat(WinkDevice, ClimateDevice):
"""Representation of a Wink thermostat."""
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS_THERMOSTAT
@asyncio.coroutine
def async_added_to_hass(self):
"""Callback when entity is added to hass."""
@ -353,6 +372,11 @@ class WinkThermostat(WinkDevice, ClimateDevice):
class WinkAC(WinkDevice, ClimateDevice):
"""Representation of a Wink air conditioner."""
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS_AC
@property
def temperature_unit(self):
"""Return the unit of measurement."""
@ -471,6 +495,11 @@ class WinkAC(WinkDevice, ClimateDevice):
class WinkWaterHeater(WinkDevice, ClimateDevice):
"""Representation of a Wink water heater."""
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS_HEATER
@property
def temperature_unit(self):
"""Return the unit of measurement."""

View File

@ -7,8 +7,9 @@ https://home-assistant.io/components/climate.zwave/
# Because we do not compile openzwave on CI
# pylint: disable=import-error
import logging
from homeassistant.components.climate import DOMAIN
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate import (
DOMAIN, ClimateDevice, SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
from homeassistant.const import (
@ -70,6 +71,18 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self._zxt_120 = 1
self.update_properties()
@property
def supported_features(self):
"""Return the list of supported features."""
support = SUPPORT_TARGET_TEMPERATURE
if self.values.fan_mode:
support |= SUPPORT_FAN_MODE
if self.values.mode:
support |= SUPPORT_OPERATION_MODE
if self._zxt_120 == 1 and self.values.zxt_120_swing_mode:
support |= SUPPORT_SWING_MODE
return support
def update_properties(self):
"""Handle the data changes for node values."""
# Operation Mode

View File

@ -104,6 +104,11 @@ class Cloud:
self.region = info['region']
self.relayer = info['relayer']
@property
def cognito_email_based(self):
"""Return if cognito is email based."""
return not self.user_pool_id.endswith('GmV')
@property
def is_logged_in(self):
"""Get if cloud is logged in."""

View File

@ -69,6 +69,9 @@ def register(cloud, email, password):
cognito = _cognito(cloud)
try:
if cloud.cognito_email_based:
cognito.register(email, password, email=email)
else:
cognito.register(_generate_username(email), password, email=email)
except ClientError as err:
raise _map_aws_exception(err)
@ -80,7 +83,11 @@ def confirm_register(cloud, confirmation_code, email):
cognito = _cognito(cloud)
try:
cognito.confirm_sign_up(confirmation_code, _generate_username(email))
if cloud.cognito_email_based:
cognito.confirm_sign_up(confirmation_code, email)
else:
cognito.confirm_sign_up(confirmation_code,
_generate_username(email))
except ClientError as err:
raise _map_aws_exception(err)
@ -89,7 +96,11 @@ def forgot_password(cloud, email):
"""Initiate forgotten password flow."""
from botocore.exceptions import ClientError
if cloud.cognito_email_based:
cognito = _cognito(cloud, username=email)
else:
cognito = _cognito(cloud, username=_generate_username(email))
try:
cognito.initiate_forgot_password()
except ClientError as err:
@ -100,7 +111,11 @@ def confirm_forgot_password(cloud, confirmation_code, email, new_password):
"""Confirm forgotten password code and change password."""
from botocore.exceptions import ClientError
if cloud.cognito_email_based:
cognito = _cognito(cloud, username=email)
else:
cognito = _cognito(cloud, username=_generate_username(email))
try:
cognito.confirm_forgot_password(confirmation_code, new_password)
except ClientError as err:

View File

@ -65,12 +65,12 @@ class CloudLoginView(HomeAssistantView):
url = '/api/cloud/login'
name = 'api:cloud:login'
@asyncio.coroutine
@_handle_cloud_errors
@RequestDataValidator(vol.Schema({
vol.Required('email'): str,
vol.Required('password'): str,
}))
@asyncio.coroutine
def post(self, request, data):
"""Handle login request."""
hass = request.app['hass']
@ -92,8 +92,8 @@ class CloudLogoutView(HomeAssistantView):
url = '/api/cloud/logout'
name = 'api:cloud:logout'
@asyncio.coroutine
@_handle_cloud_errors
@asyncio.coroutine
def post(self, request):
"""Handle logout request."""
hass = request.app['hass']
@ -129,12 +129,12 @@ class CloudRegisterView(HomeAssistantView):
url = '/api/cloud/register'
name = 'api:cloud:register'
@asyncio.coroutine
@_handle_cloud_errors
@RequestDataValidator(vol.Schema({
vol.Required('email'): str,
vol.Required('password'): vol.All(str, vol.Length(min=6)),
}))
@asyncio.coroutine
def post(self, request, data):
"""Handle registration request."""
hass = request.app['hass']
@ -153,12 +153,12 @@ class CloudConfirmRegisterView(HomeAssistantView):
url = '/api/cloud/confirm_register'
name = 'api:cloud:confirm_register'
@asyncio.coroutine
@_handle_cloud_errors
@RequestDataValidator(vol.Schema({
vol.Required('confirmation_code'): str,
vol.Required('email'): str,
}))
@asyncio.coroutine
def post(self, request, data):
"""Handle registration confirmation request."""
hass = request.app['hass']
@ -178,11 +178,11 @@ class CloudForgotPasswordView(HomeAssistantView):
url = '/api/cloud/forgot_password'
name = 'api:cloud:forgot_password'
@asyncio.coroutine
@_handle_cloud_errors
@RequestDataValidator(vol.Schema({
vol.Required('email'): str,
}))
@asyncio.coroutine
def post(self, request, data):
"""Handle forgot password request."""
hass = request.app['hass']
@ -201,13 +201,13 @@ class CloudConfirmForgotPasswordView(HomeAssistantView):
url = '/api/cloud/confirm_forgot_password'
name = 'api:cloud:confirm_forgot_password'
@asyncio.coroutine
@_handle_cloud_errors
@RequestDataValidator(vol.Schema({
vol.Required('confirmation_code'): str,
vol.Required('email'): str,
vol.Required('new_password'): vol.All(str, vol.Length(min=6))
}))
@asyncio.coroutine
def post(self, request, data):
"""Handle forgot password confirm request."""
hass = request.app['hass']

View File

@ -59,13 +59,6 @@ class CloudIoT:
if self.state == STATE_CONNECTED:
raise RuntimeError('Already connected')
self.state = STATE_CONNECTING
self.close_requested = False
remove_hass_stop_listener = None
session = async_get_clientsession(self.cloud.hass)
client = None
disconnect_warn = None
@asyncio.coroutine
def _handle_hass_stop(event):
"""Handle Home Assistant shutting down."""
@ -73,6 +66,14 @@ class CloudIoT:
remove_hass_stop_listener = None
yield from self.disconnect()
self.state = STATE_CONNECTING
self.close_requested = False
remove_hass_stop_listener = hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, _handle_hass_stop)
session = async_get_clientsession(self.cloud.hass)
client = None
disconnect_warn = None
try:
yield from hass.async_add_job(auth_api.check_token, self.cloud)
@ -83,9 +84,6 @@ class CloudIoT:
})
self.tries = 0
remove_hass_stop_listener = hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, _handle_hass_stop)
_LOGGER.info('Connected')
self.state = STATE_CONNECTED

View File

@ -1,8 +1,8 @@
"""Provide configuration end points for Groups."""
import asyncio
from homeassistant.const import SERVICE_RELOAD
from homeassistant.components.config import EditKeyBasedConfigView
from homeassistant.components.group import GROUP_SCHEMA
from homeassistant.components.group import DOMAIN, GROUP_SCHEMA
import homeassistant.helpers.config_validation as cv
@ -12,7 +12,13 @@ CONFIG_PATH = 'groups.yaml'
@asyncio.coroutine
def async_setup(hass):
"""Set up the Group config API."""
@asyncio.coroutine
def hook(hass):
"""post_write_hook for Config View that reloads groups."""
yield from hass.services.async_call(DOMAIN, SERVICE_RELOAD)
hass.http.register_view(EditKeyBasedConfigView(
'group', 'config', CONFIG_PATH, cv.slug, GROUP_SCHEMA
'group', 'config', CONFIG_PATH, cv.slug, GROUP_SCHEMA,
post_write_hook=hook
))
return True

View File

@ -50,15 +50,19 @@ def async_request_config(
Will return an ID to be used for sequent calls.
"""
if link_name is not None and link_url is not None:
description += '\n\n[{}]({})'.format(link_name, link_url)
if description_image is not None:
description += '\n\n![Description image]({})'.format(description_image)
instance = hass.data.get(_KEY_INSTANCE)
if instance is None:
instance = hass.data[_KEY_INSTANCE] = Configurator(hass)
request_id = instance.async_request_config(
name, callback,
description, description_image, submit_caption,
fields, link_name, link_url, entity_picture)
name, callback, description, submit_caption, fields, entity_picture)
if DATA_REQUESTS not in hass.data:
hass.data[DATA_REQUESTS] = {}
@ -137,9 +141,8 @@ class Configurator(object):
@async_callback
def async_request_config(
self, name, callback,
description, description_image, submit_caption,
fields, link_name, link_url, entity_picture):
self, name, callback, description, submit_caption, fields,
entity_picture):
"""Set up a request for configuration."""
entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, name, hass=self.hass)
@ -161,10 +164,7 @@ class Configurator(object):
data.update({
key: value for key, value in [
(ATTR_DESCRIPTION, description),
(ATTR_DESCRIPTION_IMAGE, description_image),
(ATTR_SUBMIT_CAPTION, submit_caption),
(ATTR_LINK_NAME, link_name),
(ATTR_LINK_URL, link_url),
] if value is not None
})

View File

@ -14,7 +14,7 @@ import voluptuous as vol
from homeassistant import core
from homeassistant.loader import bind_hass
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, HTTP_BAD_REQUEST)
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
from homeassistant.helpers import intent, config_validation as cv
from homeassistant.components import http
@ -39,6 +39,10 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({
})
})}, extra=vol.ALLOW_EXTRA)
INTENT_TURN_ON = 'HassTurnOn'
INTENT_TURN_OFF = 'HassTurnOff'
REGEX_TYPE = type(re.compile(''))
_LOGGER = logging.getLogger(__name__)
@ -60,7 +64,11 @@ def async_register(hass, intent_type, utterances):
if conf is None:
conf = intents[intent_type] = []
conf.extend(_create_matcher(utterance) for utterance in utterances)
for utterance in utterances:
if isinstance(utterance, REGEX_TYPE):
conf.append(utterance)
else:
conf.append(_create_matcher(utterance))
@asyncio.coroutine
@ -93,6 +101,13 @@ def async_setup(hass, config):
hass.http.register_view(ConversationProcessView)
hass.helpers.intent.async_register(TurnOnIntent())
hass.helpers.intent.async_register(TurnOffIntent())
async_register(hass, INTENT_TURN_ON,
['Turn {name} on', 'Turn on {name}'])
async_register(hass, INTENT_TURN_OFF, [
'Turn {name} off', 'Turn off {name}'])
return True
@ -128,48 +143,84 @@ def _process(hass, text):
if not match:
continue
response = yield from intent.async_handle(
hass, DOMAIN, intent_type,
response = yield from hass.helpers.intent.async_handle(
DOMAIN, intent_type,
{key: {'value': value} for key, value
in match.groupdict().items()}, text)
return response
@core.callback
def _match_entity(hass, name):
"""Match a name to an entity."""
from fuzzywuzzy import process as fuzzyExtract
text = text.lower()
match = REGEX_TURN_COMMAND.match(text)
if not match:
_LOGGER.error("Unable to process: %s", text)
return None
name, command = match.groups()
entities = {state.entity_id: state.name for state
in hass.states.async_all()}
entity_ids = fuzzyExtract.extractOne(
entity_id = fuzzyExtract.extractOne(
name, entities, score_cutoff=65)[2]
return hass.states.get(entity_id) if entity_id else None
if not entity_ids:
_LOGGER.error(
"Could not find entity id %s from text %s", name, text)
class TurnOnIntent(intent.IntentHandler):
"""Handle turning item on intents."""
intent_type = INTENT_TURN_ON
slot_schema = {
'name': cv.string,
}
@asyncio.coroutine
def async_handle(self, intent_obj):
"""Handle turn on intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
name = slots['name']['value']
entity = _match_entity(hass, name)
if not entity:
_LOGGER.error("Could not find entity id for %s", name)
return None
if command == 'on':
yield from hass.services.async_call(
core.DOMAIN, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity_ids,
ATTR_ENTITY_ID: entity.entity_id,
}, blocking=True)
elif command == 'off':
response = intent_obj.create_response()
response.async_set_speech(
'Turned on {}'.format(entity.name))
return response
class TurnOffIntent(intent.IntentHandler):
"""Handle turning item off intents."""
intent_type = INTENT_TURN_OFF
slot_schema = {
'name': cv.string,
}
@asyncio.coroutine
def async_handle(self, intent_obj):
"""Handle turn off intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
name = slots['name']['value']
entity = _match_entity(hass, name)
if not entity:
_LOGGER.error("Could not find entity id for %s", name)
return None
yield from hass.services.async_call(
core.DOMAIN, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity_ids,
ATTR_ENTITY_ID: entity.entity_id,
}, blocking=True)
else:
_LOGGER.error('Got unsupported command %s from text %s',
command, text)
return None
response = intent_obj.create_response()
response.async_set_speech(
'Turned off {}'.format(entity.name))
return response
class ConversationProcessView(http.HomeAssistantView):
@ -178,23 +229,15 @@ class ConversationProcessView(http.HomeAssistantView):
url = '/api/conversation/process'
name = "api:conversation:process"
@http.RequestDataValidator(vol.Schema({
vol.Required('text'): str,
}))
@asyncio.coroutine
def post(self, request):
def post(self, request, data):
"""Send a request for processing."""
hass = request.app['hass']
try:
data = yield from request.json()
except ValueError:
return self.json_message('Invalid JSON specified',
HTTP_BAD_REQUEST)
text = data.get('text')
if text is None:
return self.json_message('Missing "text" key in JSON.',
HTTP_BAD_REQUEST)
intent_result = yield from _process(hass, text)
intent_result = yield from _process(hass, data['text'])
if intent_result is None:
intent_result = intent.IntentResponse()

View File

@ -0,0 +1,73 @@
"""
Support for Tahoma cover - shutters etc.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.tahoma/
"""
import logging
from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT
from homeassistant.components.tahoma import (
DOMAIN as TAHOMA_DOMAIN, TahomaDevice)
DEPENDENCIES = ['tahoma']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Tahoma covers."""
controller = hass.data[TAHOMA_DOMAIN]['controller']
devices = []
for device in hass.data[TAHOMA_DOMAIN]['devices']['cover']:
devices.append(TahomaCover(device, controller))
add_devices(devices, True)
class TahomaCover(TahomaDevice, CoverDevice):
"""Representation a Tahoma Cover."""
def __init__(self, tahoma_device, controller):
"""Initialize the Tahoma device."""
super().__init__(tahoma_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.unique_id)
def update(self):
"""Update method."""
self.controller.get_states([self.tahoma_device])
@property
def current_cover_position(self):
"""
Return current position of cover.
0 is closed, 100 is fully open.
"""
position = 100 - self.tahoma_device.active_states['core:ClosureState']
if position <= 5:
return 0
if position >= 95:
return 100
return position
def set_cover_position(self, position, **kwargs):
"""Move the cover to a specific position."""
self.apply_action('setPosition', 100 - position)
@property
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
return self.current_cover_position == 0
def open_cover(self, **kwargs):
"""Open the cover."""
self.apply_action('open')
def close_cover(self, **kwargs):
"""Close the cover."""
self.apply_action('close')
def stop_cover(self, **kwargs):
"""Stop the cover."""
self.apply_action('stopIdentify')

View File

@ -0,0 +1,134 @@
"""
Support for Unifi AP direct access.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.unifi_direct/
"""
import logging
import json
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME,
CONF_PORT)
REQUIREMENTS = ['pexpect==4.0.1']
_LOGGER = logging.getLogger(__name__)
DEFAULT_SSH_PORT = 22
UNIFI_COMMAND = 'mca-dump | tr -d "\n"'
UNIFI_SSID_TABLE = "vap_table"
UNIFI_CLIENT_TABLE = "sta_table"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_SSH_PORT): cv.port
})
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Validate the configuration and return a Unifi direct scanner."""
scanner = UnifiDeviceScanner(config[DOMAIN])
if not scanner.connected:
return False
return scanner
class UnifiDeviceScanner(DeviceScanner):
"""This class queries Unifi wireless access point."""
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.port = config[CONF_PORT]
self.ssh = None
self.connected = False
self.last_results = {}
self._connect()
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
result = _response_to_json(self._get_update())
if result:
self.last_results = result
return self.last_results.keys()
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
hostname = next((
value.get('hostname') for key, value in self.last_results.items()
if key.upper() == device.upper()), None)
if hostname is not None:
hostname = str(hostname)
return hostname
def _connect(self):
"""Connect to the Unifi AP SSH server."""
from pexpect import pxssh, exceptions
self.ssh = pxssh.pxssh()
try:
self.ssh.login(self.host, self.username,
password=self.password, port=self.port)
self.connected = True
except exceptions.EOF:
_LOGGER.error("Connection refused. SSH enabled?")
self._disconnect()
def _disconnect(self):
"""Disconnect the current SSH connection."""
# pylint: disable=broad-except
try:
self.ssh.logout()
except Exception:
pass
finally:
self.ssh = None
self.connected = False
def _get_update(self):
from pexpect import pxssh
try:
if not self.connected:
self._connect()
self.ssh.sendline(UNIFI_COMMAND)
self.ssh.prompt()
return self.ssh.before
except pxssh.ExceptionPxssh as err:
_LOGGER.error("Unexpected SSH error: %s", str(err))
self._disconnect()
return None
except AssertionError as err:
_LOGGER.error("Connection to AP unavailable: %s", str(err))
self._disconnect()
return None
def _response_to_json(response):
try:
json_response = json.loads(str(response)[31:-1].replace("\\", ""))
_LOGGER.debug(str(json_response))
ssid_table = json_response.get(UNIFI_SSID_TABLE)
active_clients = {}
for ssid in ssid_table:
client_table = ssid.get(UNIFI_CLIENT_TABLE)
for client in client_table:
active_clients[client.get("mac")] = client
return active_clients
except ValueError:
_LOGGER.error("Failed to decode response from AP.")
return {}

View File

@ -35,6 +35,7 @@ SERVICE_AXIS = 'axis'
SERVICE_APPLE_TV = 'apple_tv'
SERVICE_WINK = 'wink'
SERVICE_XIAOMI_GW = 'xiaomi_gw'
SERVICE_TELLDUSLIVE = 'tellstick'
SERVICE_HANDLERS = {
SERVICE_HASS_IOS_APP: ('ios', None),
@ -46,6 +47,7 @@ SERVICE_HANDLERS = {
SERVICE_APPLE_TV: ('apple_tv', None),
SERVICE_WINK: ('wink', None),
SERVICE_XIAOMI_GW: ('xiaomi_aqara', None),
SERVICE_TELLDUSLIVE: ('tellduslive', None),
'philips_hue': ('light', 'hue'),
'google_cast': ('media_player', 'cast'),
'panasonic_viera': ('media_player', 'panasonic_viera'),

View File

@ -0,0 +1,240 @@
"""
Support for Dominos Pizza ordering.
The Dominos Pizza component ceates a service which can be invoked to order
from their menu
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/dominos/.
"""
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components import http
from homeassistant.core import callback
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
# The domain of your component. Should be equal to the name of your component.
DOMAIN = 'dominos'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
ATTR_COUNTRY = 'country_code'
ATTR_FIRST_NAME = 'first_name'
ATTR_LAST_NAME = 'last_name'
ATTR_EMAIL = 'email'
ATTR_PHONE = 'phone'
ATTR_ADDRESS = 'address'
ATTR_ORDERS = 'orders'
ATTR_SHOW_MENU = 'show_menu'
ATTR_ORDER_ENTITY = 'order_entity_id'
ATTR_ORDER_NAME = 'name'
ATTR_ORDER_CODES = 'codes'
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10)
MIN_TIME_BETWEEN_STORE_UPDATES = timedelta(minutes=3330)
REQUIREMENTS = ['pizzapi==0.0.3']
DEPENDENCIES = ['http']
_ORDERS_SCHEMA = vol.Schema({
vol.Required(ATTR_ORDER_NAME): cv.string,
vol.Required(ATTR_ORDER_CODES): vol.All(cv.ensure_list, [cv.string]),
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(ATTR_COUNTRY): cv.string,
vol.Required(ATTR_FIRST_NAME): cv.string,
vol.Required(ATTR_LAST_NAME): cv.string,
vol.Required(ATTR_EMAIL): cv.string,
vol.Required(ATTR_PHONE): cv.string,
vol.Required(ATTR_ADDRESS): cv.string,
vol.Optional(ATTR_SHOW_MENU): cv.boolean,
vol.Optional(ATTR_ORDERS): vol.All(cv.ensure_list, [_ORDERS_SCHEMA]),
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up is called when Home Assistant is loading our component."""
dominos = Dominos(hass, config)
component = EntityComponent(_LOGGER, DOMAIN, hass)
hass.data[DOMAIN] = {}
entities = []
conf = config[DOMAIN]
hass.services.register(DOMAIN, 'order', dominos.handle_order)
if conf.get(ATTR_SHOW_MENU):
hass.http.register_view(DominosProductListView(dominos))
for order_info in conf.get(ATTR_ORDERS):
order = DominosOrder(order_info, dominos)
entities.append(order)
component.add_entities(entities)
# Return boolean to indicate that initialization was successfully.
return True
class Dominos():
"""Main Dominos service."""
def __init__(self, hass, config):
"""Set up main service."""
conf = config[DOMAIN]
from pizzapi import Address, Customer, Store
self.hass = hass
self.customer = Customer(
conf.get(ATTR_FIRST_NAME),
conf.get(ATTR_LAST_NAME),
conf.get(ATTR_EMAIL),
conf.get(ATTR_PHONE),
conf.get(ATTR_ADDRESS))
self.address = Address(
*self.customer.address.split(','),
country=conf.get(ATTR_COUNTRY))
self.country = conf.get(ATTR_COUNTRY)
self.closest_store = Store()
def handle_order(self, call):
"""Handle ordering pizza."""
entity_ids = call.data.get(ATTR_ORDER_ENTITY, None)
target_orders = [order for order in self.hass.data[DOMAIN]['entities']
if order.entity_id in entity_ids]
for order in target_orders:
order.place()
@Throttle(MIN_TIME_BETWEEN_STORE_UPDATES)
def update_closest_store(self):
"""Update the shared closest store (if open)."""
from pizzapi.address import StoreException
try:
self.closest_store = self.address.closest_store()
except StoreException:
self.closest_store = False
def get_menu(self):
"""Return the products from the closest stores menu."""
if self.closest_store is False:
_LOGGER.warning('Cannot get menu. Store may be closed')
return
menu = self.closest_store.get_menu()
product_entries = []
for product in menu.products:
item = {}
if isinstance(product.menu_data['Variants'], list):
variants = ', '.join(product.menu_data['Variants'])
else:
variants = product.menu_data['Variants']
item['name'] = product.name
item['variants'] = variants
product_entries.append(item)
return product_entries
class DominosProductListView(http.HomeAssistantView):
"""View to retrieve product list content."""
url = '/api/dominos'
name = "api:dominos"
def __init__(self, dominos):
"""Initialize suite view."""
self.dominos = dominos
@callback
def get(self, request):
"""Retrieve if API is running."""
return self.json(self.dominos.get_menu())
class DominosOrder(Entity):
"""Represents a Dominos order entity."""
def __init__(self, order_info, dominos):
"""Set up the entity."""
self._name = order_info['name']
self._product_codes = order_info['codes']
self._orderable = False
self.dominos = dominos
@property
def name(self):
"""Return the orders name."""
return self._name
@property
def product_codes(self):
"""Return the orders product codes."""
return self._product_codes
@property
def orderable(self):
"""Return the true if orderable."""
return self._orderable
@property
def state(self):
"""Return the state either closed, orderable or unorderable."""
if self.dominos.closest_store is False:
return 'closed'
else:
return 'orderable' if self._orderable else 'unorderable'
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update the order state and refreshes the store."""
from pizzapi.address import StoreException
try:
self.dominos.update_closest_store()
except StoreException:
self._orderable = False
return
try:
order = self.order()
order.pay_with()
self._orderable = True
except StoreException:
self._orderable = False
def order(self):
"""Create the order object."""
from pizzapi import Order
order = Order(
self.dominos.closest_store,
self.dominos.customer,
self.dominos.address,
self.dominos.country)
for code in self._product_codes:
order.add_item(code)
return order
def place(self):
"""Place the order."""
from pizzapi.address import StoreException
try:
order = self.order()
order.place()
except StoreException:
self._orderable = False
_LOGGER.warning(
'Attempted to order Dominos - Order invalid or store closed')

View File

@ -6,7 +6,7 @@ import voluptuous as vol
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['DoorBirdPy==0.0.4']
REQUIREMENTS = ['DoorBirdPy==0.1.0']
_LOGGER = logging.getLogger(__name__)

View File

@ -14,8 +14,9 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.const import CONF_API_KEY
from homeassistant.util import Throttle
from homeassistant.util.json import save_json
REQUIREMENTS = ['python-ecobee-api==0.0.10']
REQUIREMENTS = ['python-ecobee-api==0.0.12']
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
@ -81,6 +82,7 @@ def setup_ecobee(hass, network, config):
hass, 'climate', DOMAIN, {'hold_temp': hold_temp}, config)
discovery.load_platform(hass, 'sensor', DOMAIN, {}, config)
discovery.load_platform(hass, 'binary_sensor', DOMAIN, {}, config)
discovery.load_platform(hass, 'weather', DOMAIN, {}, config)
class EcobeeData(object):
@ -110,12 +112,10 @@ def setup(hass, config):
if 'ecobee' in _CONFIGURING:
return
from pyecobee import config_from_file
# Create ecobee.conf if it doesn't exist
if not os.path.isfile(hass.config.path(ECOBEE_CONFIG_FILE)):
jsonconfig = {"API_KEY": config[DOMAIN].get(CONF_API_KEY)}
config_from_file(hass.config.path(ECOBEE_CONFIG_FILE), jsonconfig)
save_json(hass.config.path(ECOBEE_CONFIG_FILE), jsonconfig)
NETWORK = EcobeeData(hass.config.path(ECOBEE_CONFIG_FILE))

View File

@ -5,7 +5,6 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/emulated_hue/
"""
import asyncio
import json
import logging
import voluptuous as vol
@ -16,8 +15,10 @@ from homeassistant.const import (
)
from homeassistant.components.http import REQUIREMENTS # NOQA
from homeassistant.components.http import HomeAssistantWSGI
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.deprecation import get_deprecated
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json
from .hue_api import (
HueUsernameView, HueAllLightsStateView, HueOneLightStateView,
HueOneLightChangeView)
@ -136,7 +137,7 @@ class Config(object):
self.host_ip_addr = conf.get(CONF_HOST_IP)
if self.host_ip_addr is None:
self.host_ip_addr = util.get_local_ip()
_LOGGER.warning(
_LOGGER.info(
"Listen IP address not specified, auto-detected address is %s",
self.host_ip_addr)
@ -144,7 +145,7 @@ class Config(object):
self.listen_port = conf.get(CONF_LISTEN_PORT)
if not isinstance(self.listen_port, int):
self.listen_port = DEFAULT_LISTEN_PORT
_LOGGER.warning(
_LOGGER.info(
"Listen port not specified, defaulting to %s",
self.listen_port)
@ -187,7 +188,7 @@ class Config(object):
return entity_id
if self.numbers is None:
self.numbers = self._load_numbers_json()
self.numbers = _load_json(self.hass.config.path(NUMBERS_FILE))
# Google Home
for number, ent_id in self.numbers.items():
@ -198,7 +199,7 @@ class Config(object):
if self.numbers:
number = str(max(int(k) for k in self.numbers) + 1)
self.numbers[number] = entity_id
self._save_numbers_json()
save_json(self.hass.config.path(NUMBERS_FILE), self.numbers)
return number
def number_to_entity_id(self, number):
@ -207,7 +208,7 @@ class Config(object):
return number
if self.numbers is None:
self.numbers = self._load_numbers_json()
self.numbers = _load_json(self.hass.config.path(NUMBERS_FILE))
# Google Home
assert isinstance(number, str)
@ -244,25 +245,11 @@ class Config(object):
return is_default_exposed or expose
def _load_numbers_json(self):
"""Set up helper method to load numbers json."""
try:
with open(self.hass.config.path(NUMBERS_FILE),
encoding='utf-8') as fil:
return json.loads(fil.read())
except (OSError, ValueError) as err:
# OSError if file not found or unaccessible/no permissions
# ValueError if could not parse JSON
if not isinstance(err, FileNotFoundError):
_LOGGER.warning("Failed to open %s: %s", NUMBERS_FILE, err)
return {}
def _save_numbers_json(self):
"""Set up helper method to save numbers json."""
def _load_json(filename):
"""Wrapper, because we actually want to handle invalid json."""
try:
with open(self.hass.config.path(NUMBERS_FILE), 'w',
encoding='utf-8') as fil:
fil.write(json.dumps(self.numbers))
except OSError as err:
# OSError if file write permissions
_LOGGER.warning("Failed to write %s: %s", NUMBERS_FILE, err)
return load_json(filename)
except HomeAssistantError:
pass
return {}

View File

@ -4,9 +4,7 @@ Support for Insteon fans via local hub control.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/fan.insteon_local/
"""
import json
import logging
import os
from datetime import timedelta
from homeassistant.components.fan import (
@ -14,6 +12,7 @@ from homeassistant.components.fan import (
SUPPORT_SET_SPEED, FanEntity)
from homeassistant.helpers.entity import ToggleEntity
import homeassistant.util as util
from homeassistant.util.json import load_json, save_json
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
@ -33,7 +32,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Insteon local fan platform."""
insteonhub = hass.data['insteon_local']
conf_fans = config_from_file(hass.config.path(INSTEON_LOCAL_FANS_CONF))
conf_fans = load_json(hass.config.path(INSTEON_LOCAL_FANS_CONF))
if conf_fans:
for device_id in conf_fans:
setup_fan(device_id, conf_fans[device_id], insteonhub, hass,
@ -88,44 +87,16 @@ def setup_fan(device_id, name, insteonhub, hass, add_devices_callback):
configurator.request_done(request_id)
_LOGGER.info("Device configuration done!")
conf_fans = config_from_file(hass.config.path(INSTEON_LOCAL_FANS_CONF))
conf_fans = load_json(hass.config.path(INSTEON_LOCAL_FANS_CONF))
if device_id not in conf_fans:
conf_fans[device_id] = name
if not config_from_file(
hass.config.path(INSTEON_LOCAL_FANS_CONF),
conf_fans):
_LOGGER.error("Failed to save configuration file")
save_json(hass.config.path(INSTEON_LOCAL_FANS_CONF), conf_fans)
device = insteonhub.fan(device_id)
add_devices_callback([InsteonLocalFanDevice(device, name)])
def config_from_file(filename, config=None):
"""Small configuration file management function."""
if config:
# We're writing configuration
try:
with open(filename, 'w') as fdesc:
fdesc.write(json.dumps(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 IOError as error:
_LOGGER.error("Reading configuration file failed: %s", error)
# This won't work yet
return False
else:
return {}
class InsteonLocalFanDevice(FanEntity):
"""An abstract Class for an Insteon node."""

View File

@ -31,7 +31,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
REQUIREMENTS = ['python-miio==0.3.1']
REQUIREMENTS = ['python-miio==0.3.2']
ATTR_TEMPERATURE = 'temperature'
ATTR_HUMIDITY = 'humidity'

View File

@ -23,7 +23,7 @@ from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED
from homeassistant.core import callback
from homeassistant.loader import bind_hass
REQUIREMENTS = ['home-assistant-frontend==20171121.0']
REQUIREMENTS = ['home-assistant-frontend==20171130.0', 'user-agents==1.1.0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log']
@ -32,6 +32,7 @@ URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html'
CONF_THEMES = 'themes'
CONF_EXTRA_HTML_URL = 'extra_html_url'
CONF_EXTRA_HTML_URL_ES5 = 'extra_html_url_es5'
CONF_FRONTEND_REPO = 'development_repo'
CONF_JS_VERSION = 'javascript_version'
JS_DEFAULT_OPTION = 'es5'
@ -63,6 +64,7 @@ DATA_FINALIZE_PANEL = 'frontend_finalize_panel'
DATA_PANELS = 'frontend_panels'
DATA_JS_VERSION = 'frontend_js_version'
DATA_EXTRA_HTML_URL = 'frontend_extra_html_url'
DATA_EXTRA_HTML_URL_ES5 = 'frontend_extra_html_url_es5'
DATA_THEMES = 'frontend_themes'
DATA_DEFAULT_THEME = 'frontend_default_theme'
DEFAULT_THEME = 'default'
@ -79,6 +81,8 @@ CONFIG_SCHEMA = vol.Schema({
}),
vol.Optional(CONF_EXTRA_HTML_URL):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_EXTRA_HTML_URL_ES5):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_JS_VERSION, default=JS_DEFAULT_OPTION):
vol.In(JS_OPTIONS)
}),
@ -269,11 +273,12 @@ def async_register_panel(hass, component_name, path, md5=None,
@bind_hass
@callback
def add_extra_html_url(hass, url):
def add_extra_html_url(hass, url, es5=False):
"""Register extra html url to load."""
url_set = hass.data.get(DATA_EXTRA_HTML_URL)
key = DATA_EXTRA_HTML_URL_ES5 if es5 else DATA_EXTRA_HTML_URL
url_set = hass.data.get(key)
if url_set is None:
url_set = hass.data[DATA_EXTRA_HTML_URL] = set()
url_set = hass.data[key] = set()
url_set.add(url)
@ -358,9 +363,13 @@ def async_setup(hass, config):
if DATA_EXTRA_HTML_URL not in hass.data:
hass.data[DATA_EXTRA_HTML_URL] = set()
if DATA_EXTRA_HTML_URL_ES5 not in hass.data:
hass.data[DATA_EXTRA_HTML_URL_ES5] = set()
for url in conf.get(CONF_EXTRA_HTML_URL, []):
add_extra_html_url(hass, url)
add_extra_html_url(hass, url, False)
for url in conf.get(CONF_EXTRA_HTML_URL_ES5, []):
add_extra_html_url(hass, url, True)
yield from async_setup_themes(hass, conf.get(CONF_THEMES))
@ -467,7 +476,8 @@ class IndexView(HomeAssistantView):
def get(self, request, extra=None):
"""Serve the index view."""
hass = request.app['hass']
latest = _is_latest(self.js_option, request)
latest = self.repo_path is not None or \
_is_latest(self.js_option, request)
if request.path == '/':
panel = 'states'
@ -481,21 +491,21 @@ class IndexView(HomeAssistantView):
else:
panel_url = hass.data[DATA_PANELS][panel].webcomponent_url_es5
no_auth = 'true'
no_auth = '1'
if hass.config.api.api_password and not is_trusted_ip(request):
# do not try to auto connect on load
no_auth = 'false'
no_auth = '0'
template = yield from hass.async_add_job(self.get_template, latest)
extra_key = DATA_EXTRA_HTML_URL if latest else DATA_EXTRA_HTML_URL_ES5
resp = template.render(
no_auth=no_auth,
panel_url=panel_url,
panels=hass.data[DATA_PANELS],
dev_mode=self.repo_path is not None,
theme_color=MANIFEST_JSON['theme_color'],
extra_urls=hass.data[DATA_EXTRA_HTML_URL],
latest=latest,
extra_urls=hass.data[extra_key],
)
return web.Response(text=resp, content_type='text/html')
@ -547,10 +557,36 @@ def _is_latest(js_option, request):
"""
if request is None:
return js_option == 'latest'
latest_in_query = 'latest' in request.query or (
# latest in query
if 'latest' in request.query or (
request.headers.get('Referer') and
'latest' in urlparse(request.headers['Referer']).query)
es5_in_query = 'es5' in request.query or (
'latest' in urlparse(request.headers['Referer']).query):
return True
# es5 in query
if 'es5' in request.query or (
request.headers.get('Referer') and
'es5' in urlparse(request.headers['Referer']).query)
return latest_in_query or (not es5_in_query and js_option == 'latest')
'es5' in urlparse(request.headers['Referer']).query):
return False
# non-auto option in config
if js_option != 'auto':
return js_option == 'latest'
from user_agents import parse
useragent = parse(request.headers.get('User-Agent'))
# on iOS every browser is a Safari which we support from version 10.
if useragent.os.family == 'iOS':
return useragent.os.version[0] >= 10
family_min_version = {
'Chrome': 50, # Probably can reduce this
'Firefox': 41, # Destructuring added in 41
'Opera': 40, # Probably can reduce this
'Edge': 14, # Maybe can reduce this
'Safari': 10, # many features not supported by 9
}
version = family_min_version.get(useragent.browser.family)
return version and useragent.browser.version[0] >= version

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -126,13 +126,15 @@ class GoogleAssistantView(HomeAssistantView):
commands = []
for command in requested_commands:
ent_ids = [ent.get('id') for ent in command.get('devices', [])]
execution = command.get('execution')[0]
for execution in command.get('execution'):
for eid in ent_ids:
success = False
domain = eid.split('.')[0]
(service, service_data) = determine_service(
eid, execution.get('command'), execution.get('params'),
hass.config.units)
if domain == "group":
domain = "homeassistant"
success = yield from hass.services.async_call(
domain, service, service_data, blocking=True)
result = {"ids": [eid], "states": {}}

View File

@ -39,7 +39,7 @@ _LOGGER = logging.getLogger(__name__)
# Mapping is [actions schema, primary trait, optional features]
# optional is SUPPORT_* = (trait, command)
MAPPING_COMPONENT = {
group.DOMAIN: [TYPE_SCENE, TRAIT_SCENE, None],
group.DOMAIN: [TYPE_SWITCH, TRAIT_ONOFF, None],
scene.DOMAIN: [TYPE_SCENE, TRAIT_SCENE, None],
script.DOMAIN: [TYPE_SCENE, TRAIT_SCENE, None],
switch.DOMAIN: [TYPE_SWITCH, TRAIT_ONOFF, None],
@ -94,6 +94,7 @@ def entity_to_device(entity: Entity, units: UnitSystem):
# use aliases
aliases = entity.attributes.get(CONF_ALIASES)
if aliases:
if isinstance(aliases, list):
device['name']['nicknames'] = aliases
else:
@ -124,14 +125,15 @@ def entity_to_device(entity: Entity, units: UnitSystem):
if entity.domain == climate.DOMAIN:
modes = ','.join(
m for m in entity.attributes.get(climate.ATTR_OPERATION_LIST, [])
if m in CLIMATE_SUPPORTED_MODES)
m.lower() for m in entity.attributes.get(
climate.ATTR_OPERATION_LIST, [])
if m.lower() in CLIMATE_SUPPORTED_MODES)
device['attributes'] = {
'availableThermostatModes': modes,
'thermostatTemperatureUnit':
'F' if units.temperature_unit == TEMP_FAHRENHEIT else 'C',
}
_LOGGER.debug('Thermostat attributes %s', device['attributes'])
return device
@ -143,7 +145,7 @@ def query_device(entity: Entity, units: UnitSystem) -> dict:
return None
return round(METRIC_SYSTEM.temperature(deg, units.temperature_unit), 1)
if entity.domain == climate.DOMAIN:
mode = entity.attributes.get(climate.ATTR_OPERATION_MODE)
mode = entity.attributes.get(climate.ATTR_OPERATION_MODE).lower()
if mode not in CLIMATE_SUPPORTED_MODES:
mode = 'on'
response = {
@ -218,6 +220,7 @@ def determine_service(
Attempt to return a tuple of service and service_data based on the entity
and action requested.
"""
_LOGGER.debug("Handling command %s with data %s", command, params)
domain = entity_id.split('.')[0]
service_data = {ATTR_ENTITY_ID: entity_id} # type: Dict[str, Any]
# special media_player handling
@ -260,7 +263,6 @@ def determine_service(
service_data['brightness'] = int(brightness / 100 * 255)
return (SERVICE_TURN_ON, service_data)
_LOGGER.debug("Handling command %s with data %s", command, params)
if command == COMMAND_COLOR:
color_data = params.get('color')
if color_data is not None:

View File

@ -0,0 +1,80 @@
"""
Support for the Hive devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/hive/
"""
import logging
import voluptuous as vol
from homeassistant.const import (CONF_PASSWORD, CONF_SCAN_INTERVAL,
CONF_USERNAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
REQUIREMENTS = ['pyhiveapi==0.2.5']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'hive'
DATA_HIVE = 'data_hive'
DEVICETYPES = {
'binary_sensor': 'device_list_binary_sensor',
'climate': 'device_list_climate',
'light': 'device_list_light',
'switch': 'device_list_plug',
'sensor': 'device_list_sensor',
}
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=2): cv.positive_int,
})
}, extra=vol.ALLOW_EXTRA)
class HiveSession:
"""Initiate Hive Session Class."""
entities = []
core = None
heating = None
hotwater = None
light = None
sensor = None
switch = None
def setup(hass, config):
"""Set up the Hive Component."""
from pyhiveapi import Pyhiveapi
session = HiveSession()
session.core = Pyhiveapi()
username = config[DOMAIN][CONF_USERNAME]
password = config[DOMAIN][CONF_PASSWORD]
update_interval = config[DOMAIN][CONF_SCAN_INTERVAL]
devicelist = session.core.initialise_api(username,
password,
update_interval)
if devicelist is None:
_LOGGER.error("Hive API initialization failed")
return False
session.sensor = Pyhiveapi.Sensor()
session.heating = Pyhiveapi.Heating()
session.hotwater = Pyhiveapi.Hotwater()
session.light = Pyhiveapi.Light()
session.switch = Pyhiveapi.Switch()
hass.data[DATA_HIVE] = session
for ha_type, hive_type in DEVICETYPES.items():
for key, devices in devicelist.items():
if key == hive_type:
for hivedevice in devices:
load_platform(hass, ha_type, DOMAIN, hivedevice, config)
return True

View File

@ -21,7 +21,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_interval
from homeassistant.config import load_yaml_config_file
REQUIREMENTS = ['pyhomematic==0.1.34']
REQUIREMENTS = ['pyhomematic==0.1.35']
DOMAIN = 'homematic'
@ -56,7 +56,7 @@ SERVICE_SET_DEV_VALUE = 'set_dev_value'
HM_DEVICE_TYPES = {
DISCOVER_SWITCHES: [
'Switch', 'SwitchPowermeter', 'IOSwitch', 'IPSwitch',
'Switch', 'SwitchPowermeter', 'IOSwitch', 'IPSwitch', 'RFSiren',
'IPSwitchPowermeter', 'KeyMatic', 'HMWIOSwitch', 'Rain', 'EcoLogic'],
DISCOVER_LIGHTS: ['Dimmer', 'KeyDimmer', 'IPKeyDimmer'],
DISCOVER_SENSORS: [
@ -66,7 +66,7 @@ HM_DEVICE_TYPES = {
'WeatherStation', 'ThermostatWall2', 'TemperatureDiffSensor',
'TemperatureSensor', 'CO2Sensor', 'IPSwitchPowermeter', 'HMWIOSwitch',
'FillingLevel', 'ValveDrive', 'EcoLogic', 'IPThermostatWall',
'IPSmoke'],
'IPSmoke', 'RFSiren', 'PresenceIP'],
DISCOVER_CLIMATE: [
'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2',
'MAXWallThermostat', 'IPThermostat', 'IPThermostatWall',
@ -74,7 +74,8 @@ HM_DEVICE_TYPES = {
DISCOVER_BINARY_SENSORS: [
'ShutterContact', 'Smoke', 'SmokeV2', 'Motion', 'MotionV2',
'RemoteMotion', 'WeatherSensor', 'TiltSensor', 'IPShutterContact',
'HMWIOSwitch', 'MaxShutterContact', 'Rain', 'WiredSensor'],
'HMWIOSwitch', 'MaxShutterContact', 'Rain', 'WiredSensor',
'PresenceIP'],
DISCOVER_COVER: ['Blind', 'KeyBlind']
}

View File

@ -4,6 +4,8 @@ A component which allows you to send data to an Influx database.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/influxdb/
"""
from datetime import timedelta
from functools import wraps, partial
import logging
import re
@ -16,6 +18,7 @@ from homeassistant.const import (
CONF_EXCLUDE, CONF_INCLUDE, CONF_DOMAINS, CONF_ENTITIES)
from homeassistant.helpers import state as state_helper
from homeassistant.helpers.entity_values import EntityValues
from homeassistant.util import utcnow
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['influxdb==4.1.1']
@ -30,6 +33,8 @@ CONF_TAGS_ATTRIBUTES = 'tags_attributes'
CONF_COMPONENT_CONFIG = 'component_config'
CONF_COMPONENT_CONFIG_GLOB = 'component_config_glob'
CONF_COMPONENT_CONFIG_DOMAIN = 'component_config_domain'
CONF_RETRY_COUNT = 'max_retries'
CONF_RETRY_QUEUE = 'retry_queue_limit'
DEFAULT_DATABASE = 'home_assistant'
DEFAULT_VERIFY_SSL = True
@ -58,6 +63,8 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_DB_NAME, default=DEFAULT_DATABASE): cv.string,
vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_SSL): cv.boolean,
vol.Optional(CONF_RETRY_COUNT, default=0): cv.positive_int,
vol.Optional(CONF_RETRY_QUEUE, default=20): cv.positive_int,
vol.Optional(CONF_DEFAULT_MEASUREMENT): cv.string,
vol.Optional(CONF_OVERRIDE_MEASUREMENT): cv.string,
vol.Optional(CONF_TAGS, default={}):
@ -119,6 +126,8 @@ def setup(hass, config):
conf[CONF_COMPONENT_CONFIG],
conf[CONF_COMPONENT_CONFIG_DOMAIN],
conf[CONF_COMPONENT_CONFIG_GLOB])
max_tries = conf.get(CONF_RETRY_COUNT)
queue_limit = conf.get(CONF_RETRY_QUEUE)
try:
influx = InfluxDBClient(**kwargs)
@ -145,12 +154,18 @@ def setup(hass, config):
(whitelist_d and state.domain not in whitelist_d):
return
_state = float(state_helper.state_as_number(state))
_state_key = "value"
except ValueError:
_state = state.state
_state_key = "state"
_include_state = _include_value = False
_state_as_value = float(state.state)
_include_value = True
except ValueError:
try:
_state_as_value = float(state_helper.state_as_number(state))
_include_state = _include_value = True
except ValueError:
_include_state = True
include_uom = True
measurement = component_config.get(state.entity_id).get(
CONF_OVERRIDE_MEASUREMENT)
if measurement in (None, ''):
@ -163,6 +178,8 @@ def setup(hass, config):
measurement = default_measurement
else:
measurement = state.entity_id
else:
include_uom = False
json_body = [
{
@ -173,15 +190,18 @@ def setup(hass, config):
},
'time': event.time_fired,
'fields': {
_state_key: _state,
}
}
]
if _include_state:
json_body[0]['fields']['state'] = state.state
if _include_value:
json_body[0]['fields']['value'] = _state_as_value
for key, value in state.attributes.items():
if key in tags_attributes:
json_body[0]['tags'][key] = value
elif key != 'unit_of_measurement':
elif key != 'unit_of_measurement' or include_uom:
# If the key is already in fields
if key in json_body[0]['fields']:
key = key + "_"
@ -202,6 +222,11 @@ def setup(hass, config):
json_body[0]['tags'].update(tags)
_write_data(json_body)
@RetryOnError(hass, retry_limit=max_tries, retry_delay=20,
queue_limit=queue_limit)
def _write_data(json_body):
try:
influx.write_points(json_body)
except exceptions.InfluxDBClientError:
@ -210,3 +235,79 @@ def setup(hass, config):
hass.bus.listen(EVENT_STATE_CHANGED, influx_event_listener)
return True
class RetryOnError(object):
"""A class for retrying a failed task a certain amount of tries.
This method decorator makes a method retrying on errors. If there was an
uncaught exception, it schedules another try to execute the task after a
retry delay. It does this up to the maximum number of retries.
It can be used for all probable "self-healing" problems like network
outages. The task will be rescheduled using HAs scheduling mechanism.
It takes a Hass instance, a maximum number of retries and a retry delay
in seconds as arguments.
The queue limit defines the maximum number of calls that are allowed to
be queued at a time. If this number is reached, every new call discards
an old one.
"""
def __init__(self, hass, retry_limit=0, retry_delay=20, queue_limit=100):
"""Initialize the decorator."""
self.hass = hass
self.retry_limit = retry_limit
self.retry_delay = timedelta(seconds=retry_delay)
self.queue_limit = queue_limit
def __call__(self, method):
"""Decorate the target method."""
from homeassistant.helpers.event import track_point_in_utc_time
@wraps(method)
def wrapper(*args, **kwargs):
"""Wrapped method."""
# pylint: disable=protected-access
if not hasattr(wrapper, "_retry_queue"):
wrapper._retry_queue = []
def scheduled(retry=0, untrack=None, event=None):
"""Call the target method.
It is called directly at the first time and then called
scheduled within the Hass mainloop.
"""
if untrack is not None:
wrapper._retry_queue.remove(untrack)
# pylint: disable=broad-except
try:
method(*args, **kwargs)
except Exception as ex:
if retry == self.retry_limit:
raise
if len(wrapper._retry_queue) >= self.queue_limit:
last = wrapper._retry_queue.pop(0)
if 'remove' in last:
func = last['remove']
func()
if 'exc' in last:
_LOGGER.error(
"Retry queue overflow, drop oldest entry: %s",
str(last['exc']))
target = utcnow() + self.retry_delay
tracking = {'target': target}
remove = track_point_in_utc_time(self.hass,
partial(scheduled,
retry + 1,
tracking),
target)
tracking['remove'] = remove
tracking["exc"] = ex
wrapper._retry_queue.append(tracking)
scheduled()
return wrapper

View File

@ -5,26 +5,21 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/ecosystem/ios/
"""
import asyncio
import os
import json
import logging
import datetime
import voluptuous as vol
# from voluptuous.humanize import humanize_error
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.core import callback
from homeassistant.components.http import HomeAssistantView
from homeassistant.remote import JSONEncoder
from homeassistant.const import (HTTP_INTERNAL_SERVER_ERROR,
HTTP_BAD_REQUEST)
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.util.json import load_json, save_json
_LOGGER = logging.getLogger(__name__)
@ -174,36 +169,6 @@ CONFIG_FILE = {ATTR_DEVICES: {}}
CONFIG_FILE_PATH = ""
def _load_config(filename):
"""Load configuration."""
if not os.path.isfile(filename):
return {}
try:
with open(filename, "r") as fdesc:
inp = fdesc.read()
# In case empty file
if not inp:
return {}
return json.loads(inp)
except (IOError, ValueError) as error:
_LOGGER.error("Reading config file %s failed: %s", filename, error)
return None
def _save_config(filename, config):
"""Save configuration."""
try:
with open(filename, 'w') as fdesc:
fdesc.write(json.dumps(config, cls=JSONEncoder))
except (IOError, TypeError) as error:
_LOGGER.error("Saving config file failed: %s", error)
return False
return True
def devices_with_push():
"""Return a dictionary of push enabled targets."""
targets = {}
@ -244,7 +209,7 @@ def setup(hass, config):
CONFIG_FILE_PATH = hass.config.path(CONFIGURATION_FILE)
CONFIG_FILE = _load_config(CONFIG_FILE_PATH)
CONFIG_FILE = load_json(CONFIG_FILE_PATH)
if CONFIG_FILE == {}:
CONFIG_FILE[ATTR_DEVICES] = {}
@ -305,7 +270,9 @@ class iOSIdentifyDeviceView(HomeAssistantView):
CONFIG_FILE[ATTR_DEVICES][name] = data
if not _save_config(CONFIG_FILE_PATH, CONFIG_FILE):
try:
save_json(CONFIG_FILE_PATH, CONFIG_FILE)
except HomeAssistantError:
return self.json_message("Error saving device.",
HTTP_INTERNAL_SERVER_ERROR)

View File

@ -37,19 +37,22 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
name = config.get(CONF_NAME)
add_devices([BlinktLight(blinkt, name)])
add_devices([
BlinktLight(blinkt, name, index) for index in range(blinkt.NUM_PIXELS)
])
class BlinktLight(Light):
"""Representation of a Blinkt! Light."""
def __init__(self, blinkt, name):
def __init__(self, blinkt, name, index):
"""Initialize a Blinkt Light.
Default brightness and white color.
"""
self._blinkt = blinkt
self._name = name
self._name = "{}_{}".format(name, index)
self._index = index
self._is_on = False
self._brightness = 255
self._rgb_color = [255, 255, 255]
@ -103,7 +106,8 @@ class BlinktLight(Light):
self._brightness = kwargs[ATTR_BRIGHTNESS]
percent_bright = (self._brightness / 255)
self._blinkt.set_all(self._rgb_color[0],
self._blinkt.set_pixel(self._index,
self._rgb_color[0],
self._rgb_color[1],
self._rgb_color[2],
percent_bright)
@ -115,7 +119,7 @@ class BlinktLight(Light):
def turn_off(self, **kwargs):
"""Instruct the light to turn off."""
self._blinkt.set_brightness(0)
self._blinkt.set_pixel(self._index, 0, 0, 0, 0)
self._blinkt.show()
self._is_on = False
self.schedule_update_ha_state()

View File

@ -0,0 +1,126 @@
"""
Support for the Hive devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.hive/
"""
from homeassistant.components.hive import DATA_HIVE
from homeassistant.components.light import (ATTR_BRIGHTNESS, ATTR_COLOR_TEMP,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR_TEMP,
SUPPORT_RGB_COLOR, Light)
DEPENDENCIES = ['hive']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Hive light devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
add_devices([HiveDeviceLight(session, discovery_info)])
class HiveDeviceLight(Light):
"""Hive Active Light Device."""
def __init__(self, hivesession, hivedevice):
"""Initialize the Light device."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.light_device_type = hivedevice["Hive_Light_DeviceType"]
self.session = hivesession
self.data_updatesource = '{}.{}'.format(self.device_type,
self.node_id)
self.session.entities.append(self)
def handle_update(self, updatesource):
"""Handle the new update request."""
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
self.schedule_update_ha_state()
@property
def name(self):
"""Return the display name of this light."""
return self.node_name
@property
def min_mireds(self):
"""Return the coldest color_temp that this light supports."""
if self.light_device_type == "tuneablelight" \
or self.light_device_type == "colourtuneablelight":
return self.session.light.get_min_colour_temp(self.node_id)
@property
def max_mireds(self):
"""Return the warmest color_temp that this light supports."""
if self.light_device_type == "tuneablelight" \
or self.light_device_type == "colourtuneablelight":
return self.session.light.get_max_colour_temp(self.node_id)
@property
def color_temp(self):
"""Return the CT color value in mireds."""
if self.light_device_type == "tuneablelight" \
or self.light_device_type == "colourtuneablelight":
return self.session.light.get_color_temp(self.node_id)
@property
def brightness(self):
"""Brightness of the light (an integer in the range 1-255)."""
return self.session.light.get_brightness(self.node_id)
@property
def is_on(self):
"""Return true if light is on."""
return self.session.light.get_state(self.node_id)
def turn_on(self, **kwargs):
"""Instruct the light to turn on."""
new_brightness = None
new_color_temp = None
if ATTR_BRIGHTNESS in kwargs:
tmp_new_brightness = kwargs.get(ATTR_BRIGHTNESS)
percentage_brightness = ((tmp_new_brightness / 255) * 100)
new_brightness = int(round(percentage_brightness / 5.0) * 5.0)
if new_brightness == 0:
new_brightness = 5
if ATTR_COLOR_TEMP in kwargs:
tmp_new_color_temp = kwargs.get(ATTR_COLOR_TEMP)
new_color_temp = round(1000000 / tmp_new_color_temp)
if new_brightness is not None:
self.session.light.set_brightness(self.node_id, new_brightness)
elif new_color_temp is not None:
self.session.light.set_colour_temp(self.node_id, new_color_temp)
else:
self.session.light.turn_on(self.node_id)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def turn_off(self):
"""Instruct the light to turn off."""
self.session.light.turn_off(self.node_id)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
@property
def supported_features(self):
"""Flag supported features."""
supported_features = None
if self.light_device_type == "warmwhitelight":
supported_features = SUPPORT_BRIGHTNESS
elif self.light_device_type == "tuneablelight":
supported_features = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP)
elif self.light_device_type == "colourtuneablelight":
supported_features = (
SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_RGB_COLOR)
return supported_features
def update(self):
"""Update all Node data frome Hive."""
self.session.core.update_data(self.node_id)

View File

@ -83,7 +83,12 @@ SCENE_SCHEMA = vol.Schema({
})
ATTR_IS_HUE_GROUP = "is_hue_group"
GROUP_NAME_ALL_HUE_LIGHTS = "All Hue Lights"
CONFIG_INSTRUCTIONS = """
Press the button on the bridge to register Philips Hue with Home Assistant.
![Location of button on bridge](/static/images/config_philips_hue.jpg)
"""
def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE):
@ -204,21 +209,6 @@ def setup_bridge(host, hass, add_devices, filename, allow_unreachable,
_LOGGER.error("Got unexpected result from Hue API")
return
if not skip_groups:
# Group ID 0 is a special group in the hub for all lights, but it
# is not returned by get_api() so explicitly get it and include it.
# See https://developers.meethue.com/documentation/
# groups-api#21_get_all_groups
_LOGGER.debug("Getting group 0 from bridge")
all_lights = bridge.get_group(0)
if not isinstance(all_lights, dict):
_LOGGER.error("Got unexpected result from Hue API for group 0")
return
# Hue hub returns name of group 0 as "Group 0", so rename
# for ease of use in HA.
all_lights['name'] = GROUP_NAME_ALL_HUE_LIGHTS
api_groups["0"] = all_lights
new_lights = []
api_name = api.get('config').get('name')
@ -298,10 +288,8 @@ def request_configuration(host, hass, add_devices, filename,
_CONFIGURING[host] = configurator.request_config(
"Philips Hue", hue_configuration_callback,
description=("Press the button on the bridge to register Philips Hue "
"with Home Assistant."),
description=CONFIG_INSTRUCTIONS,
entity_picture="/static/images/logo_philips_hue.png",
description_image="/static/images/config_philips_hue.jpg",
submit_caption="I have pressed the button"
)

View File

@ -4,14 +4,14 @@ Support for Insteon dimmers via local hub control.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/light.insteon_local/
"""
import json
import logging
import os
from datetime import timedelta
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
import homeassistant.util as util
from homeassistant.util.json import load_json, save_json
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
@ -31,7 +31,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Insteon local light platform."""
insteonhub = hass.data['insteon_local']
conf_lights = config_from_file(hass.config.path(INSTEON_LOCAL_LIGHTS_CONF))
conf_lights = load_json(hass.config.path(INSTEON_LOCAL_LIGHTS_CONF))
if conf_lights:
for device_id in conf_lights:
setup_light(device_id, conf_lights[device_id], insteonhub, hass,
@ -85,44 +85,16 @@ def setup_light(device_id, name, insteonhub, hass, add_devices_callback):
configurator.request_done(request_id)
_LOGGER.debug("Device configuration done")
conf_lights = config_from_file(hass.config.path(INSTEON_LOCAL_LIGHTS_CONF))
conf_lights = load_json(hass.config.path(INSTEON_LOCAL_LIGHTS_CONF))
if device_id not in conf_lights:
conf_lights[device_id] = name
if not config_from_file(
hass.config.path(INSTEON_LOCAL_LIGHTS_CONF),
conf_lights):
_LOGGER.error("Failed to save configuration file")
save_json(hass.config.path(INSTEON_LOCAL_LIGHTS_CONF), conf_lights)
device = insteonhub.dimmer(device_id)
add_devices_callback([InsteonLocalDimmerDevice(device, name)])
def config_from_file(filename, config=None):
"""Small configuration file management function."""
if config:
# We're writing configuration
try:
with open(filename, 'w') as fdesc:
fdesc.write(json.dumps(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 IOError as error:
_LOGGER.error("Reading configuration file failed: %s", error)
# This won't work yet
return False
else:
return {}
class InsteonLocalDimmerDevice(Light):
"""An abstract Class for an Insteon node."""

View File

@ -120,6 +120,7 @@ class TradfriGroup(Light):
@callback
def _async_start_observe(self, exc=None):
"""Start observation of light."""
# pylint: disable=import-error
from pytradfri.error import PyTradFriError
if exc:
_LOGGER.warning("Observation failed for %s", self._name,
@ -279,6 +280,7 @@ class TradfriLight(Light):
@callback
def _async_start_observe(self, exc=None):
"""Start observation of light."""
# pylint: disable=import-error
from pytradfri.error import PyTradFriError
if exc:
_LOGGER.warning("Observation failed for %s", self._name,

View File

@ -28,7 +28,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
REQUIREMENTS = ['python-miio==0.3.1']
REQUIREMENTS = ['python-miio==0.3.2']
# The light does not accept cct values < 1
CCT_MIN = 1

View File

@ -222,7 +222,8 @@ class YeelightLight(Light):
color_mode = int(color_mode)
if color_mode == 2: # color temperature
return color_temperature_to_rgb(self.color_temp)
temp_in_k = mired_to_kelvin(self._color_temp)
return color_temperature_to_rgb(temp_in_k)
if color_mode == 3: # hsv
hue = int(self._properties.get('hue'))
sat = int(self._properties.get('sat'))

View File

@ -16,7 +16,7 @@ from homeassistant.components.media_player import (
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['youtube_dl==2017.11.15']
REQUIREMENTS = ['youtube_dl==2017.11.26']
_LOGGER = logging.getLogger(__name__)

View File

@ -5,8 +5,6 @@ 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
import voluptuous as vol
@ -18,6 +16,7 @@ from homeassistant.components.media_player import (
PLATFORM_SCHEMA)
from homeassistant.const import (CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json
REQUIREMENTS = [
'https://github.com/aparraga/braviarc/archive/0.3.7.zip'
@ -61,38 +60,6 @@ def _get_mac_address(ip_address):
return None
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, discovery_info=None):
"""Set up the Sony Bravia TV platform."""
@ -102,7 +69,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
pin = None
bravia_config = _config_from_file(hass.config.path(BRAVIA_CONFIG_FILE))
bravia_config = load_json(hass.config.path(BRAVIA_CONFIG_FILE))
while bravia_config:
# Set up a configured TV
host_ip, host_config = bravia_config.popitem()
@ -136,10 +103,9 @@ def setup_bravia(config, pin, hass, add_devices):
_LOGGER.info("Discovery configuration done")
# Save config
if not _config_from_file(
save_json(
hass.config.path(BRAVIA_CONFIG_FILE),
{host: {'pin': pin, 'host': host, 'mac': mac}}):
_LOGGER.error("Failed to save configuration file")
{host: {'pin': pin, 'host': host, 'mac': mac}})
add_devices([BraviaTVDevice(host, mac, name, pin)])

View File

@ -20,7 +20,7 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['pychromecast==0.8.2']
REQUIREMENTS = ['pychromecast==1.0.2']
_LOGGER = logging.getLogger(__name__)

View File

@ -6,7 +6,6 @@ https://home-assistant.io/components/media_player.gpmdp/
"""
import logging
import json
import os
import socket
import time
@ -19,6 +18,7 @@ from homeassistant.components.media_player import (
from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF, CONF_HOST, CONF_PORT, CONF_NAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['websocket-client==0.37.0']
@ -86,8 +86,7 @@ def request_configuration(hass, config, url, add_devices_callback):
continue
setup_gpmdp(hass, config, code,
add_devices_callback)
_save_config(hass.config.path(GPMDP_CONFIG_FILE),
{"CODE": code})
save_json(hass.config.path(GPMDP_CONFIG_FILE), {"CODE": code})
websocket.send(json.dumps({'namespace': 'connect',
'method': 'connect',
'arguments': ['Home Assistant', code]}))
@ -122,39 +121,9 @@ def setup_gpmdp(hass, config, code, add_devices):
add_devices([GPMDP(name, url, code)], True)
def _load_config(filename):
"""Load configuration."""
if not os.path.isfile(filename):
return {}
try:
with open(filename, 'r') as fdesc:
inp = fdesc.read()
# In case empty file
if not inp:
return {}
return json.loads(inp)
except (IOError, ValueError) as error:
_LOGGER.error("Reading config file %s failed: %s", filename, error)
return None
def _save_config(filename, config):
"""Save configuration."""
try:
with open(filename, 'w') as fdesc:
fdesc.write(json.dumps(config, indent=4, sort_keys=True))
except (IOError, TypeError) as error:
_LOGGER.error("Saving configuration file failed: %s", error)
return False
return True
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the GPMDP platform."""
codeconfig = _load_config(hass.config.path(GPMDP_CONFIG_FILE))
codeconfig = load_json(hass.config.path(GPMDP_CONFIG_FILE))
if codeconfig:
code = codeconfig.get('CODE')
elif discovery_info is not None:

View File

@ -121,13 +121,12 @@ def setup_plexserver(
_LOGGER.info("Discovery configuration done")
# Save config
if not save_json(
save_json(
hass.config.path(PLEX_CONFIG_FILE), {host: {
'token': token,
'ssl': has_ssl,
'verify': verify_ssl,
}}):
_LOGGER.error("Failed to save configuration file")
}})
_LOGGER.info('Connected to: %s://%s', http_prefix, host)

View File

@ -18,7 +18,7 @@ from homeassistant.util import Throttle
REQUIREMENTS = [
'https://github.com/jabesq/netatmo-api-python/archive/'
'v0.9.2.zip#lnetatmo==0.9.2']
'v0.9.2.1.zip#lnetatmo==0.9.2.1']
_LOGGER = logging.getLogger(__name__)

View File

@ -20,35 +20,39 @@ DEPENDENCIES = ['lametric']
_LOGGER = logging.getLogger(__name__)
CONF_DISPLAY_TIME = "display_time"
CONF_LIFETIME = "lifetime"
CONF_CYCLES = "cycles"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ICON, default="i555"): cv.string,
vol.Optional(CONF_DISPLAY_TIME, default=10): cv.positive_int,
vol.Optional(CONF_LIFETIME, default=10): cv.positive_int,
vol.Optional(CONF_CYCLES, default=1): cv.positive_int,
})
# pylint: disable=unused-variable
def get_service(hass, config, discovery_info=None):
"""Get the Slack notification service."""
"""Get the LaMetric notification service."""
hlmn = hass.data.get(LAMETRIC_DOMAIN)
return LaMetricNotificationService(hlmn,
config[CONF_ICON],
config[CONF_DISPLAY_TIME] * 1000)
config[CONF_LIFETIME] * 1000,
config[CONF_CYCLES])
class LaMetricNotificationService(BaseNotificationService):
"""Implement the notification service for LaMetric."""
def __init__(self, hasslametricmanager, icon, display_time):
def __init__(self, hasslametricmanager, icon, lifetime, cycles):
"""Initialize the service."""
self.hasslametricmanager = hasslametricmanager
self._icon = icon
self._display_time = display_time
self._lifetime = lifetime
self._cycles = cycles
# pylint: disable=broad-except
def send_message(self, message="", **kwargs):
"""Send a message to some LaMetric deviced."""
"""Send a message to some LaMetric device."""
from lmnotify import SimpleFrame, Sound, Model
from oauthlib.oauth2 import TokenExpiredError
@ -56,9 +60,10 @@ class LaMetricNotificationService(BaseNotificationService):
data = kwargs.get(ATTR_DATA)
_LOGGER.debug("Targets/Data: %s/%s", targets, data)
icon = self._icon
cycles = self._cycles
sound = None
# User-defined icon?
# Additional data?
if data is not None:
if "icon" in data:
icon = data["icon"]
@ -73,12 +78,12 @@ class LaMetricNotificationService(BaseNotificationService):
data["sound"])
text_frame = SimpleFrame(icon, message)
_LOGGER.debug("Icon/Message/Duration: %s, %s, %d",
icon, message, self._display_time)
_LOGGER.debug("Icon/Message/Cycles/Lifetime: %s, %s, %d, %d",
icon, message, self._cycles, self._lifetime)
frames = [text_frame]
model = Model(frames=frames, sound=sound)
model = Model(frames=frames, cycles=cycles, sound=sound)
lmn = self.hasslametricmanager.manager
try:
devices = lmn.get_devices()
@ -89,5 +94,5 @@ class LaMetricNotificationService(BaseNotificationService):
for dev in devices:
if targets is None or dev["name"] in targets:
lmn.set_device(dev)
lmn.send_notification(model, lifetime=self._display_time)
lmn.send_notification(model, lifetime=self._lifetime)
_LOGGER.debug("Sent notification to LaMetric %s", dev["name"])

View File

@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.matrix/
"""
import logging
import json
import os
from urllib.parse import urlparse
@ -15,6 +14,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA,
BaseNotificationService)
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_VERIFY_SSL
from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['matrix-client==0.0.6']
@ -82,8 +82,7 @@ class MatrixNotificationService(BaseNotificationService):
return {}
try:
with open(self.session_filepath) as handle:
data = json.load(handle)
data = load_json(self.session_filepath)
auth_tokens = {}
for mx_id, token in data.items():
@ -101,16 +100,7 @@ class MatrixNotificationService(BaseNotificationService):
"""Store authentication token to session and persistent storage."""
self.auth_tokens[self.mx_id] = token
try:
with open(self.session_filepath, 'w') as handle:
handle.write(json.dumps(self.auth_tokens))
# Not saving the tokens to disk should not stop the client, we can just
# login using the password every time.
except (OSError, IOError, PermissionError) as ex:
_LOGGER.warning(
"Storing authentication tokens to file '%s' failed: %s",
self.session_filepath, str(ex))
save_json(self.session_filepath, self.auth_tokens)
def login(self):
"""Login to the matrix homeserver and return the client instance."""

View File

@ -10,8 +10,8 @@ import mimetypes
import voluptuous as vol
from homeassistant.components.notify import (
ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT,
PLATFORM_SCHEMA, BaseNotificationService)
ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA,
BaseNotificationService)
from homeassistant.const import CONF_API_KEY
import homeassistant.helpers.config_validation as cv
@ -85,12 +85,12 @@ class PushBulletNotificationService(BaseNotificationService):
refreshed = False
if not targets:
# Backward compatibility, notify all devices in own account
# Backward compatibility, notify all devices in own account.
self._push_data(message, title, data, self.pushbullet)
_LOGGER.info("Sent notification to self")
return
# Main loop, process all targets specified
# Main loop, process all targets specified.
for target in targets:
try:
ttype, tname = target.split('/', 1)
@ -98,15 +98,15 @@ class PushBulletNotificationService(BaseNotificationService):
_LOGGER.error("Invalid target syntax: %s", target)
continue
# Target is email, send directly, don't use a target object
# This also seems works to send to all devices in own account
# Target is email, send directly, don't use a target object.
# This also seems works to send to all devices in own account.
if ttype == 'email':
self._push_data(message, title, data, self.pushbullet, tname)
_LOGGER.info("Sent notification to email %s", tname)
continue
# Refresh if name not found. While awaiting periodic refresh
# solution in component, poor mans refresh ;)
# solution in component, poor mans refresh.
if ttype not in self.pbtargets:
_LOGGER.error("Invalid target syntax: %s", target)
continue
@ -128,6 +128,7 @@ class PushBulletNotificationService(BaseNotificationService):
continue
def _push_data(self, message, title, data, pusher, tname=None):
"""Helper for creating the message content."""
from pushbullet import PushError
if data is None:
data = {}
@ -142,17 +143,17 @@ class PushBulletNotificationService(BaseNotificationService):
pusher.push_link(title, url, body=message)
elif filepath:
if not self.hass.config.is_allowed_path(filepath):
_LOGGER.error("Filepath is not valid or allowed.")
_LOGGER.error("Filepath is not valid or allowed")
return
with open(filepath, "rb") as fileh:
with open(filepath, 'rb') as fileh:
filedata = self.pushbullet.upload_file(fileh, filepath)
if filedata.get('file_type') == 'application/x-empty':
_LOGGER.error("Can not send an empty file.")
_LOGGER.error("Can not send an empty file")
return
pusher.push_file(title=title, body=message, **filedata)
elif file_url:
if not file_url.startswith('http'):
_LOGGER.error("Url should start with http or https.")
_LOGGER.error("URL should start with http or https")
return
pusher.push_file(title=title, body=message, file_name=file_url,
file_url=file_url,

View File

@ -12,14 +12,14 @@ from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from requests.exceptions import HTTPError, ConnectTimeout
REQUIREMENTS = ['ring_doorbell==0.1.7']
REQUIREMENTS = ['ring_doorbell==0.1.8']
_LOGGER = logging.getLogger(__name__)
CONF_ATTRIBUTION = "Data provided by Ring.com"
NOTIFICATION_ID = 'ring_notification'
NOTIFICATION_TITLE = 'Ring Sensor Setup'
NOTIFICATION_TITLE = 'Ring Setup'
DATA_RING = 'ring'
DOMAIN = 'ring'

View File

@ -8,9 +8,9 @@ import asyncio
from datetime import timedelta
import logging
from homeassistant.components.amcrest import SENSORS
from homeassistant.components.amcrest import DATA_AMCREST, SENSORS
from homeassistant.helpers.entity import Entity
from homeassistant.const import STATE_UNKNOWN
from homeassistant.const import CONF_NAME, CONF_SENSORS, STATE_UNKNOWN
DEPENDENCIES = ['amcrest']
@ -25,13 +25,14 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
if discovery_info is None:
return
device = discovery_info['device']
name = discovery_info['name']
sensors = discovery_info['sensors']
device_name = discovery_info[CONF_NAME]
sensors = discovery_info[CONF_SENSORS]
amcrest = hass.data[DATA_AMCREST][device_name]
amcrest_sensors = []
for sensor_type in sensors:
amcrest_sensors.append(AmcrestSensor(name, device, sensor_type))
amcrest_sensors.append(
AmcrestSensor(amcrest.name, amcrest.device, sensor_type))
async_add_devices(amcrest_sensors, True)
return True

View File

@ -68,10 +68,15 @@ class CurrencylayerSensor(Entity):
self._base = base
self._state = None
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._quote
@property
def name(self):
"""Return the name of the sensor."""
return '{} {}'.format(self._base, self._quote)
return self._base
@property
def icon(self):

View File

@ -4,17 +4,17 @@ 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
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.components.sensor import PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['schiene==0.18']
REQUIREMENTS = ['schiene==0.19']
_LOGGER = logging.getLogger(__name__)

View File

@ -6,16 +6,17 @@ https://home-assistant.io/components/sensor.fastdotcom/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.util.dt as dt_util
from homeassistant.components.sensor import DOMAIN, PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import (DOMAIN, PLATFORM_SCHEMA)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_change
from homeassistant.helpers.restore_state import async_get_last_state
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['fastdotcom==0.0.1']
REQUIREMENTS = ['fastdotcom==0.0.3']
_LOGGER = logging.getLogger(__name__)

View File

@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.fitbit/
"""
import os
import json
import logging
import datetime
import time
@ -19,6 +18,8 @@ from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.icon import icon_for_battery_level
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['fitbit==0.3.0']
@ -147,31 +148,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def config_from_file(filename, config=None):
"""Small configuration file management function."""
if config:
# We"re writing configuration
try:
with open(filename, 'w') as fdesc:
fdesc.write(json.dumps(config))
except IOError as error:
_LOGGER.error("Saving config file failed: %s", error)
return False
return config
else:
# We"re reading config
if os.path.isfile(filename):
try:
with open(filename, 'r') as fdesc:
return json.loads(fdesc.read())
except IOError as error:
_LOGGER.error("Reading config file failed: %s", error)
# This won"t work yet
return False
else:
return {}
def request_app_setup(hass, config, add_devices, config_path,
discovery_info=None):
"""Assist user with configuring the Fitbit dev application."""
@ -182,7 +158,7 @@ def request_app_setup(hass, config, add_devices, config_path,
"""Handle configuration updates."""
config_path = hass.config.path(FITBIT_CONFIG_FILE)
if os.path.isfile(config_path):
config_file = config_from_file(config_path)
config_file = load_json(config_path)
if config_file == DEFAULT_CONFIG:
error_msg = ("You didn't correctly modify fitbit.conf",
" please try again")
@ -242,13 +218,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Fitbit sensor."""
config_path = hass.config.path(FITBIT_CONFIG_FILE)
if os.path.isfile(config_path):
config_file = config_from_file(config_path)
config_file = load_json(config_path)
if config_file == DEFAULT_CONFIG:
request_app_setup(
hass, config, add_devices, config_path, discovery_info=None)
return False
else:
config_file = config_from_file(config_path, DEFAULT_CONFIG)
config_file = save_json(config_path, DEFAULT_CONFIG)
request_app_setup(
hass, config, add_devices, config_path, discovery_info=None)
return False
@ -384,9 +360,7 @@ class FitbitAuthCallbackView(HomeAssistantView):
ATTR_CLIENT_SECRET: self.oauth.client_secret,
ATTR_LAST_SAVED_AT: int(time.time())
}
if not config_from_file(hass.config.path(FITBIT_CONFIG_FILE),
config_contents):
_LOGGER.error("Failed to save config file")
save_json(hass.config.path(FITBIT_CONFIG_FILE), config_contents)
hass.async_add_job(setup_platform, hass, self.config, self.add_devices)
@ -513,5 +487,4 @@ class FitbitSensor(Entity):
ATTR_CLIENT_SECRET: self.client.client.client_secret,
ATTR_LAST_SAVED_AT: int(time.time())
}
if not config_from_file(self.config_path, config_contents):
_LOGGER.error("Failed to save config file")
save_json(self.config_path, config_contents)

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