Merge pull request #8468 from home-assistant/release-0-49

0.49
This commit is contained in:
Paulus Schoutsen 2017-07-15 21:05:07 -07:00 committed by GitHub
commit dc08852fc2
310 changed files with 5823 additions and 2161 deletions

View File

@ -11,9 +11,15 @@ omit =
homeassistant/components/alarmdecoder.py
homeassistant/components/*/alarmdecoder.py
homeassistant/components/amcrest.py
homeassistant/components/*/amcrest.py
homeassistant/components/apcupsd.py
homeassistant/components/*/apcupsd.py
homeassistant/components/apple_tv.py
homeassistant/components/*/apple_tv.py
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
@ -89,6 +95,9 @@ omit =
homeassistant/components/knx.py
homeassistant/components/*/knx.py
homeassistant/components/lametric.py
homeassistant/components/*/lametric.py
homeassistant/components/lutron.py
homeassistant/components/*/lutron.py
@ -164,6 +173,9 @@ omit =
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twilio_call.py
homeassistant/components/velux.py
homeassistant/components/*/velux.py
homeassistant/components/vera.py
homeassistant/components/*/vera.py
@ -211,7 +223,6 @@ omit =
homeassistant/components/binary_sensor/rest.py
homeassistant/components/binary_sensor/tapsaff.py
homeassistant/components/browser.py
homeassistant/components/camera/amcrest.py
homeassistant/components/camera/bloomsky.py
homeassistant/components/camera/ffmpeg.py
homeassistant/components/camera/foscam.py
@ -301,8 +312,8 @@ omit =
homeassistant/components/lock/nuki.py
homeassistant/components/lock/lockitron.py
homeassistant/components/lock/sesame.py
homeassistant/components/media_extractor.py
homeassistant/components/media_player/anthemav.py
homeassistant/components/media_player/apple_tv.py
homeassistant/components/media_player/aquostv.py
homeassistant/components/media_player/braviatv.py
homeassistant/components/media_player/cast.py
@ -339,6 +350,7 @@ omit =
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/spotify.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/vizio.py
homeassistant/components/media_player/vlc.py
homeassistant/components/media_player/volumio.py
homeassistant/components/media_player/yamaha.py
@ -375,11 +387,11 @@ omit =
homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py
homeassistant/components/nuimo_controller.py
homeassistant/components/prometheus.py
homeassistant/components/remote/harmony.py
homeassistant/components/remote/itach.py
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/scene/lifx_cloud.py
homeassistant/components/sensor/amcrest.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/arwn.py
homeassistant/components/sensor/bbox.py
@ -390,7 +402,7 @@ omit =
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/buienradar.py
homeassistant/components/sensor/dublin_bus_transport.py
homeassistant/components/sensor/citybikes.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/cert_expiry.py
homeassistant/components/sensor/comed_hourly_pricing.py
@ -404,6 +416,7 @@ omit =
homeassistant/components/sensor/dnsip.py
homeassistant/components/sensor/dovado.py
homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/dublin_bus_transport.py
homeassistant/components/sensor/ebox.py
homeassistant/components/sensor/eddystone_temperature.py
homeassistant/components/sensor/eliqonline.py
@ -450,6 +463,7 @@ omit =
homeassistant/components/sensor/openexchangerates.py
homeassistant/components/sensor/opensky.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/otp.py
homeassistant/components/sensor/pi_hole.py
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/pocketcasts.py
@ -511,6 +525,7 @@ omit =
homeassistant/components/switch/tplink.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wake_on_lan.py
homeassistant/components/switch/xiaomi_vacuum.py
homeassistant/components/telegram_bot/*
homeassistant/components/thingspeak.py
homeassistant/components/tts/amazon_polly.py

View File

@ -16,8 +16,8 @@ matrix:
env: TOXENV=py35
- python: "3.6"
env: TOXENV=py36
- python: "3.6-dev"
env: TOXENV=py36
# - python: "3.6-dev"
# env: TOXENV=py36
- python: "3.4.2"
env: TOXENV=requirements
# allow_failures:

41
CODEOWNERS Normal file
View File

@ -0,0 +1,41 @@
# People marked here will be automatically requested for a review
# when the code that they own is touched.
# https://github.com/blog/2392-introducing-code-owners
setup.py @home-assistant/core
homeassistant/*.py @home-assistant/core
homeassistant/helpers/* @home-assistant/core
homeassistant/util/* @home-assistant/core
homeassistant/components/api.py @home-assistant/core
homeassistant/components/automation/* @home-assistant/core
homeassistant/components/configurator.py @home-assistant/core
homeassistant/components/group.py @home-assistant/core
homeassistant/components/history.py @home-assistant/core
homeassistant/components/http/* @home-assistant/core
homeassistant/components/input_*.py @home-assistant/core
homeassistant/components/introduction.py @home-assistant/core
homeassistant/components/logger.py @home-assistant/core
homeassistant/components/mqtt/* @home-assistant/core
homeassistant/components/panel_custom.py @home-assistant/core
homeassistant/components/panel_iframe.py @home-assistant/core
homeassistant/components/persistent_notification.py @home-assistant/core
homeassistant/components/scene/__init__.py @home-assistant/core
homeassistant/components/scene/hass.py @home-assistant/core
homeassistant/components/script.py @home-assistant/core
homeassistant/components/shell_command.py @home-assistant/core
homeassistant/components/sun.py @home-assistant/core
homeassistant/components/updater.py @home-assistant/core
homeassistant/components/weblink.py @home-assistant/core
homeassistant/components/websocket_api.py @home-assistant/core
homeassistant/components/zone.py @home-assistant/core
Dockerfile @home-assistant/docker
virtualization/Docker/* @home-assistant/docker
homeassistant/components/zwave/* @home-assistant/z-wave
homeassistant/components/*/zwave.py @home-assistant/z-wave
# Indiviudal components
homeassistant/components/cover/template.py @PhracturedBlue
homeassistant/components/device_tracker/automatic.py @armills
homeassistant/components/media_player/kodi.py @armills

View File

@ -229,8 +229,8 @@ def cmdline() -> List[str]:
os.environ['PYTHONPATH'] = os.path.dirname(modulepath)
return [sys.executable] + [arg for arg in sys.argv if
arg != '--daemon']
else:
return [arg for arg in sys.argv if arg != '--daemon']
return [arg for arg in sys.argv if arg != '--daemon']
def setup_and_run_hass(config_dir: str,

View File

@ -39,19 +39,19 @@ def is_on(hass, entity_id=None):
else:
entity_ids = hass.states.entity_ids()
for entity_id in entity_ids:
domain = ha.split_entity_id(entity_id)[0]
for ent_id in entity_ids:
domain = ha.split_entity_id(ent_id)[0]
module = get_component(domain)
try:
if module.is_on(hass, entity_id):
if module.is_on(hass, ent_id):
return True
except AttributeError:
# module is None or method is_on does not exist
_LOGGER.exception("Failed to call %s.is_on for %s",
module, entity_id)
module, ent_id)
return False

View File

@ -92,8 +92,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
return STATE_ALARM_ARMED_HOME
elif self._alarm.state.lower() == 'armed away':
return STATE_ALARM_ARMED_AWAY
else:
return STATE_UNKNOWN
return STATE_UNKNOWN
@asyncio.coroutine
def async_alarm_disarm(self, code=None):

View File

@ -113,8 +113,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
"""Regex for code format or None if no code is required."""
if self._code:
return None
else:
return '^\\d{4,6}$'
return '^\\d{4,6}$'
@property
def state(self):

View File

@ -99,8 +99,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
self._trigger_time) < dt_util.utcnow():
if self._disarm_after_trigger:
return STATE_ALARM_DISARMED
else:
return self._pre_trigger_state
return self._pre_trigger_state
return self._state

View File

@ -80,8 +80,7 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
"""Return the name of the device."""
if self._name is not None:
return self._name
else:
return 'Alarm {}'.format(self.simplisafe.location_id())
return 'Alarm {}'.format(self.simplisafe.location_id())
@property
def code_format(self):

View File

@ -16,7 +16,7 @@ from homeassistant.const import (
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN,
CONF_NAME)
REQUIREMENTS = ['total_connect_client==0.7']
REQUIREMENTS = ['total_connect_client==0.11']
_LOGGER = logging.getLogger(__name__)

View File

@ -39,10 +39,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
"""Representation a Wink camera alarm."""
def __init__(self, wink, hass):
"""Initialize the Wink alarm."""
super().__init__(wink, hass)
@asyncio.coroutine
def async_added_to_hass(self):
"""Callback when entity is added to hass."""

View File

@ -271,14 +271,14 @@ class Alert(ToggleEntity):
'notify', target, {'message': self._done_message})
@asyncio.coroutine
def async_turn_on(self):
def async_turn_on(self, **kwargs):
"""Async Unacknowledge alert."""
_LOGGER.debug("Reset Alert: %s", self._name)
self._ack = False
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_turn_off(self):
def async_turn_off(self, **kwargs):
"""Async Acknowledge alert."""
_LOGGER.debug("Acknowledged Alert: %s", self._name)
self._ack = True

View File

@ -0,0 +1,149 @@
"""
This component provides basic support for Amcrest IP cameras.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/amcrest/
"""
import logging
from datetime import timedelta
import aiohttp
import voluptuous as vol
from requests.exceptions import HTTPError, ConnectTimeout
import homeassistant.loader as loader
from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD,
CONF_SENSORS, CONF_SCAN_INTERVAL, HTTP_BASIC_AUTHENTICATION)
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['amcrest==1.2.0']
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
CONF_AUTHENTICATION = 'authentication'
CONF_RESOLUTION = 'resolution'
CONF_STREAM_SOURCE = 'stream_source'
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
DEFAULT_NAME = 'Amcrest Camera'
DEFAULT_PORT = 80
DEFAULT_RESOLUTION = 'high'
DEFAULT_STREAM_SOURCE = 'snapshot'
TIMEOUT = 10
DATA_AMCREST = 'amcrest'
DOMAIN = 'amcrest'
NOTIFICATION_ID = 'amcrest_notification'
NOTIFICATION_TITLE = 'Amcrest Camera Setup'
RESOLUTION_LIST = {
'high': 0,
'low': 1,
}
SCAN_INTERVAL = timedelta(seconds=10)
AUTHENTICATION_LIST = {
'basic': 'basic'
}
STREAM_SOURCE_LIST = {
'mjpeg': 0,
'snapshot': 1,
'rtsp': 2,
}
# Sensor types are defined like: Name, units, icon
SENSORS = {
'motion_detector': ['Motion Detected', None, 'mdi:run'],
'sdcard': ['SD Used', '%', 'mdi:sd'],
'ptz_preset': ['PTZ Preset', None, 'mdi:camera-iris'],
}
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
vol.All(vol.In(AUTHENTICATION_LIST)),
vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION):
vol.All(vol.In(RESOLUTION_LIST)),
vol.Optional(CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE):
vol.All(vol.In(STREAM_SOURCE_LIST)),
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period,
vol.Optional(CONF_SENSORS, default=None):
vol.All(cv.ensure_list, [vol.In(SENSORS)]),
})])
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up the Amcrest IP Camera component."""
from amcrest import AmcrestCamera
amcrest_cams = config[DOMAIN]
persistent_notification = loader.get_component('persistent_notification')
for device in amcrest_cams:
camera = AmcrestCamera(device.get(CONF_HOST),
device.get(CONF_PORT),
device.get(CONF_USERNAME),
device.get(CONF_PASSWORD)).camera
try:
camera.current_time
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Amcrest camera: %s", str(ex))
persistent_notification.create(
hass, 'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(ex),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False
ffmpeg_arguments = device.get(CONF_FFMPEG_ARGUMENTS)
name = device.get(CONF_NAME)
resolution = RESOLUTION_LIST[device.get(CONF_RESOLUTION)]
sensors = device.get(CONF_SENSORS)
stream_source = STREAM_SOURCE_LIST[device.get(CONF_STREAM_SOURCE)]
username = device.get(CONF_USERNAME)
password = device.get(CONF_PASSWORD)
# currently aiohttp only works with basic authentication
# only valid for mjpeg streaming
if username is not None and password is not None:
if device.get(CONF_AUTHENTICATION) == HTTP_BASIC_AUTHENTICATION:
authentication = aiohttp.BasicAuth(username, password)
else:
authentication = None
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

View File

@ -13,7 +13,7 @@ from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
REQUIREMENTS = ['apcaccess==0.0.10']
REQUIREMENTS = ['apcaccess==0.0.13']
_LOGGER = logging.getLogger(__name__)

View File

@ -198,8 +198,7 @@ class APIEntityStateView(HomeAssistantView):
state = request.app['hass'].states.get(entity_id)
if state:
return self.json(state)
else:
return self.json_message('Entity not found', HTTP_NOT_FOUND)
return self.json_message('Entity not found', HTTP_NOT_FOUND)
@asyncio.coroutine
def post(self, request, entity_id):
@ -237,8 +236,7 @@ class APIEntityStateView(HomeAssistantView):
"""Remove entity."""
if request.app['hass'].states.async_remove(entity_id):
return self.json_message('Entity removed')
else:
return self.json_message('Entity not found', HTTP_NOT_FOUND)
return self.json_message('Entity not found', HTTP_NOT_FOUND)
class APIEventListenersView(HomeAssistantView):

View File

@ -0,0 +1,259 @@
"""
Support for Apple TV.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/apple_tv/
"""
import os
import asyncio
import logging
import voluptuous as vol
from homeassistant.const import (CONF_HOST, CONF_NAME, ATTR_ENTITY_ID)
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import discovery
from homeassistant.components.discovery import SERVICE_APPLE_TV
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyatv==0.3.2']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'apple_tv'
SERVICE_SCAN = 'apple_tv_scan'
SERVICE_AUTHENTICATE = 'apple_tv_authenticate'
ATTR_ATV = 'atv'
ATTR_POWER = 'power'
CONF_LOGIN_ID = 'login_id'
CONF_START_OFF = 'start_off'
CONF_CREDENTIALS = 'credentials'
DEFAULT_NAME = 'Apple TV'
DATA_APPLE_TV = 'data_apple_tv'
DATA_ENTITIES = 'data_apple_tv_entities'
KEY_CONFIG = 'apple_tv_configuring'
NOTIFICATION_AUTH_ID = 'apple_tv_auth_notification'
NOTIFICATION_AUTH_TITLE = 'Apple TV Authentication'
NOTIFICATION_SCAN_ID = 'apple_tv_scan_notification'
NOTIFICATION_SCAN_TITLE = 'Apple TV Scan'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_LOGIN_ID): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_CREDENTIALS, default=None): cv.string,
vol.Optional(CONF_START_OFF, default=False): cv.boolean
})])
}, extra=vol.ALLOW_EXTRA)
# Currently no attributes but it might change later
APPLE_TV_SCAN_SCHEMA = vol.Schema({})
APPLE_TV_AUTHENTICATE_SCHEMA = vol.Schema({
ATTR_ENTITY_ID: cv.entity_ids,
})
def request_configuration(hass, config, atv, credentials):
"""Request configuration steps from the user."""
configurator = get_component('configurator')
@asyncio.coroutine
def configuration_callback(callback_data):
"""Handle the submitted configuration."""
from pyatv import exceptions
pin = callback_data.get('pin')
notification = get_component('persistent_notification')
try:
yield from atv.airplay.finish_authentication(pin)
notification.async_create(
hass,
'Authentication succeeded!<br /><br />Add the following '
'to credentials: in your apple_tv configuration:<br /><br />'
'{0}'.format(credentials),
title=NOTIFICATION_AUTH_TITLE,
notification_id=NOTIFICATION_AUTH_ID)
except exceptions.DeviceAuthenticationError as ex:
notification.async_create(
hass,
'Authentication failed! Did you enter correct PIN?<br /><br />'
'Details: {0}'.format(ex),
title=NOTIFICATION_AUTH_TITLE,
notification_id=NOTIFICATION_AUTH_ID)
hass.async_add_job(configurator.request_done, instance)
instance = configurator.request_config(
hass, 'Apple TV Authentication', configuration_callback,
description='Please enter PIN code shown on screen.',
submit_caption='Confirm',
fields=[{'id': 'pin', 'name': 'PIN Code', 'type': 'password'}]
)
@asyncio.coroutine
def scan_for_apple_tvs(hass):
"""Scan for devices and present a notification of the ones found."""
import pyatv
atvs = yield from pyatv.scan_for_apple_tvs(hass.loop, timeout=3)
devices = []
for atv in atvs:
login_id = atv.login_id
if login_id is None:
login_id = 'Home Sharing disabled'
devices.append('Name: {0}<br />Host: {1}<br />Login ID: {2}'.format(
atv.name, atv.address, login_id))
if not devices:
devices = ['No device(s) found']
notification = get_component('persistent_notification')
notification.async_create(
hass,
'The following devices were found:<br /><br />' +
'<br /><br />'.join(devices),
title=NOTIFICATION_SCAN_TITLE,
notification_id=NOTIFICATION_SCAN_ID)
@asyncio.coroutine
def async_setup(hass, config):
"""Set up the Apple TV component."""
if DATA_APPLE_TV not in hass.data:
hass.data[DATA_APPLE_TV] = {}
@asyncio.coroutine
def async_service_handler(service):
"""Handler for service calls."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
if entity_ids:
devices = [device for device in hass.data[DATA_ENTITIES]
if device.entity_id in entity_ids]
else:
devices = hass.data[DATA_ENTITIES]
for device in devices:
atv = device.atv
if service.service == SERVICE_AUTHENTICATE:
credentials = yield from atv.airplay.generate_credentials()
yield from atv.airplay.load_credentials(credentials)
_LOGGER.debug('Generated new credentials: %s', credentials)
yield from atv.airplay.start_authentication()
hass.async_add_job(request_configuration,
hass, config, atv, credentials)
elif service.service == SERVICE_SCAN:
hass.async_add_job(scan_for_apple_tvs, hass)
@asyncio.coroutine
def atv_discovered(service, info):
"""Setup an Apple TV that was auto discovered."""
yield from _setup_atv(hass, {
CONF_NAME: info['name'],
CONF_HOST: info['host'],
CONF_LOGIN_ID: info['properties']['hG'],
CONF_START_OFF: False
})
discovery.async_listen(hass, SERVICE_APPLE_TV, atv_discovered)
tasks = [_setup_atv(hass, conf) for conf in config.get(DOMAIN, [])]
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
descriptions = yield from hass.async_add_job(
load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
hass.services.async_register(
DOMAIN, SERVICE_SCAN, async_service_handler,
descriptions.get(SERVICE_SCAN),
schema=APPLE_TV_SCAN_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_AUTHENTICATE, async_service_handler,
descriptions.get(SERVICE_AUTHENTICATE),
schema=APPLE_TV_AUTHENTICATE_SCHEMA)
return True
@asyncio.coroutine
def _setup_atv(hass, atv_config):
"""Setup an Apple TV."""
import pyatv
name = atv_config.get(CONF_NAME)
host = atv_config.get(CONF_HOST)
login_id = atv_config.get(CONF_LOGIN_ID)
start_off = atv_config.get(CONF_START_OFF)
credentials = atv_config.get(CONF_CREDENTIALS)
if host in hass.data[DATA_APPLE_TV]:
return
details = pyatv.AppleTVDevice(name, host, login_id)
session = async_get_clientsession(hass)
atv = pyatv.connect_to_apple_tv(details, hass.loop, session=session)
if credentials:
yield from atv.airplay.load_credentials(credentials)
power = AppleTVPowerManager(hass, atv, start_off)
hass.data[DATA_APPLE_TV][host] = {
ATTR_ATV: atv,
ATTR_POWER: power
}
hass.async_add_job(discovery.async_load_platform(
hass, 'media_player', DOMAIN, atv_config))
hass.async_add_job(discovery.async_load_platform(
hass, 'remote', DOMAIN, atv_config))
class AppleTVPowerManager:
"""Manager for global power management of an Apple TV.
An instance is used per device to share the same power state between
several platforms.
"""
def __init__(self, hass, atv, is_off):
"""Initialize power manager."""
self.hass = hass
self.atv = atv
self.listeners = []
self._is_on = not is_off
def init(self):
"""Initialize power management."""
if self._is_on:
self.atv.push_updater.start()
@property
def turned_on(self):
"""If device is on or off."""
return self._is_on
def set_power_on(self, value):
"""Change if a device is on or off."""
if value != self._is_on:
self._is_on = value
if not self._is_on:
self.atv.push_updater.stop()
else:
self.atv.push_updater.start()
for listener in self.listeners:
self.hass.async_add_job(listener.async_update_ha_state())

View File

@ -42,8 +42,6 @@ def async_trigger(hass, config, action):
},
})
# Do something to call action
if event == SUN_EVENT_SUNRISE:
return async_track_sunrise(hass, call_action, offset)
else:
return async_track_sunset(hass, call_action, offset)
return async_track_sunset(hass, call_action, offset)

View File

@ -68,7 +68,7 @@ class ArestBinarySensor(BinarySensorDevice):
if self._pin is not None:
request = requests.get(
'{}/mode/{}/i'.format(self._resource, self._pin), timeout=10)
if request.status_code is not 200:
if request.status_code != 200:
_LOGGER.error("Can't set mode of %s", self._resource)
@property

View File

@ -199,11 +199,10 @@ class FlicButton(BinarySensorDevice):
"Queued %s dropped for %s. Time in queue was %s",
click_type, self.address, time_string)
return True
else:
_LOGGER.info(
"Queued %s allowed for %s. Time in queue was %s",
click_type, self.address, time_string)
return False
_LOGGER.info(
"Queued %s allowed for %s. Time in queue was %s",
click_type, self.address, time_string)
return False
def _on_up_down(self, channel, click_type, was_queued, time_diff):
"""Update device state, if event was not queued."""

View File

@ -18,7 +18,7 @@ from homeassistant.const import (
CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START,
ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
REQUIREMENTS = ['pyhik==0.1.2']
REQUIREMENTS = ['pyhik==0.1.3']
_LOGGER = logging.getLogger(__name__)
CONF_IGNORED = 'ignored'

View File

@ -34,8 +34,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
devices = []
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMBinarySensor(hass, config)
for conf in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMBinarySensor(hass, conf)
new_device.link_homematic()
devices.append(new_device)

View File

@ -8,7 +8,7 @@ import logging
import voluptuous as vol
import homeassistant.components.modbus as modbus
from homeassistant.const import CONF_NAME
from homeassistant.const import CONF_NAME, CONF_SLAVE
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.helpers import config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
@ -18,7 +18,6 @@ DEPENDENCIES = ['modbus']
CONF_COIL = 'coil'
CONF_COILS = 'coils'
CONF_SLAVE = 'slave'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COILS): [{
@ -50,6 +49,7 @@ class ModbusCoilSensor(BinarySensorDevice):
self._coil = int(coil)
self._value = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@ -62,4 +62,10 @@ class ModbusCoilSensor(BinarySensorDevice):
def update(self):
"""Update the state of the sensor."""
result = modbus.HUB.read_coils(self._slave, self._coil, 1)
self._value = result.bits[0]
try:
self._value = result.bits[0]
except AttributeError:
_LOGGER.error(
'No response from modbus slave %s coil %s',
self._slave,
self._coil)

View File

@ -156,8 +156,7 @@ class NetatmoBinarySensor(BinarySensorDevice):
return WELCOME_SENSOR_TYPES.get(self._sensor_name)
elif self._cameratype == 'NOC':
return PRESENCE_SENSOR_TYPES.get(self._sensor_name)
else:
return TAG_SENSOR_TYPES.get(self._sensor_name)
return TAG_SENSOR_TYPES.get(self._sensor_name)
@property
def is_on(self):

View File

@ -12,13 +12,12 @@ import voluptuous as vol
from homeassistant.const import CONF_NAME, CONF_MONITORED_CONDITIONS
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['octoprint']
DOMAIN = "octoprint"
DEFAULT_NAME = 'OctoPrint'
SENSOR_TYPES = {
@ -37,7 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the available OctoPrint binary sensors."""
octoprint = get_component('octoprint')
octoprint_api = hass.data[DOMAIN]["api"]
name = config.get(CONF_NAME)
monitored_conditions = config.get(
CONF_MONITORED_CONDITIONS, SENSOR_TYPES.keys())
@ -45,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
devices = []
for octo_type in monitored_conditions:
new_sensor = OctoPrintBinarySensor(
octoprint.OCTOPRINT, octo_type, SENSOR_TYPES[octo_type][2],
octoprint_api, octo_type, SENSOR_TYPES[octo_type][2],
name, SENSOR_TYPES[octo_type][3], SENSOR_TYPES[octo_type][0],
SENSOR_TYPES[octo_type][1], 'flags')
devices.append(new_sensor)
@ -98,6 +97,3 @@ class OctoPrintBinarySensor(BinarySensorDevice):
except requests.exceptions.ConnectionError:
# Error calling the api, already logged in api.update()
return
if self._state is None:
_LOGGER.warning("Unable to locate value for %s", self.sensor_type)

View File

@ -28,6 +28,7 @@ from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__)
CONF_VARIABLE = 'variable'
CONF_RESET_DELAY_SEC = 'reset_delay_sec'
DEFAULT_NAME = 'Pilight Binary Sensor'
DEPENDENCIES = ['pilight']
@ -38,7 +39,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default='on'): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default='off'): cv.string,
vol.Optional(CONF_DISARM_AFTER_TRIGGER, default=False): cv.boolean
vol.Optional(CONF_DISARM_AFTER_TRIGGER, default=False): cv.boolean,
vol.Optional(CONF_RESET_DELAY_SEC, default=30): cv.positive_int
})
@ -54,6 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
payload=config.get(CONF_PAYLOAD),
on_value=config.get(CONF_PAYLOAD_ON),
off_value=config.get(CONF_PAYLOAD_OFF),
rst_dly_sec=config.get(CONF_RESET_DELAY_SEC),
)])
else:
add_devices([PilightBinarySensor(

View File

@ -126,14 +126,14 @@ class PingData(object):
'avg': rtt_avg,
'max': rtt_max,
'mdev': ''}
else:
match = PING_MATCHER.search(str(out).split('\n')[-1])
rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
return {
'min': rtt_min,
'avg': rtt_avg,
'max': rtt_max,
'mdev': rtt_mdev}
match = PING_MATCHER.search(str(out).split('\n')[-1])
rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
return {
'min': rtt_min,
'avg': rtt_avg,
'max': rtt_max,
'mdev': rtt_mdev}
except (subprocess.CalledProcessError, AttributeError):
return False

View File

@ -30,8 +30,7 @@ class VolvoSensor(VolvoEntity, BinarySensorDevice):
return bool(val)
elif self._attribute in ['doors', 'windows']:
return any([val[key] for key in val if 'Open' in key])
else:
return val != 'Normal'
return val != 'Normal'
@property
def device_class(self):

View File

@ -121,10 +121,6 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
class WinkSmokeDetector(WinkBinarySensorDevice):
"""Representation of a Wink Smoke detector."""
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
"""Return the state attributes."""
@ -136,10 +132,6 @@ class WinkSmokeDetector(WinkBinarySensorDevice):
class WinkHub(WinkBinarySensorDevice):
"""Representation of a Wink Hub."""
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
"""Return the state attributes."""
@ -152,10 +144,6 @@ class WinkHub(WinkBinarySensorDevice):
class WinkRemote(WinkBinarySensorDevice):
"""Representation of a Wink Lutron Connected bulb remote."""
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
"""Return the state attributes."""
@ -175,10 +163,6 @@ class WinkRemote(WinkBinarySensorDevice):
class WinkButton(WinkBinarySensorDevice):
"""Representation of a Wink Relay button."""
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
"""Return the state attributes."""
@ -191,10 +175,6 @@ class WinkButton(WinkBinarySensorDevice):
class WinkGang(WinkBinarySensorDevice):
"""Representation of a Wink Relay gang."""
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def is_on(self):
"""Return true if the gang is connected."""

View File

@ -34,10 +34,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
from bellows.zigbee.zcl.clusters.security import IasZone
clusters = discovery_info['clusters']
in_clusters = discovery_info['in_clusters']
device_class = None
cluster = [c for c in clusters if isinstance(c, IasZone)][0]
cluster = in_clusters[IasZone.cluster_id]
if discovery_info['new_join']:
yield from cluster.bind()
ieee = cluster.endpoint.device.application.ieee
@ -64,7 +64,7 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
super().__init__(**kwargs)
self._device_class = device_class
from bellows.zigbee.zcl.clusters.security import IasZone
self._ias_zone_cluster = self._clusters[IasZone.cluster_id]
self._ias_zone_cluster = self._in_clusters[IasZone.cluster_id]
@property
def is_on(self) -> bool:

View File

@ -148,8 +148,7 @@ class CalendarEventDevice(Entity):
if 'date' in date:
return dt.start_of_local_day(dt.dt.datetime.combine(
dt.parse_date(date['date']), dt.dt.time.min))
else:
return dt.as_local(dt.parse_datetime(date['dateTime']))
return dt.as_local(dt.parse_datetime(date['dateTime']))
start = _get_date(self.data.event['start'])
end = _get_date(self.data.event['end'])

View File

@ -266,8 +266,7 @@ class Camera(Entity):
return STATE_RECORDING
elif self.is_streaming:
return STATE_STREAMING
else:
return STATE_IDLE
return STATE_IDLE
def enable_motion_detection(self):
"""Enable motion detection in the camera."""

View File

@ -7,108 +7,59 @@ https://home-assistant.io/components/camera.amcrest/
import asyncio
import logging
import aiohttp
import voluptuous as vol
import homeassistant.loader as loader
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.components.amcrest import (
STREAM_SOURCE_LIST, TIMEOUT)
from homeassistant.components.camera import Camera
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_web,
async_aiohttp_proxy_stream)
REQUIREMENTS = ['amcrest==1.2.0']
DEPENDENCIES = ['ffmpeg']
DEPENDENCIES = ['amcrest', 'ffmpeg']
_LOGGER = logging.getLogger(__name__)
CONF_RESOLUTION = 'resolution'
CONF_STREAM_SOURCE = 'stream_source'
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
DEFAULT_NAME = 'Amcrest Camera'
DEFAULT_PORT = 80
DEFAULT_RESOLUTION = 'high'
DEFAULT_STREAM_SOURCE = 'mjpeg'
NOTIFICATION_ID = 'amcrest_notification'
NOTIFICATION_TITLE = 'Amcrest Camera Setup'
RESOLUTION_LIST = {
'high': 0,
'low': 1,
}
STREAM_SOURCE_LIST = {
'mjpeg': 0,
'snapshot': 1,
'rtsp': 2,
}
CONTENT_TYPE_HEADER = 'Content-Type'
TIMEOUT = 5
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION):
vol.All(vol.In(RESOLUTION_LIST)),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE):
vol.All(vol.In(STREAM_SOURCE_LIST)),
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up an Amcrest IP Camera."""
from amcrest import AmcrestCamera
camera = AmcrestCamera(
config.get(CONF_HOST), config.get(CONF_PORT),
config.get(CONF_USERNAME), config.get(CONF_PASSWORD)).camera
if discovery_info is None:
return
persistent_notification = loader.get_component('persistent_notification')
try:
camera.current_time
# pylint: disable=broad-except
except Exception as ex:
_LOGGER.error("Unable to connect to Amcrest camera: %s", str(ex))
persistent_notification.create(
hass, 'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(ex),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False
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']
async_add_devices([
AmcrestCam(hass,
name,
device,
authentication,
ffmpeg_arguments,
stream_source,
resolution)], True)
add_devices([AmcrestCam(hass, config, camera)])
return True
class AmcrestCam(Camera):
"""An implementation of an Amcrest IP camera."""
def __init__(self, hass, device_info, camera):
def __init__(self, hass, name, camera, authentication,
ffmpeg_arguments, stream_source, resolution):
"""Initialize an Amcrest camera."""
super(AmcrestCam, self).__init__()
self._name = name
self._camera = camera
self._base_url = self._camera.get_base_url()
self._name = device_info.get(CONF_NAME)
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
self._resolution = RESOLUTION_LIST[device_info.get(CONF_RESOLUTION)]
self._stream_source = STREAM_SOURCE_LIST[
device_info.get(CONF_STREAM_SOURCE)
]
self._token = self._auth = aiohttp.BasicAuth(
device_info.get(CONF_USERNAME),
password=device_info.get(CONF_PASSWORD)
)
self._ffmpeg_arguments = ffmpeg_arguments
self._stream_source = stream_source
self._resolution = resolution
self._token = self._auth = authentication
def camera_image(self):
"""Return a still image reponse from the camera."""

View File

@ -49,7 +49,6 @@ class ArloCam(Camera):
"""Initialize an Arlo camera."""
super().__init__()
self._camera = camera
self._base_stn = hass.data[DATA_ARLO].base_stations[0]
self._name = self._camera.name
self._motion_status = False
self._ffmpeg = hass.data[DATA_FFMPEG]
@ -103,7 +102,16 @@ class ArloCam(Camera):
def set_base_station_mode(self, mode):
"""Set the mode in the base station."""
self._base_stn.mode = mode
# Get the list of base stations identified by library
base_stations = self.hass.data[DATA_ARLO].base_stations
# Some Arlo cameras does not have basestation
# So check if there is base station detected first
# if yes, then choose the primary base station
# Set the mode on the chosen base station
if base_stations:
primary_base_station = base_stations[0]
primary_base_station.mode = mode
def enable_motion_detection(self):
"""Enable the Motion detection in base station (Arm)."""

View File

@ -113,8 +113,7 @@ class NetatmoCamera(Camera):
return "Presence"
elif self._cameratype == "NACamera":
return "Welcome"
else:
return None
return None
@property
def unique_id(self):

View File

@ -54,7 +54,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error("Unable to connect to NVR: %s", str(ex))
return False
identifier = nvrconn.server_version >= (3, 2, 0) and 'id' or 'uuid'
identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid'
# Filter out airCam models, which are not supported in the latest
# version of UnifiVideo and which are EOL by Ubiquiti
cameras = [

View File

@ -398,16 +398,14 @@ class ClimateDevice(Entity):
"""Return the current state."""
if self.current_operation:
return self.current_operation
else:
return STATE_UNKNOWN
return STATE_UNKNOWN
@property
def precision(self):
"""Return the precision of the system."""
if self.unit_of_measurement == TEMP_CELSIUS:
return PRECISION_TENTHS
else:
return PRECISION_WHOLE
return PRECISION_WHOLE
@property
def state_attributes(self):
@ -709,6 +707,5 @@ class ClimateDevice(Entity):
return round(temp * 2) / 2.0
elif self.precision == PRECISION_TENTHS:
return round(temp, 1)
else:
# PRECISION_WHOLE as a fall back
return round(temp)
# PRECISION_WHOLE as a fall back
return round(temp)

View File

@ -151,16 +151,14 @@ class Thermostat(ClimateDevice):
"""Return the lower bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
return int(self.thermostat['runtime']['desiredHeat'] / 10)
else:
return None
return None
@property
def target_temperature_high(self):
"""Return the upper bound temperature we try to reach."""
if self.current_operation == STATE_AUTO:
return int(self.thermostat['runtime']['desiredCool'] / 10)
else:
return None
return None
@property
def target_temperature(self):
@ -171,8 +169,7 @@ class Thermostat(ClimateDevice):
return int(self.thermostat['runtime']['desiredHeat'] / 10)
elif self.current_operation == STATE_COOL:
return int(self.thermostat['runtime']['desiredCool'] / 10)
else:
return None
return None
@property
def desired_fan_mode(self):
@ -184,8 +181,7 @@ class Thermostat(ClimateDevice):
"""Return the current fan state."""
if 'fan' in self.thermostat['equipmentStatus']:
return STATE_ON
else:
return STATE_OFF
return STATE_OFF
@property
def current_hold_mode(self):
@ -199,15 +195,13 @@ class Thermostat(ClimateDevice):
int(event['startDate'][0:4]) <= 1:
# A temporary hold from away climate is a hold
return 'away'
else:
# A permanent hold from away climate is away_mode
return None
# A permanent hold from away climate is away_mode
return None
elif event['holdClimateRef'] != "":
# Any other hold based on climate
return event['holdClimateRef']
else:
# Any hold not based on a climate is a temp hold
return TEMPERATURE_HOLD
# Any hold not based on a climate is a temp hold
return TEMPERATURE_HOLD
elif event['type'].startswith('auto'):
# All auto modes are treated as holds
return event['type'][4:].lower()
@ -222,8 +216,7 @@ class Thermostat(ClimateDevice):
if self.operation_mode == 'auxHeatOnly' or \
self.operation_mode == 'heatPump':
return STATE_HEAT
else:
return self.operation_mode
return self.operation_mode
@property
def operation_list(self):
@ -384,8 +377,7 @@ class Thermostat(ClimateDevice):
# add further conditions if other hold durations should be
# supported; note that this should not include 'indefinite'
# as an indefinite away hold is interpreted as away_mode
else:
return 'nextTransition'
return 'nextTransition'
@property
def climate_list(self):

View File

@ -145,4 +145,4 @@ class Flexit(ClimateDevice):
def set_fan_mode(self, fan):
"""Set new fan mode."""
self.unit.set_fan_speed(fan)
self.unit.set_fan_speed(self._fan_list.index(fan))

View File

@ -12,7 +12,8 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components import switch
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA,
STATE_AUTO)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
CONF_NAME)
@ -87,6 +88,7 @@ class GenericThermostat(ClimateDevice):
self.min_cycle_duration = min_cycle_duration
self._tolerance = tolerance
self._keep_alive = keep_alive
self._enabled = True
self._active = False
self._cur_temp = None
@ -131,18 +133,39 @@ class GenericThermostat(ClimateDevice):
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if not self._enabled:
return STATE_OFF
if self.ac_mode:
cooling = self._active and self._is_device_active
return STATE_COOL if cooling else STATE_IDLE
else:
heating = self._active and self._is_device_active
return STATE_HEAT if heating else STATE_IDLE
heating = self._active and self._is_device_active
return STATE_HEAT if heating else STATE_IDLE
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temp
@property
def operation_list(self):
"""List of available operation modes."""
return [STATE_AUTO, STATE_OFF]
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
if operation_mode == STATE_AUTO:
self._enabled = True
elif operation_mode == STATE_OFF:
self._enabled = False
if self._is_device_active:
switch.async_turn_off(self.hass, self.heater_entity_id)
else:
_LOGGER.error('Unrecognized operation mode: %s', operation_mode)
return
# Ensure we updae the current operation after changing the mode
self.schedule_update_ha_state()
@asyncio.coroutine
def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
@ -159,9 +182,9 @@ class GenericThermostat(ClimateDevice):
# pylint: disable=no-member
if self._min_temp:
return self._min_temp
else:
# get default temp from super class
return ClimateDevice.min_temp.fget(self)
# get default temp from super class
return ClimateDevice.min_temp.fget(self)
@property
def max_temp(self):
@ -169,9 +192,9 @@ class GenericThermostat(ClimateDevice):
# pylint: disable=no-member
if self._max_temp:
return self._max_temp
else:
# Get default temp from super class
return ClimateDevice.max_temp.fget(self)
# Get default temp from super class
return ClimateDevice.max_temp.fget(self)
@asyncio.coroutine
def _async_sensor_changed(self, entity_id, old_state, new_state):
@ -221,6 +244,9 @@ class GenericThermostat(ClimateDevice):
if not self._active:
return
if not self._enabled:
return
if self.min_cycle_duration:
if self._is_device_active:
current_state = STATE_ON

View File

@ -46,8 +46,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
devices = []
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMThermostat(hass, config)
for conf in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMThermostat(hass, conf)
new_device.link_homematic()
devices.append(new_device)

View File

@ -60,8 +60,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if region == 'us':
return _setup_us(username, password, config, add_devices)
else:
return _setup_round(username, password, config, add_devices)
return _setup_round(username, password, config, add_devices)
def _setup_round(username, password, config, add_devices):
@ -251,8 +251,7 @@ class HoneywellUSThermostat(ClimateDevice):
"""Return the temperature we try to reach."""
if self._device.system_mode == 'cool':
return self._device.setpoint_cool
else:
return self._device.setpoint_heat
return self._device.setpoint_heat
@property
def current_operation(self: ClimateDevice) -> str:

View File

@ -109,16 +109,14 @@ class NestThermostat(ClimateDevice):
return self._mode
elif self._mode == STATE_HEAT_COOL:
return STATE_AUTO
else:
return STATE_UNKNOWN
return STATE_UNKNOWN
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._mode != STATE_HEAT_COOL and not self.is_away_mode_on:
return self._target_temperature
else:
return None
return None
@property
def target_temperature_low(self):
@ -129,8 +127,7 @@ class NestThermostat(ClimateDevice):
return self._eco_temperature[0]
if self._mode == STATE_HEAT_COOL:
return self._target_temperature[0]
else:
return None
return None
@property
def target_temperature_high(self):
@ -141,8 +138,7 @@ class NestThermostat(ClimateDevice):
return self._eco_temperature[1]
if self._mode == STATE_HEAT_COOL:
return self._target_temperature[1]
else:
return None
return None
@property
def is_away_mode_on(self):
@ -188,9 +184,8 @@ class NestThermostat(ClimateDevice):
if self._has_fan:
# Return whether the fan is on
return STATE_ON if self._fan else STATE_AUTO
else:
# No Fan available so disable slider
return None
# No Fan available so disable slider
return None
@property
def fan_list(self):

View File

@ -119,14 +119,14 @@ class NetatmoThermostat(ClimateDevice):
self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None)
self._away = False
def set_temperature(self, endTimeOffset=DEFAULT_TIME_OFFSET, **kwargs):
def set_temperature(self, **kwargs):
"""Set new target temperature for 2 hours."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
mode = "manual"
self._data.thermostatdata.setthermpoint(
mode, temperature, endTimeOffset)
mode, temperature, DEFAULT_TIME_OFFSET)
self._target_temperature = temperature
self._away = False

View File

@ -92,8 +92,7 @@ class ThermostatDevice(ClimateDevice):
"""Return current operation i.e. heat, cool, idle."""
if self._state:
return STATE_HEAT
else:
return STATE_IDLE
return STATE_IDLE
@property
def current_temperature(self):

View File

@ -136,24 +136,53 @@ class RadioThermostat(ClimateDevice):
return self._away
def update(self):
"""Update the data from the thermostat."""
self._current_temperature = self.device.temp['raw']
"""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']
self._fmode = self.device.fmode['human']
self._tmode = self.device.tmode['human']
self._tstate = self.device.tstate['human']
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")
if self._tmode == 'Cool':
self._target_temperature = self.device.t_cool['raw']
target_temp = self.device.t_cool['raw']
if target_temp == -1:
_LOGGER.error("Couldn't get target reading")
return
self._target_temperature = target_temp
self._current_operation = STATE_COOL
elif self._tmode == 'Heat':
self._target_temperature = self.device.t_heat['raw']
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':
self._target_temperature = self.device.t_cool['raw']
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':
self._target_temperature = self.device.t_heat['raw']
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
else:
self._current_operation = STATE_IDLE

View File

@ -117,9 +117,8 @@ class SensiboClimate(ClimateDevice):
# We are working in same units as the a/c unit. Use whole degrees
# like the API supports.
return 1
else:
# Unit conversion is going on. No point to stick to specific steps.
return None
# Unit conversion is going on. No point to stick to specific steps.
return None
@property
def current_operation(self):

View File

@ -52,7 +52,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
zones = tado.get_zones()
except RuntimeError:
_LOGGER.error("Unable to get zone info from mytado")
return False
return
climate_devices = []
for zone in zones:
@ -61,9 +61,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if climate_devices:
add_devices(climate_devices, True)
return True
else:
return False
def create_climate_device(tado, hass, zone, name, zone_id):
@ -150,8 +147,7 @@ class TadoClimate(ClimateDevice):
"""Return current readable operation mode."""
if self._cooling:
return "Cooling"
else:
return OPERATION_LIST.get(self._current_operation)
return OPERATION_LIST.get(self._current_operation)
@property
def operation_list(self):
@ -163,16 +159,14 @@ class TadoClimate(ClimateDevice):
"""Return the fan setting."""
if self.ac_mode:
return FAN_MODES_LIST.get(self._current_fan)
else:
return None
return None
@property
def fan_list(self):
"""List of available fan modes."""
if self.ac_mode:
return list(FAN_MODES_LIST.values())
else:
return None
return None
@property
def temperature_unit(self):
@ -218,18 +212,16 @@ class TadoClimate(ClimateDevice):
"""Return the minimum temperature."""
if self._min_temp:
return self._min_temp
else:
# get default temp from super class
return super().min_temp
# get default temp from super class
return super().min_temp
@property
def max_temp(self):
"""Return the maximum temperature."""
if self._max_temp:
return self._max_temp
else:
# Get default temp from super class
return super().max_temp
# Get default temp from super class
return super().max_temp
def update(self):
"""Update the state of this climate device."""

View File

@ -111,8 +111,8 @@ class WinkThermostat(WinkDevice, ClimateDevice):
# This will address both possibilities
if self.wink.current_humidity() < 1:
return self.wink.current_humidity() * 100
else:
return self.wink.current_humidity()
return self.wink.current_humidity()
return None
@property
def external_temperature(self):
@ -175,10 +175,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
return self.wink.current_max_set_point()
elif self.current_operation == STATE_HEAT:
return self.wink.current_min_set_point()
else:
return None
else:
return None
return None
@property
def target_temperature_low(self):
@ -206,8 +203,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
return True
elif self.wink.current_hvac_mode() == 'aux' and not self.wink.is_on():
return False
else:
return None
return None
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@ -270,9 +266,8 @@ class WinkThermostat(WinkDevice, ClimateDevice):
return STATE_ON
elif self.wink.current_fan_mode() == 'auto':
return STATE_AUTO
else:
# No Fan available so disable slider
return None
# No Fan available so disable slider
return None
@property
def fan_list(self):
@ -451,8 +446,7 @@ class WinkAC(WinkDevice, ClimateDevice):
return SPEED_MEDIUM
elif speed <= 1.0 and speed > 0.8:
return SPEED_HIGH
else:
return STATE_UNKNOWN
return STATE_UNKNOWN
@property
def fan_list(self):

View File

@ -154,8 +154,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
return TEMP_CELSIUS
elif self._unit == 'F':
return TEMP_FAHRENHEIT
else:
return self._unit
return self._unit
@property
def current_temperature(self):

View File

@ -112,10 +112,7 @@ class CommandCover(CoverDevice):
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
return self.current_cover_position == 0
@property
def current_cover_position(self):

View File

@ -79,8 +79,7 @@ class DemoCover(CoverDevice):
"""Flag supported features."""
if self._supported_features is not None:
return self._supported_features
else:
return super().supported_features
return super().supported_features
def close_cover(self, **kwargs):
"""Close the cover."""

View File

@ -159,8 +159,7 @@ class GaradgetCover(CoverDevice):
"""Return if the cover is closed."""
if self._state == STATE_UNKNOWN:
return None
else:
return self._state == STATE_CLOSED
return self._state == STATE_CLOSED
@property
def device_class(self):

View File

@ -20,8 +20,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
devices = []
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMCover(hass, config)
for conf in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMCover(hass, conf)
new_device.link_homematic()
devices.append(new_device)
@ -52,10 +52,7 @@ class HMCover(HMDevice, CoverDevice):
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
return self.current_cover_position == 0
def open_cover(self, **kwargs):
"""Open the cover."""

View File

@ -361,8 +361,7 @@ class MqttCover(CoverDevice):
position_percentage = float(offset_position) / tilt_range * 100.0
if self._tilt_invert:
return 100 - position_percentage
else:
return position_percentage
return position_percentage
def find_in_range_from_percent(self, percentage):
"""

View File

@ -53,8 +53,7 @@ class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
set_req = self.gateway.const.SetReq
if set_req.V_DIMMER in self._values:
return self._values.get(set_req.V_DIMMER) == 0
else:
return self._values.get(set_req.V_LIGHT) == STATE_OFF
return self._values.get(set_req.V_LIGHT) == STATE_OFF
@property
def current_cover_position(self):

View File

@ -117,8 +117,7 @@ class OpenGarageCover(CoverDevice):
"""Return if the cover is closed."""
if self._state == STATE_UNKNOWN:
return None
else:
return self._state in [STATE_CLOSED, STATE_OPENING]
return self._state in [STATE_CLOSED, STATE_OPENING]
def close_cover(self):
"""Close the cover."""

View File

@ -40,14 +40,15 @@ STOP_ACTION = 'stop_cover'
POSITION_ACTION = 'set_cover_position'
TILT_ACTION = 'set_cover_tilt_position'
CONF_VALUE_OR_POSITION_TEMPLATE = 'value_or_position'
CONF_OPEN_OR_CLOSE = 'open_or_close'
TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT |
SUPPORT_SET_TILT_POSITION)
COVER_SCHEMA = vol.Schema({
vol.Required(OPEN_ACTION): cv.SCRIPT_SCHEMA,
vol.Required(CLOSE_ACTION): cv.SCRIPT_SCHEMA,
vol.Required(STOP_ACTION): cv.SCRIPT_SCHEMA,
vol.Inclusive(OPEN_ACTION, CONF_OPEN_OR_CLOSE): cv.SCRIPT_SCHEMA,
vol.Inclusive(CLOSE_ACTION, CONF_OPEN_OR_CLOSE): cv.SCRIPT_SCHEMA,
vol.Optional(STOP_ACTION): cv.SCRIPT_SCHEMA,
vol.Exclusive(CONF_POSITION_TEMPLATE,
CONF_VALUE_OR_POSITION_TEMPLATE): cv.template,
vol.Exclusive(CONF_VALUE_TEMPLATE,
@ -77,9 +78,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
position_template = device_config.get(CONF_POSITION_TEMPLATE)
tilt_template = device_config.get(CONF_TILT_TEMPLATE)
icon_template = device_config.get(CONF_ICON_TEMPLATE)
open_action = device_config[OPEN_ACTION]
close_action = device_config[CLOSE_ACTION]
stop_action = device_config[STOP_ACTION]
open_action = device_config.get(OPEN_ACTION)
close_action = device_config.get(CLOSE_ACTION)
stop_action = device_config.get(STOP_ACTION)
position_action = device_config.get(POSITION_ACTION)
tilt_action = device_config.get(TILT_ACTION)
@ -88,6 +89,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE)
continue
if position_action is None and open_action is None:
_LOGGER.error('Must specify at least one of %s' or '%s',
OPEN_ACTION, POSITION_ACTION)
continue
template_entity_ids = set()
if state_template is not None:
temp_ids = state_template.extract_entities()
@ -147,9 +152,15 @@ class CoverTemplate(CoverDevice):
self._position_template = position_template
self._tilt_template = tilt_template
self._icon_template = icon_template
self._open_script = Script(hass, open_action)
self._close_script = Script(hass, close_action)
self._stop_script = Script(hass, stop_action)
self._open_script = None
if open_action is not None:
self._open_script = Script(hass, open_action)
self._close_script = None
if close_action is not None:
self._close_script = Script(hass, close_action)
self._stop_script = None
if stop_action is not None:
self._stop_script = Script(hass, stop_action)
self._position_script = None
if position_action is not None:
self._position_script = Script(hass, position_action)
@ -227,9 +238,12 @@ class CoverTemplate(CoverDevice):
@property
def supported_features(self):
"""Flag supported features."""
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE
if self.current_cover_position is not None:
if self._stop_script is not None:
supported_features |= SUPPORT_STOP
if self._position_script is not None:
supported_features |= SUPPORT_SET_POSITION
if self.current_cover_tilt_position is not None:
@ -245,23 +259,30 @@ class CoverTemplate(CoverDevice):
@asyncio.coroutine
def async_open_cover(self, **kwargs):
"""Move the cover up."""
self.hass.async_add_job(self._open_script.async_run())
if self._open_script:
self.hass.async_add_job(self._open_script.async_run())
elif self._position_script:
self.hass.async_add_job(self._position_script.async_run(
{"position": 100}))
@asyncio.coroutine
def async_close_cover(self, **kwargs):
"""Move the cover down."""
self.hass.async_add_job(self._close_script.async_run())
if self._close_script:
self.hass.async_add_job(self._close_script.async_run())
elif self._position_script:
self.hass.async_add_job(self._position_script.async_run(
{"position": 0}))
@asyncio.coroutine
def async_stop_cover(self, **kwargs):
"""Fire the stop action."""
self.hass.async_add_job(self._stop_script.async_run())
if self._stop_script:
self.hass.async_add_job(self._stop_script.async_run())
@asyncio.coroutine
def async_set_cover_position(self, **kwargs):
"""Set cover position."""
if ATTR_POSITION not in kwargs:
return
self._position = kwargs[ATTR_POSITION]
self.hass.async_add_job(self._position_script.async_run(
{"position": self._position}))
@ -283,8 +304,6 @@ class CoverTemplate(CoverDevice):
@asyncio.coroutine
def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
if ATTR_TILT_POSITION not in kwargs:
return
self._tilt_value = kwargs[ATTR_TILT_POSITION]
self.hass.async_add_job(self._tilt_script.async_run(
{"tilt": self._tilt_value}))

View File

@ -53,10 +53,7 @@ class VeraCover(VeraDevice, CoverDevice):
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
return self.current_cover_position == 0
def open_cover(self, **kwargs):
"""Open the cover."""

View File

@ -29,20 +29,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkCoverDevice(WinkDevice, CoverDevice):
"""Representation of a Wink cover device."""
def __init__(self, wink, hass):
"""Initialize the cover."""
super().__init__(wink, hass)
@asyncio.coroutine
def async_added_to_hass(self):
"""Callback when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['cover'].append(self)
def close_cover(self):
def close_cover(self, **kwargs):
"""Close the shade."""
self.wink.set_state(0)
def open_cover(self):
def open_cover(self, **kwargs):
"""Open the shade."""
self.wink.set_state(1)

View File

@ -73,8 +73,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
return None
if self.current_cover_position > 0:
return False
else:
return True
return True
@property
def current_cover_position(self):
@ -86,8 +85,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
return 0
elif self._current_position >= 95:
return 100
else:
return self._current_position
return self._current_position
def open_cover(self, **kwargs):
"""Move the roller shutter up."""

View File

@ -60,20 +60,11 @@ _LEASES_REGEX = re.compile(
r'(?P<host>([^\s]+))')
# Command to get both 5GHz and 2.4GHz clients
_WL_CMD = '{ wl -i eth2 assoclist & wl -i eth1 assoclist ; }'
_WL_CMD = 'for dev in `nvram get wl_ifnames`; do wl -i $dev assoclist; done'
_WL_REGEX = re.compile(
r'\w+\s' +
r'(?P<mac>(([0-9A-F]{2}[:-]){5}([0-9A-F]{2})))')
_ARP_CMD = 'arp -n'
_ARP_REGEX = re.compile(
r'.+\s' +
r'\((?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\)\s' +
r'.+\s' +
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))' +
r'\s' +
r'.*')
_IP_NEIGH_CMD = 'ip neigh'
_IP_NEIGH_REGEX = re.compile(
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3}|'
@ -84,15 +75,6 @@ _IP_NEIGH_REGEX = re.compile(
r'\s?(router)?'
r'(?P<status>(\w+))')
_NVRAM_CMD = 'nvram get client_info_tmp'
_NVRAM_REGEX = re.compile(
r'.*>.*>' +
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})' +
r'>' +
r'(?P<mac>(([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2})))' +
r'>' +
r'.*')
# pylint: disable=unused-argument
def get_scanner(hass, config):
@ -102,7 +84,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
AsusWrtResult = namedtuple('AsusWrtResult', 'neighbors leases arp nvram')
AsusWrtResult = namedtuple('AsusWrtResult', 'neighbors leases')
class AsusWrtDeviceScanner(DeviceScanner):
@ -173,7 +155,7 @@ class AsusWrtDeviceScanner(DeviceScanner):
return False
with self.lock:
_LOGGER.info('Checking ARP')
_LOGGER.info('Checking Devices')
data = self.get_asuswrt_data()
if not data:
return False
@ -182,7 +164,7 @@ class AsusWrtDeviceScanner(DeviceScanner):
client['status'] == 'REACHABLE' or
client['status'] == 'DELAY' or
client['status'] == 'STALE' or
client['status'] == 'IN_NVRAM']
client['status'] == 'IN_ASSOCLIST']
self.last_results = active_clients
return True
@ -204,41 +186,12 @@ class AsusWrtDeviceScanner(DeviceScanner):
host = ''
# match mac addresses to IP addresses in ARP table
for arp in result.arp:
if match.group('mac').lower() in \
arp.decode('utf-8').lower():
arp_match = _ARP_REGEX.search(
arp.decode('utf-8').lower())
if not arp_match:
_LOGGER.warning("Could not parse arp row: %s", arp)
continue
devices[arp_match.group('ip')] = {
'host': host,
'status': '',
'ip': arp_match.group('ip'),
'mac': match.group('mac').upper(),
}
# match mac addresses to IP addresses in NVRAM table
for nvr in result.nvram:
if match.group('mac').upper() in nvr.decode('utf-8'):
nvram_match = _NVRAM_REGEX.search(nvr.decode('utf-8'))
if not nvram_match:
_LOGGER.warning("Could not parse nvr row: %s", nvr)
continue
# skip current check if already in ARP table
if nvram_match.group('ip') in devices.keys():
continue
devices[nvram_match.group('ip')] = {
'host': host,
'status': 'IN_NVRAM',
'ip': nvram_match.group('ip'),
'mac': match.group('mac').upper(),
}
devices[match.group('mac').upper()] = {
'host': host,
'status': 'IN_ASSOCLIST',
'ip': '',
'mac': match.group('mac').upper(),
}
else:
for lease in result.leases:
@ -256,20 +209,23 @@ class AsusWrtDeviceScanner(DeviceScanner):
if host == '*':
host = ''
devices[match.group('ip')] = {
devices[match.group('mac')] = {
'host': host,
'status': '',
'ip': match.group('ip'),
'mac': match.group('mac').upper(),
}
for neighbor in result.neighbors:
match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8'))
if not match:
_LOGGER.warning("Could not parse neighbor row: %s", neighbor)
continue
if match.group('ip') in devices:
devices[match.group('ip')]['status'] = match.group('status')
for neighbor in result.neighbors:
match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8'))
if not match:
_LOGGER.warning("Could not parse neighbor row: %s",
neighbor)
continue
if match.group('mac') in devices:
devices[match.group('mac')]['status'] = (
match.group('status'))
return devices
@ -317,27 +273,19 @@ class SshConnection(_Connection):
try:
if not self.connected:
self.connect()
self._ssh.sendline(_IP_NEIGH_CMD)
self._ssh.prompt()
neighbors = self._ssh.before.split(b'\n')[1:-1]
if self._ap:
self._ssh.sendline(_ARP_CMD)
self._ssh.prompt()
arp_result = self._ssh.before.split(b'\n')[1:-1]
neighbors = ['']
self._ssh.sendline(_WL_CMD)
self._ssh.prompt()
leases_result = self._ssh.before.split(b'\n')[1:-1]
self._ssh.sendline(_NVRAM_CMD)
self._ssh.prompt()
nvram_result = self._ssh.before.split(b'\n')[1].split(b'<')[1:]
else:
arp_result = ['']
nvram_result = ['']
self._ssh.sendline(_IP_NEIGH_CMD)
self._ssh.prompt()
neighbors = self._ssh.before.split(b'\n')[1:-1]
self._ssh.sendline(_LEASES_CMD)
self._ssh.prompt()
leases_result = self._ssh.before.split(b'\n')[1:-1]
return AsusWrtResult(neighbors, leases_result, arp_result,
nvram_result)
return AsusWrtResult(neighbors, leases_result)
except exceptions.EOF as err:
_LOGGER.error("Connection refused. SSH enabled?")
self.disconnect()
@ -407,23 +355,14 @@ class TelnetConnection(_Connection):
neighbors = (self._telnet.read_until(self._prompt_string).
split(b'\n')[1:-1])
if self._ap:
self._telnet.write('{}\n'.format(_ARP_CMD).encode('ascii'))
arp_result = (self._telnet.read_until(self._prompt_string).
split(b'\n')[1:-1])
self._telnet.write('{}\n'.format(_WL_CMD).encode('ascii'))
leases_result = (self._telnet.read_until(self._prompt_string).
split(b'\n')[1:-1])
self._telnet.write('{}\n'.format(_NVRAM_CMD).encode('ascii'))
nvram_result = (self._telnet.read_until(self._prompt_string).
split(b'\n')[1].split(b'<')[1:])
else:
arp_result = ['']
nvram_result = ['']
self._telnet.write('{}\n'.format(_LEASES_CMD).encode('ascii'))
leases_result = (self._telnet.read_until(self._prompt_string).
split(b'\n')[1:-1])
return AsusWrtResult(neighbors, leases_result, arp_result,
nvram_result)
return AsusWrtResult(neighbors, leases_result)
except EOFError:
_LOGGER.error("Unexpected response from router")
self.disconnect()

View File

@ -52,8 +52,7 @@ class BboxDeviceScanner(DeviceScanner):
if filter_named:
return filter_named[0]
else:
return None
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):

View File

@ -87,21 +87,20 @@ class CiscoDeviceScanner(DeviceScanner):
lines_result = lines_result[2:]
for line in lines_result:
if len(line.split()) is 6:
parts = line.split()
if len(parts) != 6:
continue
parts = line.split()
if len(parts) != 6:
continue
# ['Internet', '10.10.11.1', '-', '0027.d32d.0123', 'ARPA',
# 'GigabitEthernet0']
age = parts[2]
hw_addr = parts[3]
# ['Internet', '10.10.11.1', '-', '0027.d32d.0123', 'ARPA',
# 'GigabitEthernet0']
age = parts[2]
hw_addr = parts[3]
if age != "-":
mac = _parse_cisco_mac_address(hw_addr)
age = int(age)
if age < 1:
last_results.append(mac)
if age != "-":
mac = _parse_cisco_mac_address(hw_addr)
age = int(age)
if age < 1:
last_results.append(mac)
self.last_results = last_results
return True

View File

@ -83,11 +83,14 @@ class LinksysSmartWifiDeviceScanner(DeviceScanner):
if not connections:
_LOGGER.debug("Device %s is not connected", mac)
continue
name = device["friendlyName"]
properties = device["properties"]
for prop in properties:
name = None
for prop in device["properties"]:
if prop["name"] == "userDeviceName":
name = prop["value"]
if not name:
name = device.get("friendlyName", device["deviceID"])
_LOGGER.debug("Device %s is connected", mac)
self.last_results[mac] = name
except (KeyError, IndexError):

View File

@ -94,21 +94,20 @@ class LocativeView(HomeAssistantView):
partial(self.see, dev_id=device,
location_name=location_name, gps=gps_location))
return 'Setting location to not home'
else:
# Ignore the message if it is telling us to exit a zone that we
# aren't currently in. This occurs when a zone is entered
# before the previous zone was exited. The enter message will
# be sent first, then the exit message will be sent second.
return 'Ignoring exit from {} (already in {})'.format(
location_name, current_state)
# Ignore the message if it is telling us to exit a zone that we
# aren't currently in. This occurs when a zone is entered
# before the previous zone was exited. The enter message will
# be sent first, then the exit message will be sent second.
return 'Ignoring exit from {} (already in {})'.format(
location_name, current_state)
elif direction == 'test':
# In the app, a test message can be sent. Just return something to
# the user to let them know that it works.
return 'Received test message.'
else:
_LOGGER.error('Received unidentified message from Locative: %s',
direction)
return ('Received unidentified message: {}'.format(direction),
HTTP_UNPROCESSABLE_ENTITY)
_LOGGER.error('Received unidentified message from Locative: %s',
direction)
return ('Received unidentified message: {}'.format(direction),
HTTP_UNPROCESSABLE_ENTITY)

View File

@ -95,8 +95,7 @@ class NmapDeviceScanner(DeviceScanner):
if filter_named:
return filter_named[0]
else:
return None
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):

View File

@ -131,8 +131,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
plaintext_payload = decrypt_payload(topic, data['data'])
if plaintext_payload is None:
return None
else:
return validate_payload(topic, plaintext_payload, data_type)
return validate_payload(topic, plaintext_payload, data_type)
if not isinstance(data, dict) or data.get('_type') != data_type:
_LOGGER.debug("Skipping %s update for following data "

View File

@ -90,8 +90,7 @@ class TadoDeviceScanner(DeviceScanner):
if filter_named:
return filter_named[0]
else:
return None
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):

View File

@ -73,8 +73,8 @@ class TomatoDeviceScanner(DeviceScanner):
if not filter_named or not filter_named[0]:
return None
else:
return filter_named[0]
return filter_named[0]
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_tomato_info(self):

View File

@ -9,7 +9,7 @@ import hashlib
import logging
import re
import threading
from datetime import timedelta
from datetime import timedelta, datetime
import requests
import voluptuous as vol
@ -33,8 +33,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def get_scanner(hass, config):
"""Validate the configuration and return a TP-Link scanner."""
for cls in [Tplink4DeviceScanner, Tplink3DeviceScanner,
Tplink2DeviceScanner, TplinkDeviceScanner]:
for cls in [Tplink5DeviceScanner, Tplink4DeviceScanner,
Tplink3DeviceScanner, Tplink2DeviceScanner,
TplinkDeviceScanner]:
scanner = cls(config[DOMAIN])
if scanner.success_init:
return scanner
@ -234,10 +235,9 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
self.stok = ''
self.sysauth = ''
return False
else:
_LOGGER.error(
"An unknown error happened while fetching data")
return False
_LOGGER.error(
"An unknown error happened while fetching data")
return False
except ValueError:
_LOGGER.error("Router didn't respond with JSON. "
"Check if credentials are correct")
@ -350,3 +350,83 @@ class Tplink4DeviceScanner(TplinkDeviceScanner):
self.last_results = [mac.replace("-", ":") for mac in mac_results]
return True
class Tplink5DeviceScanner(TplinkDeviceScanner):
"""This class queries a TP-Link EAP-225 AP with newer TP-Link FW."""
def scan_devices(self):
"""Scan for new devices and return a list with found MAC IDs."""
self._update_info()
return self.last_results.keys()
# pylint: disable=no-self-use
def get_device_name(self, device):
"""Get firmware doesn't save the name of the wireless device."""
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the TP-Link AP is up to date.
Return boolean if scanning successful.
"""
with self.lock:
_LOGGER.info("Loading wireless clients...")
base_url = 'http://{}'.format(self.host)
header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12;"
" rv:53.0) Gecko/20100101 Firefox/53.0",
"Accept": "application/json, text/javascript, */*; q=0.01",
"Accept-Language": "Accept-Language: en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Content-Type": "application/x-www-form-urlencoded; "
"charset=UTF-8",
"X-Requested-With": "XMLHttpRequest",
"Referer": "http://" + self.host + "/",
"Connection": "keep-alive",
"Pragma": "no-cache",
"Cache-Control": "no-cache"
}
password_md5 = hashlib.md5(self.password).hexdigest().upper()
# create a session to handle cookie easier
session = requests.session()
session.get(base_url, headers=header)
login_data = {"username": self.username, "password": password_md5}
session.post(base_url, login_data, headers=header)
# a timestamp is required to be sent as get parameter
timestamp = int(datetime.now().timestamp() * 1e3)
client_list_url = '{}/data/monitor.client.client.json'.format(
base_url)
get_params = {
'operation': 'load',
'_': timestamp
}
response = session.get(client_list_url,
headers=header,
params=get_params)
session.close()
try:
list_of_devices = response.json()
except ValueError:
_LOGGER.error("AP didn't respond with JSON. "
"Check if credentials are correct.")
return False
if list_of_devices:
self.last_results = {
device['MAC'].replace('-', ':'): device['DeviceName']
for device in list_of_devices['data']
}
return True
return False

View File

@ -12,11 +12,10 @@ import aiohttp
import async_timeout
import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
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
from homeassistant.const import CONF_HOST
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -25,12 +24,9 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_IP = '192.168.0.1'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_HOST, default=DEFAULT_IP): cv.string,
})
CMD_LOGIN = 15
CMD_LOGOUT = 16
CMD_DEVICES = 123
@ -38,7 +34,7 @@ CMD_DEVICES = 123
def async_get_scanner(hass, config):
"""Return the UPC device scanner."""
scanner = UPCDeviceScanner(hass, config[DOMAIN])
success_init = yield from scanner.async_login()
success_init = yield from scanner.async_initialize_token()
return scanner if success_init else None
@ -50,7 +46,6 @@ class UPCDeviceScanner(DeviceScanner):
"""Initialize the scanner."""
self.hass = hass
self.host = config[CONF_HOST]
self.password = config[CONF_PASSWORD]
self.data = {}
self.token = None
@ -65,21 +60,12 @@ class UPCDeviceScanner(DeviceScanner):
self.websession = async_get_clientsession(hass)
@asyncio.coroutine
def async_logout(event):
"""Logout from upc connect box."""
yield from self._async_ws_function(CMD_LOGOUT)
self.token = None
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, async_logout)
@asyncio.coroutine
def async_scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
if self.token is None:
reconnect = yield from self.async_login()
if not reconnect:
token_initialized = yield from self.async_initialize_token()
if not token_initialized:
_LOGGER.error("Not connected to %s", self.host)
return []
@ -95,55 +81,42 @@ class UPCDeviceScanner(DeviceScanner):
@asyncio.coroutine
def async_get_device_name(self, device):
"""Ge the firmware doesn't save the name of the wireless device."""
"""The firmware doesn't save the name of the wireless device."""
return None
@asyncio.coroutine
def async_login(self):
"""Login into firmware and get first token."""
def async_initialize_token(self):
"""Get first token."""
try:
# get first token
with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from self.websession.get(
"http://{}/common_page/login.html".format(self.host)
"http://{}/common_page/login.html".format(self.host),
headers=self.headers
)
yield from response.text()
self.token = response.cookies['sessionToken'].value
# login
data = yield from self._async_ws_function(CMD_LOGIN, {
'Username': 'NULL',
'Password': self.password,
})
# Successful?
return data is not None
return True
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Can not load login page from %s", self.host)
return False
@asyncio.coroutine
def _async_ws_function(self, function, additional_form=None):
def _async_ws_function(self, function):
"""Execute a command on UPC firmware webservice."""
form_data = {
'token': self.token,
'fun': function
}
if additional_form:
form_data.update(additional_form)
redirects = function != CMD_DEVICES
try:
with async_timeout.timeout(10, loop=self.hass.loop):
# The 'token' parameter has to be first, and 'fun' second
# or the UPC firmware will return an error
response = yield from self.websession.post(
"http://{}/xml/getter.xml".format(self.host),
data=form_data,
data="token={}&fun={}".format(self.token, function),
headers=self.headers,
allow_redirects=redirects
allow_redirects=False
)
# error?

View File

@ -101,10 +101,10 @@ class XiaomiDeviceScanner(DeviceScanner):
result = _retrieve_list(self.host, self.token)
if result:
return result
else:
_LOGGER.info("Refreshing token and retrying device list refresh")
self.token = _get_token(self.host, self.username, self.password)
return _retrieve_list(self.host, self.token)
_LOGGER.info("Refreshing token and retrying device list refresh")
self.token = _get_token(self.host, self.username, self.password)
return _retrieve_list(self.host, self.token)
def _store_result(self, result):
"""Extract and store the device list in self.last_results."""

View File

@ -32,6 +32,7 @@ SERVICE_HASS_IOS_APP = 'hass_ios'
SERVICE_IKEA_TRADFRI = 'ikea_tradfri'
SERVICE_HASSIO = 'hassio'
SERVICE_AXIS = 'axis'
SERVICE_APPLE_TV = 'apple_tv'
SERVICE_HANDLERS = {
SERVICE_HASS_IOS_APP: ('ios', None),
@ -40,6 +41,7 @@ SERVICE_HANDLERS = {
SERVICE_IKEA_TRADFRI: ('tradfri', None),
SERVICE_HASSIO: ('hassio', None),
SERVICE_AXIS: ('axis', None),
SERVICE_APPLE_TV: ('apple_tv', None),
'philips_hue': ('light', 'hue'),
'google_cast': ('media_player', 'cast'),
'panasonic_viera': ('media_player', 'panasonic_viera'),
@ -52,7 +54,6 @@ SERVICE_HANDLERS = {
'denonavr': ('media_player', 'denonavr'),
'samsung_tv': ('media_player', 'samsungtv'),
'yeelight': ('light', 'yeelight'),
'apple_tv': ('media_player', 'apple_tv'),
'frontier_silicon': ('media_player', 'frontier_silicon'),
'openhome': ('media_player', 'openhome'),
'harmony': ('remote', 'harmony'),

View File

@ -1,4 +1,8 @@
"""Parent component for Dyson Pure Cool Link devices."""
"""Parent component for Dyson Pure Cool Link devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/dyson/
"""
import logging
@ -9,7 +13,7 @@ from homeassistant.helpers import discovery
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT, \
CONF_DEVICES
REQUIREMENTS = ['libpurecoollink==0.1.5']
REQUIREMENTS = ['libpurecoollink==0.2.0']
_LOGGER = logging.getLogger(__name__)

View File

@ -1,4 +1,8 @@
"""Support for Dyson Pure Cool link fan."""
"""Support for Dyson Pure Cool link fan.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/fan.dyson/
"""
import logging
import asyncio
from os import path
@ -79,9 +83,11 @@ class DysonPureCoolLinkDevice(FanEntity):
def on_message(self, message):
"""Called when new messages received from the fan."""
_LOGGER.debug(
"Message received for fan device %s : %s", self.name, message)
self.schedule_update_ha_state()
from libpurecoollink.dyson import DysonState
if isinstance(message, DysonState):
_LOGGER.debug("Message received for fan device %s : %s", self.name,
message)
self.schedule_update_ha_state()
@property
def should_poll(self):
@ -157,8 +163,7 @@ class DysonPureCoolLinkDevice(FanEntity):
from libpurecoollink.const import FanSpeed
if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value:
return self._device.state.speed
else:
return int(self._device.state.speed)
return int(self._device.state.speed)
return None
@property

View File

@ -35,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
insteonhub = hass.data['insteon_local']
conf_fans = config_from_file(hass.config.path(INSTEON_LOCAL_FANS_CONF))
if len(conf_fans):
if conf_fans:
for device_id in conf_fans:
setup_fan(device_id, conf_fans[device_id], insteonhub, hass,
add_devices)

View File

@ -33,10 +33,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkFanDevice(WinkDevice, FanEntity):
"""Representation of a Wink fan."""
def __init__(self, wink, hass):
"""Initialize the fan."""
super().__init__(wink, hass)
@asyncio.coroutine
def async_added_to_hass(self):
"""Callback when entity is added to hass."""

View File

@ -6,10 +6,13 @@ import logging
import os
from aiohttp import web
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.config import find_config_file, load_yaml_config_file
from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED
from homeassistant.core import callback
from homeassistant.const import HTTP_NOT_FOUND
from homeassistant.components import api, group
from homeassistant.components import api
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.auth import is_trusted_ip
from homeassistant.components.http.const import KEY_DEVELOPMENT
@ -23,6 +26,8 @@ URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html'
STATIC_PATH = os.path.join(os.path.dirname(__file__), 'www_static/')
ATTR_THEMES = 'themes'
DEFAULT_THEME_COLOR = '#03A9F4'
MANIFEST_JSON = {
'background_color': '#FFFFFF',
'description': 'Open-source home automation platform running on Python 3.',
@ -33,7 +38,7 @@ MANIFEST_JSON = {
'name': 'Home Assistant',
'short_name': 'Assistant',
'start_url': '/',
'theme_color': '#03A9F4'
'theme_color': DEFAULT_THEME_COLOR
}
for size in (192, 384, 512, 1024):
@ -45,11 +50,30 @@ for size in (192, 384, 512, 1024):
DATA_PANELS = 'frontend_panels'
DATA_INDEX_VIEW = 'frontend_index_view'
DATA_THEMES = 'frontend_themes'
DATA_DEFAULT_THEME = 'frontend_default_theme'
DEFAULT_THEME = 'default'
PRIMARY_COLOR = 'primary-color'
# To keep track we don't register a component twice (gives a warning)
_REGISTERED_COMPONENTS = set()
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(ATTR_THEMES): vol.Schema({
cv.string: {cv.string: cv.string}
}),
}),
}, extra=vol.ALLOW_EXTRA)
SERVICE_SET_THEME = 'set_theme'
SERVICE_RELOAD_THEMES = 'reload_themes'
SERVICE_SET_THEME_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
})
def register_built_in_panel(hass, component_name, sidebar_title=None,
sidebar_icon=None, url_path=None, config=None):
@ -138,6 +162,8 @@ def register_panel(hass, component_name, path, md5=None, sidebar_title=None,
if index_view:
hass.http.app.router.add_route(
'get', '/{}'.format(url_path), index_view.get)
hass.http.app.router.add_route(
'get', '/{}/{{extra:.+}}'.format(url_path), index_view.get)
def add_manifest_json_key(key, val):
@ -172,20 +198,81 @@ def setup(hass, config):
# Now register their urls.
if DATA_PANELS in hass.data:
for url_path in hass.data[DATA_PANELS]:
hass.http.app.router.add_route('get', '/{}'.format(url_path),
index_view.get)
hass.http.app.router.add_route(
'get', '/{}'.format(url_path), index_view.get)
hass.http.app.router.add_route(
'get', '/{}/{{extra:.+}}'.format(url_path), index_view.get)
else:
hass.data[DATA_PANELS] = {}
register_built_in_panel(hass, 'map', 'Map', 'mdi:account-location')
for panel in ('dev-event', 'dev-info', 'dev-service', 'dev-state',
'dev-template'):
'dev-template', 'kiosk'):
register_built_in_panel(hass, panel)
themes = config.get(DOMAIN, {}).get(ATTR_THEMES)
setup_themes(hass, themes)
return True
def setup_themes(hass, themes):
"""Set up themes data and services."""
hass.http.register_view(ThemesView)
hass.data[DATA_DEFAULT_THEME] = DEFAULT_THEME
if themes is None:
hass.data[DATA_THEMES] = {}
return
hass.data[DATA_THEMES] = themes
@callback
def update_theme_and_fire_event():
"""Update theme_color in manifest."""
name = hass.data[DATA_DEFAULT_THEME]
themes = hass.data[DATA_THEMES]
if name != DEFAULT_THEME and PRIMARY_COLOR in themes[name]:
MANIFEST_JSON['theme_color'] = themes[name][PRIMARY_COLOR]
else:
MANIFEST_JSON['theme_color'] = DEFAULT_THEME_COLOR
hass.bus.async_fire(EVENT_THEMES_UPDATED, {
'themes': themes,
'default_theme': name,
})
@callback
def set_theme(call):
"""Set backend-prefered theme."""
data = call.data
name = data[CONF_NAME]
if name == DEFAULT_THEME or name in hass.data[DATA_THEMES]:
_LOGGER.info("Theme %s set as default", name)
hass.data[DATA_DEFAULT_THEME] = name
update_theme_and_fire_event()
else:
_LOGGER.warning("Theme %s is not defined.", name)
@callback
def reload_themes(_):
"""Reload themes."""
path = find_config_file(hass.config.config_dir)
new_themes = load_yaml_config_file(path)[DOMAIN].get(ATTR_THEMES, {})
hass.data[DATA_THEMES] = new_themes
if hass.data[DATA_DEFAULT_THEME] not in new_themes:
hass.data[DATA_DEFAULT_THEME] = DEFAULT_THEME
update_theme_and_fire_event()
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_SET_THEME,
set_theme,
descriptions[SERVICE_SET_THEME],
SERVICE_SET_THEME_SCHEMA)
hass.services.register(DOMAIN, SERVICE_RELOAD_THEMES, reload_themes,
descriptions[SERVICE_RELOAD_THEMES])
class BootstrapView(HomeAssistantView):
"""View to bootstrap frontend with all needed data."""
@ -212,7 +299,7 @@ class IndexView(HomeAssistantView):
url = '/'
name = 'frontend:index'
requires_auth = False
extra_urls = ['/states', '/states/{entity_id}']
extra_urls = ['/states', '/states/{extra}']
def __init__(self):
"""Initialize the frontend view."""
@ -225,17 +312,10 @@ class IndexView(HomeAssistantView):
)
@asyncio.coroutine
def get(self, request, entity_id=None):
def get(self, request, extra=None):
"""Serve the index view."""
hass = request.app['hass']
if entity_id is not None:
state = hass.states.get(entity_id)
if (not state or state.domain != 'group' or
not state.attributes.get(group.ATTR_VIEW)):
return self.json_message('Entity not found', HTTP_NOT_FOUND)
if request.app[KEY_DEVELOPMENT]:
core_url = '/static/home-assistant-polymer/build/core.js'
compatibility_url = \
@ -295,3 +375,21 @@ class ManifestJSONView(HomeAssistantView):
"""Return the manifest.json."""
msg = json.dumps(MANIFEST_JSON, sort_keys=True).encode('UTF-8')
return web.Response(body=msg, content_type="application/manifest+json")
class ThemesView(HomeAssistantView):
"""View to return defined themes."""
requires_auth = False
url = '/api/themes'
name = 'api:themes'
@callback
def get(self, request):
"""Return themes."""
hass = request.app['hass']
return self.json({
'themes': hass.data[DATA_THEMES],
'default_theme': hass.data[DATA_DEFAULT_THEME],
})

View File

@ -0,0 +1,11 @@
# Describes the format for available frontend services
set_theme:
description: Set a theme unless the client selected per-device theme.
fields:
name:
description: Name of a predefined theme or 'default'.
example: 'light'
reload_themes:
description: Reload themes from yaml config.

View File

@ -3,21 +3,21 @@
FINGERPRINTS = {
"compatibility.js": "8e4c44b5f4288cc48ec1ba94a9bec812",
"core.js": "d4a7cb8c80c62b536764e0e81385f6aa",
"frontend.html": "bdcde4695ce32595a9e1d813b9d7c5f9",
"mdi.html": "c92bd28c434865d6cabb34cd3c0a3e4c",
"frontend.html": "a7d4cb8260e8094342b5bd8c36c4bf5b",
"mdi.html": "e91f61a039ed0a9936e7ee5360da3870",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-automation.html": "4f98839bb082885657bbcd0ac04fc680",
"panels/ha-panel-automation.html": "72a5c1856cece8d9246328e84185ab0b",
"panels/ha-panel-config.html": "76853de505d173e82249bf605eb73505",
"panels/ha-panel-dev-event.html": "4886c821235492b1b92739b580d21c61",
"panels/ha-panel-dev-info.html": "24e888ec7a8acd0c395b34396e9001bc",
"panels/ha-panel-dev-service.html": "92c6be30b1af95791d5a6429df505852",
"panels/ha-panel-dev-service.html": "ac2c50e486927dc4443e93d79f08c06e",
"panels/ha-panel-dev-state.html": "8f1a27c04db6329d31cfcc7d0d6a0869",
"panels/ha-panel-dev-template.html": "d33a55b937b50cdfe8b6fae81f70a139",
"panels/ha-panel-hassio.html": "9474ba65077371622f21ed9a30cf5229",
"panels/ha-panel-dev-template.html": "82cd543177c417e5c6612e07df851e6b",
"panels/ha-panel-hassio.html": "262d31efd9add719e0325da5cf79a096",
"panels/ha-panel-history.html": "35177e2046c9a4191c8f51f8160255ce",
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
"panels/ha-panel-kiosk.html": "2ac2df41bd447600692a0054892fc094",
"panels/ha-panel-logbook.html": "7c45bd41c146ec38b9938b8a5188bb0d",
"panels/ha-panel-map.html": "0ba605729197c4724ecc7310b08f7050",
"panels/ha-panel-zwave.html": "2ea2223339d1d2faff478751c2927d11",
"websocket_test.html": "575de64b431fe11c3785bf96d7813450"
"panels/ha-panel-map.html": "d3dae1400ec4e4cd7681d2aa79131d55",
"panels/ha-panel-zwave.html": "2ea2223339d1d2faff478751c2927d11"
}

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 6e30534e2dc906d29ce497bf0a2eacb18e36f9c5
Subproject commit 7c079dc01f03d7599762df1ac8c6eab8e4b90004

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-hassio"><template><style>[hidden]{display:none!important}</style><hassio-main hass="[[hass]]" narrow="[[narrow]]" show-menu="[[showMenu]]"></hassio-main></template></dom-module><script>Polymer({is:"ha-panel-hassio",properties:{hass:{type:Object},narrow:{type:Boolean},showMenu:{type:Boolean,value:!1},loaded:{type:Boolean,value:!1}},attached:function(){window.HASS_DEV||this.importHref("/api/hassio/panel",null,function(){alert("Failed to load the Hass.io panel from supervisor.")})}})</script></body></html>
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-hassio"><template><style>[hidden]{display:none!important}</style><hassio-main hass="[[hass]]" narrow="[[narrow]]" show-menu="[[showMenu]]" route="[[route]]"></hassio-main></template></dom-module><script>Polymer({is:"ha-panel-hassio",properties:{hass:Object,narrow:Boolean,showMenu:Boolean,route:Object,loaded:{type:Boolean,value:!1}},attached:function(){window.HASS_DEV||this.importHref("/api/hassio/panel",null,function(){alert("Failed to load the Hass.io panel from supervisor.")})}})</script></body></html>

View File

@ -0,0 +1 @@
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-kiosk"><template><partial-cards id="kiosk-states" hass="[[hass]]" show-menu="" route="[[route]]" panel-visible=""></partial-cards></template></dom-module><script>Polymer({is:"ha-panel-kiosk",properties:{hass:Object,route:Object}})</script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,125 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket debug</title>
<style>
.controls {
display: flex;
flex-direction: row;
}
.controls textarea {
height: 160px;
min-width: 400px;
margin-right: 24px;
}
</style>
</head>
<body>
<div class='controls'>
<textarea id="messageinput">
{
"id": 1, "type": "subscribe_events", "event_type": "state_changed"
}
</textarea>
<pre>
Examples:
{
"id": 2, "type": "subscribe_events", "event_type": "state_changed"
}
{
"id": 3, "type": "call_service", "domain": "light", "service": "turn_off"
}
{
"id": 4, "type": "unsubscribe_events", "subscription": 2
}
{
"id": 5, "type": "get_states"
}
{
"id": 6, "type": "get_config"
}
{
"id": 7, "type": "get_services"
}
{
"id": 8, "type": "get_panels"
}
</pre>
</div>
<div>
<button type="button" onclick="openSocket();" >Open</button>
<button type="button" onclick="send();" >Send</button>
<button type="button" onclick="closeSocket();" >Close</button>
</div>
<!-- Server responses get written here -->
<pre id="messages"></pre>
<!-- Script to utilise the WebSocket -->
<script type="text/javascript">
var webSocket;
var messages = document.getElementById("messages");
function openSocket(){
var isOpen = false;
// Ensures only one connection is open at a time
if(webSocket !== undefined && webSocket.readyState !== WebSocket.CLOSED){
writeResponse("WebSocket is already opened.");
return;
}
// Create a new instance of the websocket
webSocket = new WebSocket("ws://localhost:8123/api/websocket");
/**
* Binds functions to the listeners for the websocket.
*/
webSocket.onopen = function(event){
if (!isOpen) {
isOpen = true;
writeResponse('Connection opened');
}
// For reasons I can't determine, onopen gets called twice
// and the first time event.data is undefined.
// Leave a comment if you know the answer.
if(event.data === undefined)
return;
writeResponse(event.data);
};
webSocket.onmessage = function(event){
writeResponse(event.data);
};
webSocket.onclose = function(event){
writeResponse("Connection closed");
};
}
/**
* Sends the value of the text input to the server
*/
function send(){
var text = document.getElementById("messageinput").value;
webSocket.send(text);
}
function closeSocket(){
webSocket.close();
}
function writeResponse(text){
messages.innerHTML += "\n" + text;
}
openSocket();
</script>
</body>
</html>

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