Merge pull request #6199 from home-assistant/release-0-39

0.39
pull/5920/head^2
Paulus Schoutsen 2017-02-25 14:36:17 -08:00 committed by GitHub
commit a19a285bb0
349 changed files with 10495 additions and 3239 deletions

View File

@ -41,6 +41,9 @@ omit =
homeassistant/components/insteon_local.py
homeassistant/components/*/insteon_local.py
homeassistant/components/insteon_plm.py
homeassistant/components/*/insteon_plm.py
homeassistant/components/ios.py
homeassistant/components/*/ios.py
@ -88,6 +91,9 @@ omit =
homeassistant/components/verisure.py
homeassistant/components/*/verisure.py
homeassistant/components/volvooncall.py
homeassistant/components/*/volvooncall.py
homeassistant/components/*/webostv.py
homeassistant/components/wemo.py
@ -149,10 +155,12 @@ omit =
homeassistant/components/climate/heatmiser.py
homeassistant/components/climate/homematic.py
homeassistant/components/climate/knx.py
homeassistant/components/climate/oem.py
homeassistant/components/climate/proliphix.py
homeassistant/components/climate/radiotherm.py
homeassistant/components/cover/garadget.py
homeassistant/components/cover/homematic.py
homeassistant/components/cover/myq.py
homeassistant/components/cover/rpi_gpio.py
homeassistant/components/cover/scsgate.py
homeassistant/components/cover/wink.py
@ -181,9 +189,7 @@ omit =
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/trackr.py
homeassistant/components/device_tracker/ubus.py
homeassistant/components/device_tracker/volvooncall.py
homeassistant/components/device_tracker/xiaomi.py
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/emoncms_history.py
homeassistant/components/emulated_hue/upnp.py
@ -207,6 +213,7 @@ omit =
homeassistant/components/light/tikteck.py
homeassistant/components/light/x10.py
homeassistant/components/light/yeelight.py
homeassistant/components/light/yeelightsunflower.py
homeassistant/components/light/piglow.py
homeassistant/components/light/zengge.py
homeassistant/components/lirc.py
@ -216,6 +223,7 @@ omit =
homeassistant/components/media_player/aquostv.py
homeassistant/components/media_player/braviatv.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/clementine.py
homeassistant/components/media_player/cmus.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/denonavr.py
@ -224,6 +232,7 @@ omit =
homeassistant/components/media_player/emby.py
homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/gpmdp.py
homeassistant/components/media_player/gstreamer.py
homeassistant/components/media_player/hdmi_cec.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
@ -233,6 +242,7 @@ omit =
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/nad.py
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/openhome.py
homeassistant/components/media_player/panasonic_viera.py
homeassistant/components/media_player/pandora.py
homeassistant/components/media_player/philips_js.py
@ -267,6 +277,7 @@ omit =
homeassistant/components/notify/pushbullet.py
homeassistant/components/notify/pushetta.py
homeassistant/components/notify/pushover.py
homeassistant/components/notify/pushsafer.py
homeassistant/components/notify/rest.py
homeassistant/components/notify/sendgrid.py
homeassistant/components/notify/simplepush.py
@ -281,6 +292,7 @@ omit =
homeassistant/components/notify/xmpp.py
homeassistant/components/nuimo_controller.py
homeassistant/components/remote/harmony.py
homeassistant/components/remote/itach.py
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/sensor/amcrest.py
homeassistant/components/sensor/arest.py
@ -299,13 +311,17 @@ omit =
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/dovado.py
homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/ebox.py
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/emoncms.py
homeassistant/components/sensor/fastdotcom.py
homeassistant/components/sensor/fedex.py
homeassistant/components/sensor/fido.py
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/fritzbox_callmonitor.py
homeassistant/components/sensor/fritzbox_netmonitor.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
homeassistant/components/sensor/gpsd.py
@ -334,6 +350,7 @@ omit =
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/pi_hole.py
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/pocketcasts.py
homeassistant/components/sensor/pvoutput.py
homeassistant/components/sensor/qnap.py
homeassistant/components/sensor/sabnzbd.py
@ -358,6 +375,7 @@ omit =
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/ups.py
homeassistant/components/sensor/usps.py
homeassistant/components/sensor/vasttrafik.py
homeassistant/components/sensor/waqi.py
@ -386,6 +404,7 @@ omit =
homeassistant/components/switch/tplink.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wake_on_lan.py
homeassistant/components/telegram_webhooks.py
homeassistant/components/thingspeak.py
homeassistant/components/tts/amazon_polly.py
homeassistant/components/tts/picotts.py

View File

@ -26,5 +26,5 @@ If the code does not interact with devices:
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
- [ ] Tests have been added to verify that the new code works.
[ex-requir]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L16
[ex-import]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L51
[ex-requir]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L14
[ex-import]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L54

View File

@ -75,7 +75,8 @@ Build home automation on top of your devices:
`Instapush <https://instapush.im>`__, `Notify My Android
(NMA) <http://www.notifymyandroid.com/>`__,
`PushBullet <https://www.pushbullet.com/>`__,
`PushOver <https://pushover.net/>`__, `Slack <https://slack.com/>`__,
`PushOver <https://pushover.net/>`__,
`Slack <https://slack.com/>`__,
`Telegram <https://telegram.org/>`__, `Join <http://joaoapps.com/join/>`__, and `Jabber
(XMPP) <http://xmpp.org>`__

View File

@ -16,6 +16,7 @@ import homeassistant.components as core_components
from homeassistant.components import persistent_notification
import homeassistant.config as conf_util
import homeassistant.core as core
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
import homeassistant.loader as loader
import homeassistant.util.package as pkg_util
from homeassistant.util.async import (
@ -166,7 +167,7 @@ def _async_setup_component(hass: core.HomeAssistant,
loader.set_component(domain, None)
return False
hass.config.components.append(component.DOMAIN)
hass.config.components.add(component.DOMAIN)
hass.bus.async_fire(
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}
@ -298,6 +299,10 @@ def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
# Load dependencies
for component in getattr(platform, 'DEPENDENCIES', []):
if component in loader.DEPENDENCY_BLACKLIST:
raise HomeAssistantError(
'{} is not allowed to be a dependency.'.format(component))
res = yield from async_setup_component(hass, component, config)
if not res:
_LOGGER.error(
@ -386,7 +391,7 @@ def async_from_config_dict(config: Dict[str, Any],
None, conf_util.process_ha_config_upgrade, hass)
if enable_log:
enable_logging(hass, verbose, log_rotate_days)
async_enable_logging(hass, verbose, log_rotate_days)
hass.config.skip_pip = skip_pip
if skip_pip:
@ -429,7 +434,13 @@ def async_from_config_dict(config: Dict[str, Any],
service.HASS = hass
# Setup the components
dependency_blacklist = loader.DEPENDENCY_BLACKLIST - set(components)
for domain in loader.load_order_components(components):
if domain in dependency_blacklist:
raise HomeAssistantError(
'{} is not allowed to be a dependency'.format(domain))
yield from _async_setup_component(hass, domain, config)
setup_lock.release()
@ -488,7 +499,7 @@ def async_from_config_file(config_path: str,
yield from hass.loop.run_in_executor(
None, mount_local_lib_path, config_dir)
enable_logging(hass, verbose, log_rotate_days)
async_enable_logging(hass, verbose, log_rotate_days)
try:
config_dict = yield from hass.loop.run_in_executor(
@ -503,11 +514,12 @@ def async_from_config_file(config_path: str,
return hass
def enable_logging(hass: core.HomeAssistant, verbose: bool=False,
log_rotate_days=None) -> None:
@core.callback
def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False,
log_rotate_days=None) -> None:
"""Setup the logging.
Async friendly.
This method must be run in the event loop.
"""
logging.basicConfig(level=logging.INFO)
fmt = ("%(asctime)s %(levelname)s (%(threadName)s) "
@ -537,10 +549,6 @@ def enable_logging(hass: core.HomeAssistant, verbose: bool=False,
except ImportError:
pass
# AsyncHandler allready exists?
if hass.data.get(core.DATA_ASYNCHANDLER):
return
# Log errors to a file if we have write access to file or config dir
err_log_path = hass.config.path(ERROR_LOG_FILENAME)
err_path_exists = os.path.isfile(err_log_path)
@ -561,7 +569,15 @@ def enable_logging(hass: core.HomeAssistant, verbose: bool=False,
err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt))
async_handler = AsyncHandler(hass.loop, err_handler)
hass.data[core.DATA_ASYNCHANDLER] = async_handler
@asyncio.coroutine
def async_stop_async_handler(event):
"""Cleanup async handler."""
logging.getLogger('').removeHandler(async_handler)
yield from async_handler.async_close(blocking=True)
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_CLOSE, async_stop_async_handler)
logger = logging.getLogger('')
logger.addHandler(async_handler)

View File

@ -156,10 +156,18 @@ def async_setup(hass, config):
return
try:
yield from conf_util.async_check_ha_config_file(hass)
errors = yield from conf_util.async_check_ha_config_file(hass)
except HomeAssistantError:
return
if errors:
notif = get_component('persistent_notification')
_LOGGER.error(errors)
notif.async_create(
hass, "Config error. See dev-info panel for details.",
"Config validating", "{0}.check_config".format(ha.DOMAIN))
return
if call.service == SERVICE_HOMEASSISTANT_RESTART:
hass.async_add_job(hass.async_stop(RESTART_EXIT_CODE))

View File

@ -4,16 +4,20 @@ Support for Envisalink-based alarm control panels (Honeywell/DSC).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.envisalink/
"""
from os import path
import asyncio
import logging
import os
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.helpers.config_validation as cv
from homeassistant.config import load_yaml_config_file
from homeassistant.components.envisalink import (
EVL_CONTROLLER, EnvisalinkDevice, PARTITION_SCHEMA, CONF_CODE, CONF_PANIC,
CONF_PARTITIONNAME, SIGNAL_PARTITION_UPDATE, SIGNAL_KEYPAD_UPDATE)
DATA_EVL, EnvisalinkDevice, PARTITION_SCHEMA, CONF_CODE, CONF_PANIC,
CONF_PARTITIONNAME, SIGNAL_KEYPAD_UPDATE, SIGNAL_PARTITION_UPDATE)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_UNKNOWN, STATE_ALARM_TRIGGERED, STATE_ALARM_PENDING, ATTR_ENTITY_ID)
@ -22,8 +26,6 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['envisalink']
DEVICES = []
SERVICE_ALARM_KEYPRESS = 'envisalink_alarm_keypress'
ATTR_KEYPRESS = 'keypress'
ALARM_KEYPRESS_SCHEMA = vol.Schema({
@ -32,68 +34,72 @@ ALARM_KEYPRESS_SCHEMA = vol.Schema({
})
def alarm_keypress_handler(service):
"""Map services to methods on Alarm."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
keypress = service.data.get(ATTR_KEYPRESS)
_target_devices = [device for device in DEVICES
if device.entity_id in entity_ids]
for device in _target_devices:
EnvisalinkAlarm.alarm_keypress(device, keypress)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Perform the setup for Envisalink alarm panels."""
_configured_partitions = discovery_info['partitions']
_code = discovery_info[CONF_CODE]
_panic_type = discovery_info[CONF_PANIC]
for part_num in _configured_partitions:
_device_config_data = PARTITION_SCHEMA(
_configured_partitions[part_num])
_device = EnvisalinkAlarm(
part_num,
_device_config_data[CONF_PARTITIONNAME],
_code,
_panic_type,
EVL_CONTROLLER.alarm_state['partition'][part_num],
EVL_CONTROLLER)
DEVICES.append(_device)
configured_partitions = discovery_info['partitions']
code = discovery_info[CONF_CODE]
panic_type = discovery_info[CONF_PANIC]
add_devices(DEVICES)
devices = []
for part_num in configured_partitions:
device_config_data = PARTITION_SCHEMA(configured_partitions[part_num])
device = EnvisalinkAlarm(
hass,
part_num,
device_config_data[CONF_PARTITIONNAME],
code,
panic_type,
hass.data[DATA_EVL].alarm_state['partition'][part_num],
hass.data[DATA_EVL]
)
devices.append(device)
yield from async_add_devices(devices)
@callback
def alarm_keypress_handler(service):
"""Map services to methods on Alarm."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
keypress = service.data.get(ATTR_KEYPRESS)
target_devices = [device for device in devices
if device.entity_id in entity_ids]
for device in target_devices:
device.async_alarm_keypress(keypress)
# Register Envisalink specific services
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
hass.services.async_register(
alarm.DOMAIN, SERVICE_ALARM_KEYPRESS, alarm_keypress_handler,
descriptions.get(SERVICE_ALARM_KEYPRESS), schema=ALARM_KEYPRESS_SCHEMA)
hass.services.register(alarm.DOMAIN, SERVICE_ALARM_KEYPRESS,
alarm_keypress_handler,
descriptions.get(SERVICE_ALARM_KEYPRESS),
schema=ALARM_KEYPRESS_SCHEMA)
return True
class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
"""Representation of an Envisalink-based alarm panel."""
def __init__(self, partition_number, alarm_name, code, panic_type, info,
controller):
def __init__(self, hass, partition_number, alarm_name, code, panic_type,
info, controller):
"""Initialize the alarm panel."""
from pydispatch import dispatcher
self._partition_number = partition_number
self._code = code
self._panic_type = panic_type
_LOGGER.debug("Setting up alarm: %s", alarm_name)
EnvisalinkDevice.__init__(self, alarm_name, info, controller)
dispatcher.connect(
self._update_callback, signal=SIGNAL_PARTITION_UPDATE,
sender=dispatcher.Any)
dispatcher.connect(
self._update_callback, signal=SIGNAL_KEYPAD_UPDATE,
sender=dispatcher.Any)
_LOGGER.debug("Setting up alarm: %s", alarm_name)
super().__init__(alarm_name, info, controller)
async_dispatcher_connect(
hass, SIGNAL_KEYPAD_UPDATE, self._update_callback)
async_dispatcher_connect(
hass, SIGNAL_PARTITION_UPDATE, self._update_callback)
@callback
def _update_callback(self, partition):
"""Update HA state, if needed."""
if partition is None or int(partition) == self._partition_number:
@ -126,39 +132,44 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
state = STATE_ALARM_DISARMED
return state
def alarm_disarm(self, code=None):
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
"""Send disarm command."""
if code:
EVL_CONTROLLER.disarm_partition(str(code),
self._partition_number)
self.hass.data[DATA_EVL].disarm_partition(
str(code), self._partition_number)
else:
EVL_CONTROLLER.disarm_partition(str(self._code),
self._partition_number)
self.hass.data[DATA_EVL].disarm_partition(
str(self._code), self._partition_number)
def alarm_arm_home(self, code=None):
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
if code:
EVL_CONTROLLER.arm_stay_partition(str(code),
self._partition_number)
self.hass.data[DATA_EVL].arm_stay_partition(
str(code), self._partition_number)
else:
EVL_CONTROLLER.arm_stay_partition(str(self._code),
self._partition_number)
self.hass.data[DATA_EVL].arm_stay_partition(
str(self._code), self._partition_number)
def alarm_arm_away(self, code=None):
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
if code:
EVL_CONTROLLER.arm_away_partition(str(code),
self._partition_number)
self.hass.data[DATA_EVL].arm_away_partition(
str(code), self._partition_number)
else:
EVL_CONTROLLER.arm_away_partition(str(self._code),
self._partition_number)
self.hass.data[DATA_EVL].arm_away_partition(
str(self._code), self._partition_number)
def alarm_trigger(self, code=None):
@asyncio.coroutine
def async_alarm_trigger(self, code=None):
"""Alarm trigger command. Will be used to trigger a panic alarm."""
EVL_CONTROLLER.panic_alarm(self._panic_type)
self.hass.data[DATA_EVL].panic_alarm(self._panic_type)
def alarm_keypress(self, keypress=None):
@callback
def async_alarm_keypress(self, keypress=None):
"""Send custom keypress."""
if keypress:
EVL_CONTROLLER.keypresses_to_partition(self._partition_number,
keypress)
self.hass.data[DATA_EVL].keypresses_to_partition(
self._partition_number, keypress)

View File

@ -4,10 +4,12 @@ This platform enables the possibility to control a MQTT alarm.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.mqtt/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.components.mqtt as mqtt
from homeassistant.const import (
@ -41,10 +43,10 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
})
def setup_platform(hass, config, add_devices, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup the MQTT platform."""
add_devices([MqttAlarm(
hass,
yield from async_add_devices([MqttAlarm(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_COMMAND_TOPIC),
@ -58,11 +60,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class MqttAlarm(alarm.AlarmControlPanel):
"""Representation of a MQTT alarm status."""
def __init__(self, hass, name, state_topic, command_topic, qos,
payload_disarm, payload_arm_home, payload_arm_away, code):
def __init__(self, name, state_topic, command_topic, qos, payload_disarm,
payload_arm_home, payload_arm_away, code):
"""Initalize the MQTT alarm panel."""
self._state = STATE_UNKNOWN
self._hass = hass
self._name = name
self._state_topic = state_topic
self._command_topic = command_topic
@ -72,6 +73,12 @@ class MqttAlarm(alarm.AlarmControlPanel):
self._payload_arm_away = payload_arm_away
self._code = code
def async_added_to_hass(self):
"""Subscribe mqtt events.
This method must be run in the event loop and returns a coroutine.
"""
@callback
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if payload not in (STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
@ -80,9 +87,10 @@ class MqttAlarm(alarm.AlarmControlPanel):
_LOGGER.warning('Received unexpected payload: %s', payload)
return
self._state = payload
self.update_ha_state()
self.hass.async_add_job(self.async_update_ha_state())
mqtt.subscribe(hass, self._state_topic, message_received, self._qos)
return mqtt.async_subscribe(
self.hass, self._state_topic, message_received, self._qos)
@property
def should_poll(self):
@ -104,26 +112,38 @@ class MqttAlarm(alarm.AlarmControlPanel):
"""One or more characters if code is defined."""
return None if self._code is None else '.+'
def alarm_disarm(self, code=None):
"""Send disarm command."""
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
"""Send disarm command.
This method is a coroutine.
"""
if not self._validate_code(code, 'disarming'):
return
mqtt.publish(self.hass, self._command_topic,
self._payload_disarm, self._qos)
mqtt.async_publish(
self.hass, self._command_topic, self._payload_disarm, self._qos)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
"""Send arm home command.
This method is a coroutine.
"""
if not self._validate_code(code, 'arming home'):
return
mqtt.publish(self.hass, self._command_topic,
self._payload_arm_home, self._qos)
mqtt.async_publish(
self.hass, self._command_topic, self._payload_arm_home, self._qos)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
"""Send arm away command.
This method is a coroutine.
"""
if not self._validate_code(code, 'arming away'):
return
mqtt.publish(self.hass, self._command_topic,
self._payload_arm_away, self._qos)
mqtt.async_publish(
self.hass, self._command_topic, self._payload_arm_away, self._qos)
def _validate_code(self, code, state):
"""Validate given code."""

View File

@ -12,16 +12,19 @@ import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN, CONF_CODE, CONF_NAME,
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY)
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
EVENT_HOMEASSISTANT_STOP)
import homeassistant.helpers.config_validation as cv
import homeassistant.loader as loader
REQUIREMENTS = ['https://github.com/w1ll1am23/simplisafe-python/archive/'
'586fede0e85fd69e56e516aaa8e97eb644ca8866.zip#'
'simplisafe-python==0.0.1']
REQUIREMENTS = ['simplisafe-python==1.0.2']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'SimpliSafe'
DOMAIN = 'simplisafe'
NOTIFICATION_ID = 'simplisafe_notification'
NOTIFICATION_TITLE = 'SimpliSafe Setup'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
@ -33,33 +36,44 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the SimpliSafe platform."""
from simplipy.api import SimpliSafeApiInterface, get_systems
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
add_devices([SimpliSafeAlarm(name, username, password, code)])
persistent_notification = loader.get_component('persistent_notification')
simplisafe = SimpliSafeApiInterface()
status = simplisafe.set_credentials(username, password)
if status:
hass.data[DOMAIN] = simplisafe
locations = get_systems(simplisafe)
for location in locations:
add_devices([SimpliSafeAlarm(location, name, code)])
else:
message = 'Failed to log into SimpliSafe. Check credentials.'
_LOGGER.error(message)
persistent_notification.create(
hass, message,
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False
def logout(event):
"""Logout of the SimpliSafe API."""
hass.data[DOMAIN].logout()
hass.bus.listen(EVENT_HOMEASSISTANT_STOP, logout)
class SimpliSafeAlarm(alarm.AlarmControlPanel):
"""Representation a SimpliSafe alarm."""
def __init__(self, name, username, password, code):
def __init__(self, simplisafe, name, code):
"""Initialize the SimpliSafe alarm."""
from simplisafe import SimpliSafe
self.simplisafe = SimpliSafe(username, password)
self.simplisafe = simplisafe
self._name = name
self._code = str(code) if code else None
self._id = self.simplisafe.get_id()
status = self.simplisafe.get_state()
if status == 'Off':
self._state = STATE_ALARM_DISARMED
elif status == 'Home':
self._state = STATE_ALARM_ARMED_HOME
elif status == 'Away':
self._state = STATE_ALARM_ARMED_AWAY
else:
self._state = STATE_UNKNOWN
@property
def name(self):
@ -67,7 +81,7 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
if self._name is not None:
return self._name
else:
return 'Alarm {}'.format(self._id)
return 'Alarm {}'.format(self.simplisafe.location_id())
@property
def code_format(self):
@ -77,21 +91,32 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
return self._state
status = self.simplisafe.state()
if status == 'Off':
state = STATE_ALARM_DISARMED
elif status == 'Home':
state = STATE_ALARM_ARMED_HOME
elif status == 'Away':
state = STATE_ALARM_ARMED_AWAY
else:
state = STATE_UNKNOWN
return state
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
'temperature': self.simplisafe.temperature(),
'co': self.simplisafe.carbon_monoxide(),
'fire': self.simplisafe.fire(),
'alarm': self.simplisafe.alarm(),
'last_event': self.simplisafe.last_event(),
'flood': self.simplisafe.flood()
}
def update(self):
"""Update alarm status."""
self.simplisafe.get_location()
status = self.simplisafe.get_state()
if status == 'Off':
self._state = STATE_ALARM_DISARMED
elif status == 'Home':
self._state = STATE_ALARM_ARMED_HOME
elif status == 'Away':
self._state = STATE_ALARM_ARMED_AWAY
else:
self._state = STATE_UNKNOWN
self.simplisafe.update()
def alarm_disarm(self, code=None):
"""Send disarm command."""

View File

@ -13,10 +13,9 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (CONF_ENTITY_ID, STATE_IDLE, CONF_NAME,
CONF_STATE, STATE_ON, STATE_OFF,
SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_TOGGLE, ATTR_ENTITY_ID)
from homeassistant.const import (
CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, STATE_ON, STATE_OFF,
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers import service, event
from homeassistant.util.async import run_callback_threadsafe
@ -32,13 +31,16 @@ CONF_NOTIFIERS = 'notifiers'
CONF_REPEAT = 'repeat'
CONF_SKIP_FIRST = 'skip_first'
DEFAULT_CAN_ACK = True
DEFAULT_SKIP_FIRST = False
ALERT_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_STATE, default=STATE_ON): cv.string,
vol.Required(CONF_REPEAT): vol.All(cv.ensure_list, [vol.Coerce(float)]),
vol.Required(CONF_CAN_ACK, default=True): cv.boolean,
vol.Required(CONF_SKIP_FIRST, default=False): cv.boolean,
vol.Required(CONF_CAN_ACK, default=DEFAULT_CAN_ACK): cv.boolean,
vol.Required(CONF_SKIP_FIRST, default=DEFAULT_SKIP_FIRST): cv.boolean,
vol.Required(CONF_NOTIFIERS): cv.ensure_list})
CONFIG_SCHEMA = vol.Schema({
@ -60,7 +62,8 @@ def is_on(hass, entity_id):
def turn_on(hass, entity_id):
"""Reset the alert."""
run_callback_threadsafe(hass.loop, async_turn_on, hass, entity_id)
run_callback_threadsafe(
hass.loop, async_turn_on, hass, entity_id).result()
@callback
@ -73,7 +76,8 @@ def async_turn_on(hass, entity_id):
def turn_off(hass, entity_id):
"""Acknowledge alert."""
run_callback_threadsafe(hass.loop, async_turn_off, hass, entity_id)
run_callback_threadsafe(
hass.loop, async_turn_off, hass, entity_id).result()
@callback
@ -99,7 +103,7 @@ def async_toggle(hass, entity_id):
@asyncio.coroutine
def async_setup(hass, config):
"""Setup alert component."""
"""Set up the Alert component."""
alerts = config.get(DOMAIN)
all_alerts = {}
@ -117,7 +121,7 @@ def async_setup(hass, config):
else:
yield from alert.async_turn_off()
# setup alerts
# Setup alerts
for entity_id, alert in alerts.items():
entity = Alert(hass, entity_id,
alert[CONF_NAME], alert[CONF_ENTITY_ID],
@ -126,13 +130,13 @@ def async_setup(hass, config):
alert[CONF_CAN_ACK])
all_alerts[entity.entity_id] = entity
# read descriptions
# Read descriptions
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
descriptions = descriptions.get(DOMAIN, {})
# setup service calls
# Setup service calls
hass.services.async_register(
DOMAIN, SERVICE_TURN_OFF, async_handle_alert_service,
descriptions.get(SERVICE_TURN_OFF), schema=ALERT_SERVICE_SCHEMA)
@ -171,8 +175,8 @@ class Alert(ToggleEntity):
self._cancel = None
self.entity_id = ENTITY_ID_FORMAT.format(entity_id)
event.async_track_state_change(hass, watched_entity_id,
self.watched_entity_change)
event.async_track_state_change(
hass, watched_entity_id, self.watched_entity_change)
@property
def name(self):
@ -201,7 +205,7 @@ class Alert(ToggleEntity):
@asyncio.coroutine
def watched_entity_change(self, entity, from_state, to_state):
"""Determine if the alert should start or stop."""
_LOGGER.debug('Watched entity (%s) has changed.', entity)
_LOGGER.debug("Watched entity (%s) has changed", entity)
if to_state.state == self._alert_state and not self._firing:
yield from self.begin_alerting()
if to_state.state != self._alert_state and self._firing:
@ -210,7 +214,7 @@ class Alert(ToggleEntity):
@asyncio.coroutine
def begin_alerting(self):
"""Begin the alert procedures."""
_LOGGER.debug('Beginning Alert: %s', self._name)
_LOGGER.debug("Beginning Alert: %s", self._name)
self._ack = False
self._firing = True
self._next_delay = 0
@ -225,7 +229,7 @@ class Alert(ToggleEntity):
@asyncio.coroutine
def end_alerting(self):
"""End the alert procedures."""
_LOGGER.debug('Ending Alert: %s', self._name)
_LOGGER.debug("Ending Alert: %s", self._name)
self._cancel()
self._ack = False
self._firing = False
@ -247,7 +251,7 @@ class Alert(ToggleEntity):
return
if not self._ack:
_LOGGER.info('Alerting: %s', self._name)
_LOGGER.info("Alerting: %s", self._name)
for target in self._notifiers:
yield from self.hass.services.async_call(
'notify', target, {'message': self._name})
@ -256,14 +260,14 @@ class Alert(ToggleEntity):
@asyncio.coroutine
def async_turn_on(self):
"""Async Unacknowledge alert."""
_LOGGER.debug('Reset Alert: %s', self._name)
_LOGGER.debug("Reset Alert: %s", self._name)
self._ack = False
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_turn_off(self):
"""Async Acknowledge alert."""
_LOGGER.debug('Acknowledged Alert: %s', self._name)
_LOGGER.debug("Acknowledged Alert: %s", self._name)
self._ack = True
yield from self.async_update_ha_state()

View File

@ -77,14 +77,14 @@ class ApiaiIntentsView(HomeAssistantView):
"""Handle API.AI."""
data = yield from request.json()
_LOGGER.debug('Received Apiai request: %s', data)
_LOGGER.debug("Received api.ai request: %s", data)
req = data.get('result')
if req is None:
_LOGGER.error('Received invalid data from Apiai: %s', data)
return self.json_message('Expected result value not received',
HTTP_BAD_REQUEST)
_LOGGER.error("Received invalid data from api.ai: %s", data)
return self.json_message(
"Expected result value not received", HTTP_BAD_REQUEST)
action_incomplete = req['actionIncomplete']
@ -106,7 +106,7 @@ class ApiaiIntentsView(HomeAssistantView):
# return self.json(response)
if intent == "":
_LOGGER.warning('Received intent with empty action')
_LOGGER.warning("Received intent with empty action")
response.add_speech(
"You have not defined an action in your api.ai intent.")
return self.json(response)
@ -114,7 +114,7 @@ class ApiaiIntentsView(HomeAssistantView):
config = self.intents.get(intent)
if config is None:
_LOGGER.warning('Received unknown intent %s', intent)
_LOGGER.warning("Received unknown intent %s", intent)
response.add_speech(
"Intent '%s' is not yet configured within Home Assistant." %
intent)

View File

@ -412,7 +412,7 @@ def _async_process_trigger(hass, config, trigger_configs, name, action):
if platform is None:
return None
remove = platform.async_trigger(hass, conf, action)
remove = yield from platform.async_trigger(hass, conf, action)
if not remove:
_LOGGER.error("Error setting up trigger %s", name)

View File

@ -4,6 +4,7 @@ Offer event listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#event-trigger
"""
import asyncio
import logging
import voluptuous as vol
@ -24,6 +25,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
@asyncio.coroutine
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE)

View File

@ -4,6 +4,7 @@ Trigger an automation when a LiteJet switch is released.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/automation.litejet/
"""
import asyncio
import logging
import voluptuous as vol
@ -32,6 +33,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
@asyncio.coroutine
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
number = config.get(CONF_NUMBER)

View File

@ -4,6 +4,7 @@ Offer MQTT listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#mqtt-trigger
"""
import asyncio
import json
import voluptuous as vol
@ -24,6 +25,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
@asyncio.coroutine
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
topic = config.get(CONF_TOPIC)
@ -49,4 +51,6 @@ def async_trigger(hass, config, action):
'trigger': data
})
return mqtt.async_subscribe(hass, topic, mqtt_automation_listener)
remove = yield from mqtt.async_subscribe(
hass, topic, mqtt_automation_listener)
return remove

View File

@ -4,6 +4,7 @@ Offer numeric state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#numeric-state-trigger
"""
import asyncio
import logging
import voluptuous as vol
@ -26,6 +27,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
_LOGGER = logging.getLogger(__name__)
@asyncio.coroutine
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)

View File

@ -4,6 +4,7 @@ Offer state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#state-trigger
"""
import asyncio
import voluptuous as vol
from homeassistant.core import callback
@ -34,6 +35,7 @@ TRIGGER_SCHEMA = vol.All(
)
@asyncio.coroutine
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
@ -43,6 +45,19 @@ def async_trigger(hass, config, action):
async_remove_state_for_cancel = None
async_remove_state_for_listener = None
@callback
def clear_listener():
"""Clear all unsub listener."""
nonlocal async_remove_state_for_cancel, async_remove_state_for_listener
# pylint: disable=not-callable
if async_remove_state_for_listener is not None:
async_remove_state_for_listener()
async_remove_state_for_listener = None
if async_remove_state_for_cancel is not None:
async_remove_state_for_cancel()
async_remove_state_for_cancel = None
@callback
def state_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
@ -64,18 +79,11 @@ def async_trigger(hass, config, action):
call_action()
return
@callback
def clear_listener():
"""Clear all unsub listener."""
nonlocal async_remove_state_for_cancel
nonlocal async_remove_state_for_listener
async_remove_state_for_listener = None
async_remove_state_for_cancel = None
@callback
def state_for_listener(now):
"""Fire on state changes after a delay and calls action."""
async_remove_state_for_cancel()
nonlocal async_remove_state_for_listener
async_remove_state_for_listener = None
clear_listener()
call_action()
@ -84,10 +92,11 @@ def async_trigger(hass, config, action):
"""Fire on changes and cancel for listener if changed."""
if inner_to_s.state == to_s.state:
return
async_remove_state_for_listener()
async_remove_state_for_cancel()
clear_listener()
# cleanup previous listener
clear_listener()
async_remove_state_for_listener = async_track_point_in_utc_time(
hass, state_for_listener, dt_util.utcnow() + time_delta)
@ -97,14 +106,10 @@ def async_trigger(hass, config, action):
unsub = async_track_state_change(
hass, entity_id, state_automation_listener, from_state, to_state)
@callback
def async_remove():
"""Remove state listeners async."""
unsub()
# pylint: disable=not-callable
if async_remove_state_for_cancel is not None:
async_remove_state_for_cancel()
if async_remove_state_for_listener is not None:
async_remove_state_for_listener()
clear_listener()
return async_remove

View File

@ -4,6 +4,7 @@ Offer sun based automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#sun-trigger
"""
import asyncio
from datetime import timedelta
import logging
@ -26,6 +27,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
@asyncio.coroutine
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)

View File

@ -4,14 +4,14 @@ Offer template automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#template-trigger
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM
from homeassistant.helpers import condition
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.event import async_track_template
import homeassistant.helpers.config_validation as cv
@ -23,33 +23,22 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
})
@asyncio.coroutine
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass
# Local variable to keep track of if the action has already been triggered
already_triggered = False
@callback
def state_changed_listener(entity_id, from_s, to_s):
def template_listener(entity_id, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal already_triggered
template_result = condition.async_template(hass, value_template)
hass.async_run_job(action, {
'trigger': {
'platform': 'template',
'entity_id': entity_id,
'from_state': from_s,
'to_state': to_s,
},
})
# Check to see if template returns true
if template_result and not already_triggered:
already_triggered = True
hass.async_run_job(action, {
'trigger': {
'platform': 'template',
'entity_id': entity_id,
'from_state': from_s,
'to_state': to_s,
},
})
elif not template_result:
already_triggered = False
return async_track_state_change(hass, value_template.extract_entities(),
state_changed_listener)
return async_track_template(hass, value_template, template_listener)

View File

@ -4,6 +4,7 @@ Offer time listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#time-trigger
"""
import asyncio
import logging
import voluptuous as vol
@ -29,6 +30,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
CONF_SECONDS, CONF_AFTER))
@asyncio.coroutine
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
if CONF_AFTER in config:

View File

@ -4,6 +4,7 @@ Offer zone automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#zone-trigger
"""
import asyncio
import voluptuous as vol
from homeassistant.core import callback
@ -26,6 +27,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
@asyncio.coroutine
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)

View File

@ -14,13 +14,13 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.const import (STATE_ON, STATE_OFF)
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.helpers.deprecation import deprecated_substitute
DOMAIN = 'binary_sensor'
SCAN_INTERVAL = timedelta(seconds=30)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
SENSOR_CLASSES = [
None, # Generic on/off
DEVICE_CLASSES = [
'cold', # On means cold (or too cold)
'connectivity', # On means connection present, Off = no connection
'gas', # CO, CO2, etc.
@ -38,7 +38,7 @@ SENSOR_CLASSES = [
'vibration', # On means vibration detected, Off means no vibration
]
SENSOR_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(SENSOR_CLASSES))
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
@asyncio.coroutine
@ -66,16 +66,7 @@ class BinarySensorDevice(Entity):
return STATE_ON if self.is_on else STATE_OFF
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
@deprecated_substitute('sensor_class')
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return None
@property
def state_attributes(self):
"""Return device specific state attributes."""
attr = {}
if self.sensor_class is not None:
attr['sensor_class'] = self.sensor_class
return attr

View File

@ -11,11 +11,12 @@ import requests
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, SENSOR_CLASSES_SCHEMA)
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_SENSOR_CLASS)
CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_SENSOR_CLASS, CONF_DEVICE_CLASS)
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
_LOGGER = logging.getLogger(__name__)
@ -25,7 +26,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_PIN): cv.string,
vol.Optional(CONF_SENSOR_CLASS): SENSOR_CLASSES_SCHEMA,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
@ -33,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the aREST binary sensor."""
resource = config.get(CONF_RESOURCE)
pin = config.get(CONF_PIN)
sensor_class = config.get(CONF_SENSOR_CLASS)
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
try:
response = requests.get(resource, timeout=10).json()
@ -49,18 +51,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([ArestBinarySensor(
arest, resource, config.get(CONF_NAME, response[CONF_NAME]),
sensor_class, pin)])
device_class, pin)])
class ArestBinarySensor(BinarySensorDevice):
"""Implement an aREST binary sensor for a pin."""
def __init__(self, arest, resource, name, sensor_class, pin):
def __init__(self, arest, resource, name, device_class, pin):
"""Initialize the aREST device."""
self.arest = arest
self._resource = resource
self._name = name
self._sensor_class = sensor_class
self._device_class = device_class
self._pin = pin
self.update()
@ -81,9 +83,9 @@ class ArestBinarySensor(BinarySensorDevice):
return bool(self.arest.data.get('state'))
@property
def sensor_class(self):
def device_class(self):
"""Return the class of this sensor."""
return self._sensor_class
return self._device_class
def update(self):
"""Get the latest data from aREST API."""

View File

@ -0,0 +1,148 @@
"""
Support for aurora forecast data sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.aurora/
"""
from datetime import timedelta
import logging
import requests
import voluptuous as vol
from homeassistant.components.binary_sensor \
import (BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
CONF_THRESHOLD = "forecast_threshold"
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Aurora Visibility'
DEFAULT_DEVICE_CLASS = "visible"
DEFAULT_THRESHOLD = 75
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_THRESHOLD, default=DEFAULT_THRESHOLD): cv.positive_int,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the aurora sensor."""
if None in (hass.config.latitude, hass.config.longitude):
_LOGGER.error("Lat. or long. not set in Home Assistant config")
return False
name = config.get(CONF_NAME)
threshold = config.get(CONF_THRESHOLD)
try:
aurora_data = AuroraData(
hass.config.latitude,
hass.config.longitude,
threshold
)
aurora_data.update()
except requests.exceptions.HTTPError as error:
_LOGGER.error(
"Connection to aurora forecast service failed: %s", error)
return False
add_devices([AuroraSensor(aurora_data, name)], True)
class AuroraSensor(BinarySensorDevice):
"""Implementation of an aurora sensor."""
def __init__(self, aurora_data, name):
"""Initialize the sensor."""
self.aurora_data = aurora_data
self._name = name
@property
def name(self):
"""Return the name of the sensor."""
return '{}'.format(self._name)
@property
def is_on(self):
"""Return true if aurora is visible."""
return self.aurora_data.is_visible if self.aurora_data else False
@property
def device_class(self):
"""Return the class of this device."""
return DEFAULT_DEVICE_CLASS
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {}
if self.aurora_data:
attrs["visibility_level"] = self.aurora_data.visibility_level
attrs["message"] = self.aurora_data.is_visible_text
return attrs
def update(self):
"""Get the latest data from Aurora API and updates the states."""
self.aurora_data.update()
class AuroraData(object):
"""Get aurora forecast."""
def __init__(self, latitude, longitude, threshold):
"""Initialize the data object."""
self.latitude = latitude
self.longitude = longitude
self.number_of_latitude_intervals = 513
self.number_of_longitude_intervals = 1024
self.api_url = \
"http://services.swpc.noaa.gov/text/aurora-nowcast-map.txt"
self.headers = {"User-Agent": "Home Assistant Aurora Tracker v.0.1.0"}
self.threshold = int(threshold)
self.is_visible = None
self.is_visible_text = None
self.visibility_level = None
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from the Aurora service."""
try:
self.visibility_level = self.get_aurora_forecast()
if int(self.visibility_level) > self.threshold:
self.is_visible = True
self.is_visible_text = "visible!"
else:
self.is_visible = False
self.is_visible_text = "nothing's out"
except requests.exceptions.HTTPError as error:
_LOGGER.error(
"Connection to aurora forecast service failed: %s", error)
return False
def get_aurora_forecast(self):
"""Get forecast data and parse for given long/lat."""
raw_data = requests.get(self.api_url, headers=self.headers).text
forecast_table = [
row.strip(" ").split(" ")
for row in raw_data.split("\n")
if not row.startswith("#")
]
# convert lat and long for data points in table
converted_latitude = round((self.latitude / 180)
* self.number_of_latitude_intervals)
converted_longitude = round((self.longitude / 360)
* self.number_of_longitude_intervals)
return forecast_table[converted_latitude][converted_longitude]

View File

@ -64,8 +64,8 @@ class BloomSkySensor(BinarySensorDevice):
return self._unique_id
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return SENSOR_TYPES.get(self._sensor_name)
@property

View File

@ -10,12 +10,13 @@ import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES_SCHEMA, PLATFORM_SCHEMA)
BinarySensorDevice, DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA)
from homeassistant.components.sensor.command_line import CommandSensorData
from homeassistant.const import (
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_COMMAND)
CONF_SENSOR_CLASS, CONF_COMMAND, CONF_DEVICE_CLASS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
_LOGGER = logging.getLogger(__name__)
@ -30,7 +31,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_SENSOR_CLASS): SENSOR_CLASSES_SCHEMA,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
})
@ -42,27 +44,27 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
command = config.get(CONF_COMMAND)
payload_off = config.get(CONF_PAYLOAD_OFF)
payload_on = config.get(CONF_PAYLOAD_ON)
sensor_class = config.get(CONF_SENSOR_CLASS)
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
data = CommandSensorData(command)
add_devices([CommandBinarySensor(
hass, data, name, sensor_class, payload_on, payload_off,
hass, data, name, device_class, payload_on, payload_off,
value_template)])
class CommandBinarySensor(BinarySensorDevice):
"""Represent a command line binary sensor."""
def __init__(self, hass, data, name, sensor_class, payload_on,
def __init__(self, hass, data, name, device_class, payload_on,
payload_off, value_template):
"""Initialize the Command line binary sensor."""
self._hass = hass
self.data = data
self._name = name
self._sensor_class = sensor_class
self._device_class = device_class
self._state = False
self._payload_on = payload_on
self._payload_off = payload_off
@ -80,9 +82,9 @@ class CommandBinarySensor(BinarySensorDevice):
return self._state
@ property
def sensor_class(self):
def device_class(self):
"""Return the class of the binary sensor."""
return self._sensor_class
return self._device_class
def update(self):
"""Get the latest data and updates the state."""

View File

@ -11,7 +11,7 @@ import requests
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, SENSOR_CLASSES)
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES)
from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
@ -30,7 +30,7 @@ DEFAULT_SSL = False
SCAN_INTERVAL = datetime.timedelta(seconds=1)
ZONE_TYPES_SCHEMA = vol.Schema({
cv.positive_int: vol.In(SENSOR_CLASSES),
cv.positive_int: vol.In(DEVICE_CLASSES),
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -102,8 +102,8 @@ class Concord232ZoneSensor(BinarySensorDevice):
self.update()
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._zone_type
@property

View File

@ -18,14 +18,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DemoBinarySensor(BinarySensorDevice):
"""A Demo binary sensor."""
def __init__(self, name, state, sensor_class):
def __init__(self, name, state, device_class):
"""Initialize the demo sensor."""
self._name = name
self._state = state
self._sensor_type = sensor_class
self._sensor_type = device_class
@property
def sensor_class(self):
def device_class(self):
"""Return the class of this sensor."""
return self._sensor_type

View File

@ -63,7 +63,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice):
return self.data.status == 'active'
@property
def sensor_class(self):
def device_class(self):
"""Return the class of this sensor."""
return DEFAULT_SENSOR_CLASS

View File

@ -38,7 +38,7 @@ class EcobeeBinarySensor(BinarySensorDevice):
self.sensor_name = sensor_name
self.index = sensor_index
self._state = None
self._sensor_class = 'occupancy'
self._device_class = 'occupancy'
self.update()
@property
@ -57,9 +57,9 @@ class EcobeeBinarySensor(BinarySensorDevice):
return "binary_sensor_ecobee_{}_{}".format(self._name, self.index)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return self._sensor_class
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._device_class
def update(self):
"""Get the latest state of the sensor."""

View File

@ -9,10 +9,12 @@ import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, SENSOR_CLASSES_SCHEMA)
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
from homeassistant.components import enocean
from homeassistant.const import (CONF_NAME, CONF_ID, CONF_SENSOR_CLASS)
from homeassistant.const import (
CONF_NAME, CONF_ID, CONF_SENSOR_CLASS, CONF_DEVICE_CLASS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
_LOGGER = logging.getLogger(__name__)
@ -22,7 +24,8 @@ DEFAULT_NAME = 'EnOcean binary sensor'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
@ -30,15 +33,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Binary Sensor platform fo EnOcean."""
dev_id = config.get(CONF_ID)
devname = config.get(CONF_NAME)
sensor_class = config.get(CONF_SENSOR_CLASS)
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
add_devices([EnOceanBinarySensor(dev_id, devname, sensor_class)])
add_devices([EnOceanBinarySensor(dev_id, devname, device_class)])
class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
"""Representation of EnOcean binary sensors such as wall switches."""
def __init__(self, dev_id, devname, sensor_class):
def __init__(self, dev_id, devname, device_class):
"""Initialize the EnOcean binary sensor."""
enocean.EnOceanDevice.__init__(self)
self.stype = "listener"
@ -46,7 +49,7 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
self.which = -1
self.onoff = -1
self.devname = devname
self._sensor_class = sensor_class
self._device_class = device_class
@property
def name(self):
@ -54,9 +57,9 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
return self.devname
@property
def sensor_class(self):
def device_class(self):
"""Return the class of this sensor."""
return self._sensor_class
return self._device_class
def value_changed(self, value, value2):
"""Fire an event with the data that have changed.

View File

@ -4,48 +4,56 @@ Support for Envisalink zone states- represented as binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.envisalink/
"""
import asyncio
import logging
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.envisalink import (EVL_CONTROLLER,
ZONE_SCHEMA,
CONF_ZONENAME,
CONF_ZONETYPE,
EnvisalinkDevice,
SIGNAL_ZONE_UPDATE)
from homeassistant.components.envisalink import (
DATA_EVL, ZONE_SCHEMA, CONF_ZONENAME, CONF_ZONETYPE, EnvisalinkDevice,
SIGNAL_ZONE_UPDATE)
from homeassistant.const import ATTR_LAST_TRIP_TIME
DEPENDENCIES = ['envisalink']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup Envisalink binary sensor devices."""
_configured_zones = discovery_info['zones']
for zone_num in _configured_zones:
_device_config_data = ZONE_SCHEMA(_configured_zones[zone_num])
_device = EnvisalinkBinarySensor(
configured_zones = discovery_info['zones']
devices = []
for zone_num in configured_zones:
device_config_data = ZONE_SCHEMA(configured_zones[zone_num])
device = EnvisalinkBinarySensor(
hass,
zone_num,
_device_config_data[CONF_ZONENAME],
_device_config_data[CONF_ZONETYPE],
EVL_CONTROLLER.alarm_state['zone'][zone_num],
EVL_CONTROLLER)
add_devices_callback([_device])
device_config_data[CONF_ZONENAME],
device_config_data[CONF_ZONETYPE],
hass.data[DATA_EVL].alarm_state['zone'][zone_num],
hass.data[DATA_EVL]
)
devices.append(device)
yield from async_add_devices(devices)
class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
"""Representation of an Envisalink binary sensor."""
def __init__(self, zone_number, zone_name, zone_type, info, controller):
def __init__(self, hass, zone_number, zone_name, zone_type, info,
controller):
"""Initialize the binary_sensor."""
from pydispatch import dispatcher
self._zone_type = zone_type
self._zone_number = zone_number
_LOGGER.debug('Setting up zone: ' + zone_name)
EnvisalinkDevice.__init__(self, zone_name, info, controller)
dispatcher.connect(self._update_callback,
signal=SIGNAL_ZONE_UPDATE,
sender=dispatcher.Any)
super().__init__(zone_name, info, controller)
async_dispatcher_connect(
hass, SIGNAL_ZONE_UPDATE, self._update_callback)
@property
def device_state_attributes(self):
@ -60,11 +68,12 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
return self._info['status']['open']
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._zone_type
@callback
def _update_callback(self, zone):
"""Update the zone's state, if needed."""
if zone is None or int(zone) == self._zone_number:
self.hass.async_add_job(self.update_ha_state)
self.hass.async_add_job(self.async_update_ha_state())

View File

@ -122,6 +122,6 @@ class FFmpegMotion(FFmpegBinarySensor):
)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return "motion"

View File

@ -91,6 +91,6 @@ class FFmpegNoise(FFmpegBinarySensor):
)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return "sound"

View File

@ -179,12 +179,9 @@ class FlicButton(BinarySensorDevice):
return False
@property
def state_attributes(self):
def device_state_attributes(self):
"""Return device specific state attributes."""
attr = super(FlicButton, self).state_attributes
attr["address"] = self.address
return attr
return {"address": self.address}
def _queued_event_check(self, click_type, time_diff):
"""Generate a log message and returns true if timeout exceeded."""

View File

@ -29,7 +29,7 @@ DEFAULT_DELAY = 0
ATTR_DELAY = 'delay'
SENSOR_CLASS_MAP = {
DEVICE_CLASS_MAP = {
'Motion': 'motion',
'Line Crossing': 'motion',
'IO Trigger': None,
@ -201,10 +201,10 @@ class HikvisionBinarySensor(BinarySensorDevice):
return self._sensor_state()
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
try:
return SENSOR_CLASS_MAP[self._sensor]
return DEVICE_CLASS_MAP[self._sensor]
except KeyError:
# Sensor must be unknown to us, add as generic
return None

View File

@ -7,8 +7,7 @@ https://home-assistant.io/components/binary_sensor.homematic/
import logging
from homeassistant.const import STATE_UNKNOWN
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.homematic import HMDevice
from homeassistant.loader import get_component
from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
_LOGGER = logging.getLogger(__name__)
@ -29,18 +28,18 @@ SENSOR_TYPES_CLASS = {
}
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Homematic binary sensor platform."""
if discovery_info is None:
return
homematic = get_component("homematic")
return homematic.setup_hmdevice_discovery_helper(
hass,
HMBinarySensor,
discovery_info,
add_callback_devices
)
devices = []
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMBinarySensor(hass, config)
new_device.link_homematic()
devices.append(new_device)
add_devices(devices)
class HMBinarySensor(HMDevice, BinarySensorDevice):
@ -54,11 +53,8 @@ class HMBinarySensor(HMDevice, BinarySensorDevice):
return bool(self._hm_get_state())
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
if not self.available:
return None
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
# If state is MOTION (RemoteMotion works only)
if self._state == "MOTION":
return "motion"

View File

@ -0,0 +1,87 @@
"""
Support for INSTEON dimmers via PowerLinc Modem.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/insteon_plm/
"""
import logging
import asyncio
from homeassistant.core import callback
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.loader import get_component
DEPENDENCIES = ['insteon_plm']
_LOGGER = logging.getLogger(__name__)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the INSTEON PLM device class for the hass platform."""
plm = hass.data['insteon_plm']
device_list = []
for device in discovery_info:
name = device.get('address')
address = device.get('address_hex')
_LOGGER.info('Registered %s with binary_sensor platform.', name)
device_list.append(
InsteonPLMBinarySensorDevice(hass, plm, address, name)
)
hass.async_add_job(async_add_devices(device_list))
class InsteonPLMBinarySensorDevice(BinarySensorDevice):
"""A Class for an Insteon device."""
def __init__(self, hass, plm, address, name):
"""Initialize the binarysensor."""
self._hass = hass
self._plm = plm.protocol
self._address = address
self._name = name
self._plm.add_update_callback(
self.async_binarysensor_update, {'address': self._address})
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def address(self):
"""Return the the address of the node."""
return self._address
@property
def name(self):
"""Return the the name of the node."""
return self._name
@property
def is_on(self):
"""Return the boolean response if the node is on."""
sensorstate = self._plm.get_device_attr(self._address, 'sensorstate')
_LOGGER.info('sensor state for %s is %s', self._address, sensorstate)
return bool(sensorstate)
@property
def device_state_attributes(self):
"""Provide attributes for display on device card."""
insteon_plm = get_component('insteon_plm')
return insteon_plm.common_attributes(self)
def get_attr(self, key):
"""Return specified attribute for this device."""
return self._plm.get_device_attr(self.address, key)
@callback
def async_binarysensor_update(self, message):
"""Receive notification from transport that new data exists."""
_LOGGER.info('Received update calback from PLM for %s', self._address)
self._hass.async_add_job(self.async_update_ha_state())

View File

@ -26,7 +26,7 @@ ATTR_ISS_NUMBER_PEOPLE_SPACE = 'number_of_people_in_space'
CONF_SHOW_ON_MAP = 'show_on_map'
DEFAULT_NAME = 'ISS'
DEFAULT_SENSOR_CLASS = 'visible'
DEFAULT_DEVICE_CLASS = 'visible'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
@ -77,9 +77,9 @@ class IssBinarySensor(BinarySensorDevice):
return self.iss_data.is_above if self.iss_data else False
@property
def sensor_class(self):
def device_class(self):
"""Return the class of this sensor."""
return DEFAULT_SENSOR_CLASS
return DEFAULT_DEVICE_CLASS
@property
def device_state_attributes(self):

View File

@ -4,6 +4,7 @@ Support for MQTT binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mqtt/
"""
import asyncio
import logging
import voluptuous as vol
@ -11,12 +12,13 @@ import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES)
BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
CONF_SENSOR_CLASS)
CONF_SENSOR_CLASS, CONF_DEVICE_CLASS)
from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
_LOGGER = logging.getLogger(__name__)
@ -29,13 +31,13 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_SENSOR_CLASS, default=None):
vol.Any(vol.In(SENSOR_CLASSES), vol.SetTo(None)),
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
# pylint: disable=unused-argument
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 the MQTT binary sensor."""
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
@ -43,11 +45,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices([MqttBinarySensor(
hass,
yield from async_add_devices([MqttBinarySensor(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_SENSOR_CLASS),
get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS),
config.get(CONF_QOS),
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF),
@ -58,32 +60,38 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class MqttBinarySensor(BinarySensorDevice):
"""Representation a binary sensor that is updated by MQTT."""
def __init__(self, hass, name, state_topic, sensor_class, qos, payload_on,
def __init__(self, name, state_topic, device_class, qos, payload_on,
payload_off, value_template):
"""Initialize the MQTT binary sensor."""
self._hass = hass
self._name = name
self._state = False
self._state_topic = state_topic
self._sensor_class = sensor_class
self._device_class = device_class
self._payload_on = payload_on
self._payload_off = payload_off
self._qos = qos
self._template = value_template
def async_added_to_hass(self):
"""Subscribe mqtt events.
This method must be run in the event loop and returns a coroutine.
"""
@callback
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = value_template.async_render_with_possible_json_value(
if self._template is not None:
payload = self._template.async_render_with_possible_json_value(
payload)
if payload == self._payload_on:
self._state = True
hass.async_add_job(self.async_update_ha_state())
elif payload == self._payload_off:
self._state = False
hass.async_add_job(self.async_update_ha_state())
mqtt.subscribe(hass, self._state_topic, message_received, self._qos)
self.hass.async_add_job(self.async_update_ha_state())
return mqtt.async_subscribe(
self.hass, self._state_topic, message_received, self._qos)
@property
def should_poll(self):
@ -101,6 +109,6 @@ class MqttBinarySensor(BinarySensorDevice):
return self._state
@property
def sensor_class(self):
def device_class(self):
"""Return the class of this sensor."""
return self._sensor_class
return self._device_class

View File

@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.mysensors/
import logging
from homeassistant.components import mysensors
from homeassistant.components.binary_sensor import (SENSOR_CLASSES,
from homeassistant.components.binary_sensor import (DEVICE_CLASSES,
BinarySensorDevice)
from homeassistant.const import STATE_ON
@ -62,8 +62,8 @@ class MySensorsBinarySensor(
return False
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
pres = self.gateway.const.Presentation
class_map = {
pres.S_DOOR: 'opening',
@ -78,5 +78,5 @@ class MySensorsBinarySensor(
pres.S_VIBRATION: 'vibration',
pres.S_MOISTURE: 'moisture',
})
if class_map.get(self.child_type) in SENSOR_CLASSES:
if class_map.get(self.child_type) in DEVICE_CLASSES:
return class_map.get(self.child_type)

View File

@ -154,8 +154,8 @@ class NetatmoBinarySensor(BinarySensorDevice):
return self._unique_id
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
if self._cameratype == "NACamera":
return WELCOME_SENSOR_TYPES.get(self._sensor_name)
elif self._cameratype == "NOC":

View File

@ -12,7 +12,7 @@ import requests
import voluptuous as vol
from homeassistant.components.binary_sensor import (
SENSOR_CLASSES, BinarySensorDevice, PLATFORM_SCHEMA)
DEVICE_CLASSES, BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
@ -28,7 +28,7 @@ DEFAULT_PORT = '5007'
DEFAULT_SSL = False
ZONE_TYPES_SCHEMA = vol.Schema({
cv.positive_int: vol.In(SENSOR_CLASSES),
cv.positive_int: vol.In(DEVICE_CLASSES),
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -85,8 +85,8 @@ class NX584ZoneSensor(BinarySensorDevice):
self._zone_type = zone_type
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._zone_type
@property

View File

@ -99,8 +99,8 @@ class OctoPrintBinarySensor(BinarySensorDevice):
return STATE_OFF
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return None
def update(self):

View File

@ -10,14 +10,15 @@ import voluptuous as vol
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES_SCHEMA, PLATFORM_SCHEMA)
BinarySensorDevice, DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA)
from homeassistant.components.sensor.rest import RestData
from homeassistant.const import (
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
CONF_SENSOR_CLASS, CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD,
CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
HTTP_DIGEST_AUTHENTICATION)
HTTP_DIGEST_AUTHENTICATION, CONF_DEVICE_CLASS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
_LOGGER = logging.getLogger(__name__)
@ -34,7 +35,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_PAYLOAD): cv.string,
vol.Optional(CONF_SENSOR_CLASS): SENSOR_CLASSES_SCHEMA,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
@ -51,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
headers = config.get(CONF_HEADERS)
sensor_class = config.get(CONF_SENSOR_CLASS)
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
@ -72,18 +74,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
add_devices([RestBinarySensor(
hass, rest, name, sensor_class, value_template)])
hass, rest, name, device_class, value_template)])
class RestBinarySensor(BinarySensorDevice):
"""Representation of a REST binary sensor."""
def __init__(self, hass, rest, name, sensor_class, value_template):
def __init__(self, hass, rest, name, device_class, value_template):
"""Initialize a REST binary sensor."""
self._hass = hass
self.rest = rest
self._name = name
self._sensor_class = sensor_class
self._device_class = device_class
self._state = False
self._previous_data = None
self._value_template = value_template
@ -95,9 +97,9 @@ class RestBinarySensor(BinarySensorDevice):
return self._name
@property
def sensor_class(self):
def device_class(self):
"""Return the class of this sensor."""
return self._sensor_class
return self._device_class
@property
def is_on(self):

View File

@ -42,7 +42,7 @@ class IsInBedBinarySensor(sleepiq.SleepIQSensor, BinarySensorDevice):
return self._state is True
@property
def sensor_class(self):
def device_class(self):
"""Return the class of this sensor."""
return "occupancy"

View File

@ -12,14 +12,15 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
SENSOR_CLASSES_SCHEMA)
DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_SENSORS)
CONF_SENSOR_CLASS, CONF_SENSORS, CONF_DEVICE_CLASS)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
_LOGGER = logging.getLogger(__name__)
@ -27,7 +28,8 @@ SENSOR_SCHEMA = vol.Schema({
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -45,7 +47,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
value_template.extract_entities())
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
sensor_class = device_config.get(CONF_SENSOR_CLASS)
device_class = get_deprecated(
device_config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
if value_template is not None:
value_template.hass = hass
@ -55,7 +58,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
hass,
device,
friendly_name,
sensor_class,
device_class,
value_template,
entity_ids)
)
@ -70,14 +73,14 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class BinarySensorTemplate(BinarySensorDevice):
"""A virtual binary sensor that triggers from another sensor."""
def __init__(self, hass, device, friendly_name, sensor_class,
def __init__(self, hass, device, friendly_name, device_class,
value_template, entity_ids):
"""Initialize the Template binary sensor."""
self.hass = hass
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device,
hass=hass)
self._name = friendly_name
self._sensor_class = sensor_class
self._device_class = device_class
self._template = value_template
self._state = None
@ -100,9 +103,9 @@ class BinarySensorTemplate(BinarySensorDevice):
return self._state
@property
def sensor_class(self):
def device_class(self):
"""Return the sensor class of the sensor."""
return self._sensor_class
return self._device_class
@property
def should_poll(self):

View File

@ -11,11 +11,12 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, SENSOR_CLASSES_SCHEMA)
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_NAME, CONF_ENTITY_ID, CONF_TYPE, STATE_UNKNOWN, CONF_SENSOR_CLASS,
ATTR_ENTITY_ID)
ATTR_ENTITY_ID, CONF_DEVICE_CLASS)
from homeassistant.core import callback
from homeassistant.helpers.deprecation import get_deprecated
from homeassistant.helpers.event import async_track_state_change
_LOGGER = logging.getLogger(__name__)
@ -37,7 +38,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_THRESHOLD): vol.Coerce(float),
vol.Required(CONF_TYPE): vol.In(SENSOR_TYPES),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA,
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
@ -48,11 +50,11 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
name = config.get(CONF_NAME)
threshold = config.get(CONF_THRESHOLD)
limit_type = config.get(CONF_TYPE)
sensor_class = config.get(CONF_SENSOR_CLASS)
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
yield from async_add_devices(
[ThresholdSensor(hass, entity_id, name, threshold, limit_type,
sensor_class)], True)
device_class)], True)
return True
@ -60,14 +62,14 @@ class ThresholdSensor(BinarySensorDevice):
"""Representation of a Threshold sensor."""
def __init__(self, hass, entity_id, name, threshold, limit_type,
sensor_class):
device_class):
"""Initialize the Threshold sensor."""
self._hass = hass
self._entity_id = entity_id
self.is_upper = limit_type == 'upper'
self._name = name
self._threshold = threshold
self._sensor_class = sensor_class
self._device_class = device_class
self._deviation = False
self.sensor_value = 0
@ -105,9 +107,9 @@ class ThresholdSensor(BinarySensorDevice):
return False
@property
def sensor_class(self):
def device_class(self):
"""Return the sensor class of the sensor."""
return self._sensor_class
return self._device_class
@property
def device_state_attributes(self):

View File

@ -15,12 +15,14 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice,
ENTITY_ID_FORMAT,
PLATFORM_SCHEMA,
SENSOR_CLASSES_SCHEMA)
DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME,
ATTR_ENTITY_ID,
CONF_SENSOR_CLASS,
CONF_DEVICE_CLASS,
STATE_UNKNOWN,)
from homeassistant.helpers.deprecation import get_deprecated
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change
@ -34,8 +36,8 @@ SENSOR_SCHEMA = vol.Schema({
vol.Optional(CONF_ATTRIBUTE): cv.string,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_INVERT, default=False): cv.boolean,
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -52,7 +54,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
entity_id = device_config[ATTR_ENTITY_ID]
attribute = device_config.get(CONF_ATTRIBUTE)
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
sensor_class = device_config[CONF_SENSOR_CLASS]
device_class = get_deprecated(
device_config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
invert = device_config[CONF_INVERT]
sensors.append(
@ -62,7 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
friendly_name,
entity_id,
attribute,
sensor_class,
device_class,
invert)
)
if not sensors:
@ -76,7 +79,7 @@ class SensorTrend(BinarySensorDevice):
"""Representation of a trend Sensor."""
def __init__(self, hass, device_id, friendly_name,
target_entity, attribute, sensor_class, invert):
target_entity, attribute, device_class, invert):
"""Initialize the sensor."""
self._hass = hass
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
@ -84,7 +87,7 @@ class SensorTrend(BinarySensorDevice):
self._name = friendly_name
self._target_entity = target_entity
self._attribute = attribute
self._sensor_class = sensor_class
self._device_class = device_class
self._invert = invert
self._state = None
self.from_state = None
@ -111,9 +114,9 @@ class SensorTrend(BinarySensorDevice):
return self._state
@property
def sensor_class(self):
def device_class(self):
"""Return the sensor class of the sensor."""
return self._sensor_class
return self._device_class
@property
def should_poll(self):

View File

@ -0,0 +1,40 @@
"""
Support for VOC.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.volvooncall/
"""
import logging
from homeassistant.components.volvooncall import VolvoEntity
from homeassistant.components.binary_sensor import BinarySensorDevice
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Volvo sensors."""
if discovery_info is None:
return
add_devices([VolvoSensor(hass, *discovery_info)])
class VolvoSensor(VolvoEntity, BinarySensorDevice):
"""Representation of a Volvo sensor."""
@property
def is_on(self):
"""Return True if the binary sensor is on."""
val = getattr(self.vehicle, self._attribute)
if self._attribute == 'bulb_failures':
return len(val) > 0
elif self._attribute in ['doors', 'windows']:
return any([val[key] for key in val if 'Open' in key])
else:
return val != 'Normal'
@property
def device_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return 'safety'

View File

@ -92,13 +92,13 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
try:
if hasattr(self.wink, 'unit'):
self._unit_of_measurement = self.wink.unit()
except AttributeError:
else:
self._unit_of_measurement = None
try:
if hasattr(self.wink, 'capability'):
self.capability = self.wink.capability()
except AttributeError:
else:
self.capability = None
@property
@ -107,8 +107,8 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
return self.wink.state()
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return SENSOR_TYPES.get(self.capability)
@ -161,8 +161,8 @@ class WinkRemote(WinkBinarySensorDevice):
}
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return None

View File

@ -48,19 +48,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
"""Representation of a binary sensor within Z-Wave."""
def __init__(self, value, sensor_class):
def __init__(self, value, device_class):
"""Initialize the sensor."""
self._sensor_type = sensor_class
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self._sensor_type = device_class
self._state = self._value.data
def update_properties(self):
"""Callback on data changes for node values."""
self._state = self._value.data
@property
def is_on(self):
"""Return True if the binary sensor is on."""
return self._value.data
return self._state
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._sensor_type
@property
@ -72,34 +77,31 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
class ZWaveTriggerSensor(ZWaveBinarySensor):
"""Representation of a stateless sensor within Z-Wave."""
def __init__(self, value, sensor_class, hass, re_arm_sec=60):
def __init__(self, value, device_class, hass, re_arm_sec=60):
"""Initialize the sensor."""
super(ZWaveTriggerSensor, self).__init__(value, sensor_class)
super(ZWaveTriggerSensor, self).__init__(value, device_class)
self._hass = hass
self.re_arm_sec = re_arm_sec
self.invalidate_after = dt_util.utcnow() + datetime.timedelta(
seconds=self.re_arm_sec)
# If it's active make sure that we set the timeout tracker
if value.data:
track_point_in_time(
self._hass, self.async_update_ha_state,
self.invalidate_after)
track_point_in_time(
self._hass, self.async_update_ha_state,
self.invalidate_after)
def value_changed(self, value):
def update_properties(self):
"""Called when a value for this entity's node has changed."""
if self._value.value_id == value.value_id:
self.schedule_update_ha_state()
if value.data:
# only allow this value to be true for re_arm secs
self.invalidate_after = dt_util.utcnow() + datetime.timedelta(
seconds=self.re_arm_sec)
track_point_in_time(
self._hass, self.async_update_ha_state,
self.invalidate_after)
self._state = self._value.data
# only allow this value to be true for re_arm secs
self.invalidate_after = dt_util.utcnow() + datetime.timedelta(
seconds=self.re_arm_sec)
track_point_in_time(
self._hass, self.async_update_ha_state,
self.invalidate_after)
@property
def is_on(self):
"""Return True if movement has happened within the rearm time."""
return self._value.data and \
return self._state and \
(self.invalidate_after is None or
self.invalidate_after > dt_util.utcnow())

View File

@ -118,12 +118,13 @@ class GenericCamera(Camera):
_LOGGER.error('Timeout getting camera image')
return self._last_image
except (aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as err:
aiohttp.errors.DisconnectedError,
aiohttp.errors.HttpProcessingError) as err:
_LOGGER.error('Error getting new camera image: %s', err)
return self._last_image
finally:
if response is not None:
self.hass.async_add_job(response.release())
yield from response.release()
self._last_url = url
return self._last_image

View File

@ -0,0 +1,78 @@
"""
Support for ZoneMinder camera streaming.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.zoneminder/
"""
import asyncio
import logging
from urllib.parse import urljoin, urlencode
from homeassistant.const import CONF_NAME
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
import homeassistant.components.zoneminder as zoneminder
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['zoneminder']
DOMAIN = 'zoneminder'
def _get_image_url(hass, monitor, mode):
zm_data = hass.data[DOMAIN]
query = urlencode({
'mode': mode,
'buffer': monitor['StreamReplayBuffer'],
'monitor': monitor['Id'],
})
url = '{zms_url}?{query}'.format(
zms_url=urljoin(zm_data['server_origin'], zm_data['path_zms']),
query=query,
)
_LOGGER.debug('Monitor %s %s URL (without auth): %s',
monitor['Id'], mode, url)
if not zm_data['username']:
return url
url += '&user={:s}'.format(zm_data['username'])
if not zm_data['password']:
return url
return url + '&pass={:s}'.format(zm_data['password'])
@asyncio.coroutine
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup ZoneMinder cameras."""
cameras = []
monitors = zoneminder.get_state('api/monitors.json')
if not monitors:
_LOGGER.warning('Could not fetch monitors from ZoneMinder')
return
for i in monitors['monitors']:
monitor = i['Monitor']
if monitor['Function'] == 'None':
_LOGGER.info('Skipping camera %s', monitor['Id'])
continue
_LOGGER.info('Initializing camera %s', monitor['Id'])
device_info = {
CONF_NAME: monitor['Name'],
CONF_MJPEG_URL: _get_image_url(hass, monitor, 'jpeg'),
CONF_STILL_IMAGE_URL: _get_image_url(hass, monitor, 'single')
}
cameras.append(MjpegCamera(hass, device_info))
if not cameras:
_LOGGER.warning('No active cameras found')
return
yield from async_add_devices(cameras)

View File

@ -6,13 +6,14 @@ https://home-assistant.io/components/climate.homematic/
"""
import logging
from homeassistant.components.climate import ClimateDevice, STATE_AUTO
from homeassistant.components.homematic import HMDevice
from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
from homeassistant.util.temperature import convert
from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN, ATTR_TEMPERATURE
from homeassistant.loader import get_component
DEPENDENCIES = ['homematic']
_LOGGER = logging.getLogger(__name__)
STATE_MANUAL = "manual"
STATE_BOOST = "boost"
@ -22,21 +23,31 @@ HM_STATE_MAP = {
"BOOST_MODE": STATE_BOOST,
}
_LOGGER = logging.getLogger(__name__)
HM_TEMP_MAP = [
'ACTUAL_TEMPERATURE',
'TEMPERATURE',
]
HM_HUMI_MAP = [
'ACTUAL_HUMIDITY',
'HUMIDITY',
]
HM_CONTROL_MODE = 'CONTROL_MODE'
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Homematic thermostat platform."""
if discovery_info is None:
return
homematic = get_component("homematic")
return homematic.setup_hmdevice_discovery_helper(
hass,
HMThermostat,
discovery_info,
add_callback_devices
)
devices = []
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMThermostat(hass, config)
new_device.link_homematic()
devices.append(new_device)
add_devices(devices)
class HMThermostat(HMDevice, ClimateDevice):
@ -50,7 +61,7 @@ class HMThermostat(HMDevice, ClimateDevice):
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if not self.available:
if HM_CONTROL_MODE not in self._data:
return None
# read state and search
@ -62,8 +73,6 @@ class HMThermostat(HMDevice, ClimateDevice):
@property
def operation_list(self):
"""List of available operation modes."""
if not self.available:
return None
op_list = []
# generate list
@ -76,31 +85,29 @@ class HMThermostat(HMDevice, ClimateDevice):
@property
def current_humidity(self):
"""Return the current humidity."""
if not self.available:
return None
return self._data.get('ACTUAL_HUMIDITY', None)
for node in HM_HUMI_MAP:
if node in self._data:
return self._data[node]
@property
def current_temperature(self):
"""Return the current temperature."""
if not self.available:
return None
return self._data.get('ACTUAL_TEMPERATURE', None)
for node in HM_TEMP_MAP:
if node in self._data:
return self._data[node]
@property
def target_temperature(self):
"""Return the target temperature."""
if not self.available:
return None
return self._data.get('SET_TEMPERATURE', None)
return self._data.get(self._state)
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if not self.available or temperature is None:
if temperature is None:
return None
self._hmdevice.set_temperature(temperature)
self._hmdevice.writeNodeData(self._state, float(temperature))
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
@ -122,10 +129,12 @@ class HMThermostat(HMDevice, ClimateDevice):
def _init_data_struct(self):
"""Generate a data dict (self._data) from the Homematic metadata."""
# Add state to data dict
self._data.update({"CONTROL_MODE": STATE_UNKNOWN,
"SET_TEMPERATURE": STATE_UNKNOWN,
"ACTUAL_TEMPERATURE": STATE_UNKNOWN})
self._state = next(iter(self._hmdevice.WRITENODE.keys()))
self._data[self._state] = STATE_UNKNOWN
# support humidity
if 'ACTUAL_HUMIDITY' in self._hmdevice.SENSORNODE:
self._data.update({'ACTUAL_HUMIDITY': STATE_UNKNOWN})
# support state
if HM_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE:
self._data[HM_CONTROL_MODE] = STATE_UNKNOWN
for node in self._hmdevice.SENSORNODE.keys():
self._data[node] = STATE_UNKNOWN

View File

@ -0,0 +1,148 @@
"""
OpenEnergyMonitor Thermostat Support.
This provides a climate component for the ESP8266 based thermostat sold by
OpenEnergyMonitor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.oem/
"""
import logging
import requests
import voluptuous as vol
# Import the device class from the component that you want to support
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE, ATTR_TEMPERATURE)
from homeassistant.const import (CONF_HOST, CONF_USERNAME, CONF_PASSWORD,
CONF_PORT, TEMP_CELSIUS, CONF_NAME)
import homeassistant.helpers.config_validation as cv
# Home Assistant depends on 3rd party packages for API specific code.
REQUIREMENTS = ['oemthermostat==1.1']
_LOGGER = logging.getLogger(__name__)
# Local configs
CONF_AWAY_TEMP = 'away_temp'
# Validation of the user's configuration
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default="Thermostat"): cv.string,
vol.Optional(CONF_PORT, default=80): cv.port,
vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string,
vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string,
vol.Optional(CONF_AWAY_TEMP, default=14): vol.Coerce(float)
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup oemthermostat."""
from oemthermostat import Thermostat
# Assign configuration variables. The configuration check takes care they
# are present.
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
away_temp = config.get(CONF_AWAY_TEMP)
# If creating the class raises an exception, it failed to connect or
# something else went wrong.
try:
therm = Thermostat(host, port=port,
username=username, password=password)
except (ValueError, AssertionError, requests.RequestException):
return False
# Add devices
add_devices((ThermostatDevice(hass, therm, name, away_temp), ), True)
class ThermostatDevice(ClimateDevice):
"""Interface class for the oemthermostat module and HA."""
def __init__(self, hass, thermostat, name, away_temp):
"""Initialize the device."""
self._name = name
self.hass = hass
# Away mode stuff
self._away = False
self._away_temp = away_temp
self._prev_temp = thermostat.setpoint
self.thermostat = thermostat
# Set the thermostat mode to manual
self.thermostat.mode = 2
# set up internal state varS
self._state = None
self._temperature = None
self._setpoint = None
@property
def name(self):
"""Name of this Thermostat."""
return self._name
@property
def temperature_unit(self):
"""The unit of measurement used by the platform."""
return TEMP_CELSIUS
@property
def current_operation(self):
"""Return current operation i.e. heat, cool, idle."""
if self._state:
return STATE_HEAT
else:
return STATE_IDLE
@property
def current_temperature(self):
"""Return the current temperature."""
return self._temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._setpoint
def set_temperature(self, **kwargs):
"""Change the setpoint of the thermostat."""
# If we are setting the temp, then we don't want away mode anymore.
self.turn_away_mode_off()
temp = kwargs.get(ATTR_TEMPERATURE)
self.thermostat.setpoint = temp
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
def turn_away_mode_on(self):
"""Turn away mode on."""
if not self._away:
self._prev_temp = self._setpoint
self.thermostat.setpoint = self._away_temp
self._away = True
def turn_away_mode_off(self):
"""Turn away mode off."""
if self._away:
self.thermostat.setpoint = self._prev_temp
self._away = False
def update(self):
"""Update local state."""
self._setpoint = self.thermostat.setpoint
self._temperature = self.thermostat.temperature
self._state = self.thermostat.state

View File

@ -68,7 +68,6 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self._unit = temp_unit
_LOGGER.debug("temp_unit is %s", self._unit)
self._zxt_120 = None
self.update_properties()
# Make sure that we have values for the key before converting to int
if (value.node.manufacturer_id.strip() and
value.node.product_id.strip()):
@ -79,6 +78,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
_LOGGER.debug("Remotec ZXT-120 Zwave Thermostat"
" workaround")
self._zxt_120 = 1
self.update_properties()
def update_properties(self):
"""Callback on data changes for node values."""

View File

@ -0,0 +1,133 @@
"""Component to configure Home Assistant via an API."""
import asyncio
import os
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import EVENT_COMPONENT_LOADED
from homeassistant.bootstrap import (
async_prepare_setup_platform, ATTR_COMPONENT)
from homeassistant.components.frontend import register_built_in_panel
from homeassistant.components.http import HomeAssistantView
from homeassistant.util.yaml import load_yaml, dump
DOMAIN = 'config'
DEPENDENCIES = ['http']
SECTIONS = ('core', 'group', 'hassbian')
ON_DEMAND = ('zwave', )
@asyncio.coroutine
def async_setup(hass, config):
"""Setup the config component."""
register_built_in_panel(hass, 'config', 'Configuration', 'mdi:settings')
@asyncio.coroutine
def setup_panel(panel_name):
"""Setup a panel."""
panel = yield from async_prepare_setup_platform(hass, config, DOMAIN,
panel_name)
if not panel:
return
success = yield from panel.async_setup(hass)
if success:
key = '{}.{}'.format(DOMAIN, panel_name)
hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: key})
hass.config.components.add(key)
tasks = [setup_panel(panel_name) for panel_name in SECTIONS]
for panel_name in ON_DEMAND:
if panel_name in hass.config.components:
tasks.append(setup_panel(panel_name))
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
@callback
def component_loaded(event):
"""Respond to components being loaded."""
panel_name = event.data.get(ATTR_COMPONENT)
if panel_name in ON_DEMAND:
hass.async_add_job(setup_panel(panel_name))
hass.bus.async_listen(EVENT_COMPONENT_LOADED, component_loaded)
return True
class EditKeyBasedConfigView(HomeAssistantView):
"""Configure a Group endpoint."""
def __init__(self, component, config_type, path, key_schema, data_schema,
*, post_write_hook=None):
"""Initialize a config view."""
self.url = '/api/config/%s/%s/{config_key}' % (component, config_type)
self.name = 'api:config:%s:%s' % (component, config_type)
self.path = path
self.key_schema = key_schema
self.data_schema = data_schema
self.post_write_hook = post_write_hook
@asyncio.coroutine
def get(self, request, config_key):
"""Fetch device specific config."""
hass = request.app['hass']
current = yield from hass.loop.run_in_executor(
None, _read, hass.config.path(self.path))
return self.json(current.get(config_key, {}))
@asyncio.coroutine
def post(self, request, config_key):
"""Validate config and return results."""
try:
data = yield from request.json()
except ValueError:
return self.json_message('Invalid JSON specified', 400)
try:
self.key_schema(config_key)
except vol.Invalid as err:
return self.json_message('Key malformed: {}'.format(err), 400)
try:
# We just validate, we don't store that data because
# we don't want to store the defaults.
self.data_schema(data)
except vol.Invalid as err:
return self.json_message('Message malformed: {}'.format(err), 400)
hass = request.app['hass']
path = hass.config.path(self.path)
current = yield from hass.loop.run_in_executor(None, _read, path)
current.setdefault(config_key, {}).update(data)
yield from hass.loop.run_in_executor(None, _write, path, current)
if self.post_write_hook is not None:
hass.async_add_job(self.post_write_hook(hass))
return self.json({
'result': 'ok',
})
def _read(path):
"""Read YAML helper."""
if not os.path.isfile(path):
with open(path, 'w'):
pass
return {}
return load_yaml(path)
def _write(path, data):
"""Write YAML helper."""
with open(path, 'w', encoding='utf-8') as outfile:
outfile.write(dump(data))

View File

@ -0,0 +1,31 @@
"""Component to interact with Hassbian tools."""
import asyncio
from homeassistant.components.http import HomeAssistantView
from homeassistant.config import async_check_ha_config_file
@asyncio.coroutine
def async_setup(hass):
"""Setup the hassbian config."""
hass.http.register_view(CheckConfigView)
return True
class CheckConfigView(HomeAssistantView):
"""Hassbian packages endpoint."""
url = '/api/config/core/check_config'
name = 'api:config:core:check_config'
@asyncio.coroutine
def post(self, request):
"""Validate config and return results."""
errors = yield from async_check_ha_config_file(request.app['hass'])
state = 'invalid' if errors else 'valid'
return self.json({
"result": state,
"errors": errors,
})

View File

@ -0,0 +1,19 @@
"""Provide configuration end points for Groups."""
import asyncio
from homeassistant.components.config import EditKeyBasedConfigView
from homeassistant.components.group import GROUP_SCHEMA
import homeassistant.helpers.config_validation as cv
CONFIG_PATH = 'groups.yaml'
@asyncio.coroutine
def async_setup(hass):
"""Setup the Group config API."""
hass.http.register_view(EditKeyBasedConfigView(
'group', 'config', CONFIG_PATH, cv.slug,
GROUP_SCHEMA
))
return True

View File

@ -0,0 +1,91 @@
"""Component to interact with Hassbian tools."""
import asyncio
import json
import os
from homeassistant.components.http import HomeAssistantView
_TEST_OUTPUT = """
{
"suites":{
"libcec":{
"state":"Uninstalled",
"description":"Installs the libcec package for controlling CEC devices from this Pi"
},
"mosquitto":{
"state":"failed",
"description":"Installs the Mosquitto package for setting up a local MQTT server"
},
"openzwave":{
"state":"Uninstalled",
"description":"Installs the Open Z-wave package for setting up your zwave network"
},
"samba":{
"state":"installing",
"description":"Installs the samba package for sharing the hassbian configuration files over the Pi's network."
}
}
}
""" # noqa
@asyncio.coroutine
def async_setup(hass):
"""Setup the hassbian config."""
# Test if is hassbian
test_mode = 'FORCE_HASSBIAN' in os.environ
is_hassbian = test_mode
if not is_hassbian:
return False
hass.http.register_view(HassbianSuitesView(test_mode))
hass.http.register_view(HassbianSuiteInstallView(test_mode))
return True
@asyncio.coroutine
def hassbian_status(hass, test_mode=False):
"""Query for the Hassbian status."""
# fetch real output when not in test mode
if test_mode:
return json.loads(_TEST_OUTPUT)
raise Exception('Real mode not implemented yet.')
class HassbianSuitesView(HomeAssistantView):
"""Hassbian packages endpoint."""
url = '/api/config/hassbian/suites'
name = 'api:config:hassbian:suites'
def __init__(self, test_mode):
"""Initialize suites view."""
self._test_mode = test_mode
@asyncio.coroutine
def get(self, request):
"""Request suite status."""
inp = yield from hassbian_status(request.app['hass'], self._test_mode)
return self.json(inp['suites'])
class HassbianSuiteInstallView(HomeAssistantView):
"""Hassbian packages endpoint."""
url = '/api/config/hassbian/suites/{suite}/install'
name = 'api:config:hassbian:suite'
def __init__(self, test_mode):
"""Initialize suite view."""
self._test_mode = test_mode
@asyncio.coroutine
def post(self, request, suite):
"""Request suite status."""
# do real install if not in test mode
return self.json({"status": "ok"})

View File

@ -0,0 +1,19 @@
"""Provide configuration end points for Z-Wave."""
import asyncio
from homeassistant.components.config import EditKeyBasedConfigView
from homeassistant.components.zwave import DEVICE_CONFIG_SCHEMA_ENTRY
import homeassistant.helpers.config_validation as cv
CONFIG_PATH = 'zwave_device_config.yaml'
@asyncio.coroutine
def async_setup(hass):
"""Setup the Z-Wave config API."""
hass.http.register_view(EditKeyBasedConfigView(
'zwave', 'device_config', CONFIG_PATH, cv.entity_id,
DEVICE_CONFIG_SCHEMA_ENTRY
))
return True

View File

@ -6,15 +6,16 @@ This will return a request id that has to be used for future calls.
A callback has to be provided to `request_config` which will be called when
the user has submitted configuration information.
"""
import asyncio
import logging
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \
ATTR_ENTITY_PICTURE
from homeassistant.helpers.entity import generate_entity_id
_INSTANCES = {}
_LOGGER = logging.getLogger(__name__)
_REQUESTS = {}
_KEY_INSTANCE = 'configurator'
ATTR_CONFIGURE_ID = 'configure_id'
ATTR_DESCRIPTION = 'description'
@ -72,22 +73,20 @@ def request_done(request_id):
pass
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Setup the configurator component."""
return True
def _get_instance(hass):
"""Get an instance per hass object."""
try:
return _INSTANCES[hass]
except KeyError:
_INSTANCES[hass] = Configurator(hass)
instance = hass.data.get(_KEY_INSTANCE)
if DOMAIN not in hass.config.components:
hass.config.components.append(DOMAIN)
if instance is None:
instance = hass.data[_KEY_INSTANCE] = Configurator(hass)
return _INSTANCES[hass]
return instance
class Configurator(object):

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['fuzzywuzzy==0.14.0']
REQUIREMENTS = ['fuzzywuzzy==0.15.0']
ATTR_TEXT = 'text'

View File

@ -33,6 +33,20 @@ ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format('all_covers')
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DEVICE_CLASSES = [
'window', # Window control
'garage', # Garage door control
]
SUPPORT_OPEN = 1
SUPPORT_CLOSE = 2
SUPPORT_SET_POSITION = 4
SUPPORT_STOP = 8
SUPPORT_OPEN_TILT = 16
SUPPORT_CLOSE_TILT = 32
SUPPORT_STOP_TILT = 64
SUPPORT_SET_TILT_POSITION = 128
_LOGGER = logging.getLogger(__name__)
ATTR_CURRENT_POSITION = 'current_position'
@ -221,6 +235,21 @@ class CoverDevice(Entity):
return data
@property
def supported_features(self):
"""Flag supported features."""
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP
if self.current_cover_position is not None:
supported_features |= SUPPORT_SET_POSITION
if self.current_cover_tilt_position is not None:
supported_features |= (
SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT |
SUPPORT_SET_TILT_POSITION)
return supported_features
@property
def is_closed(self):
"""Return if the cover is closed or not."""

View File

@ -4,7 +4,8 @@ Demo platform for the cover component.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
from homeassistant.components.cover import CoverDevice
from homeassistant.components.cover import (
CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE)
from homeassistant.helpers.event import track_utc_time_change
@ -14,6 +15,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
DemoCover(hass, 'Kitchen Window'),
DemoCover(hass, 'Hall Window', 10),
DemoCover(hass, 'Living Room Window', 70, 50),
DemoCover(hass, 'Garage Door', device_class='garage',
supported_features=(SUPPORT_OPEN | SUPPORT_CLOSE)),
])
@ -21,11 +24,14 @@ class DemoCover(CoverDevice):
"""Representation of a demo cover."""
# pylint: disable=no-self-use
def __init__(self, hass, name, position=None, tilt_position=None):
def __init__(self, hass, name, position=None, tilt_position=None,
device_class=None, supported_features=None):
"""Initialize the cover."""
self.hass = hass
self._name = name
self._position = position
self._device_class = device_class
self._supported_features = supported_features
self._set_position = None
self._set_tilt_position = None
self._tilt_position = tilt_position
@ -33,6 +39,10 @@ class DemoCover(CoverDevice):
self._closing_tilt = True
self._unsub_listener_cover = None
self._unsub_listener_cover_tilt = None
if position is None:
self._closed = True
else:
self._closed = self.current_cover_position <= 0
@property
def name(self):
@ -57,17 +67,28 @@ class DemoCover(CoverDevice):
@property
def is_closed(self):
"""Return if the cover is closed."""
if self._position is not None:
if self.current_cover_position > 0:
return False
else:
return True
return self._closed
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return self._device_class
@property
def supported_features(self):
"""Flag supported features."""
if self._supported_features is not None:
return self._supported_features
else:
return None
return super().supported_features
def close_cover(self, **kwargs):
"""Close the cover."""
if self._position in (0, None):
if self._position == 0:
return
elif self._position is None:
self._closed = True
self.schedule_update_ha_state()
return
self._listen_cover()
@ -83,7 +104,11 @@ class DemoCover(CoverDevice):
def open_cover(self, **kwargs):
"""Open the cover."""
if self._position in (100, None):
if self._position == 100:
return
elif self._position is None:
self._closed = False
self.schedule_update_ha_state()
return
self._listen_cover()
@ -149,6 +174,9 @@ class DemoCover(CoverDevice):
if self._position in (100, 0, self._set_position):
self.stop_cover()
self._closed = self.current_cover_position <= 0
self.schedule_update_ha_state()
def _listen_cover_tilt(self):

View File

@ -168,6 +168,11 @@ class GaradgetCover(CoverDevice):
else:
return self._state == STATE_CLOSED
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return 'garage'
def get_token(self):
"""Get new token for usage during this session."""
args = {

View File

@ -10,28 +10,26 @@ properly configured.
import logging
from homeassistant.const import STATE_UNKNOWN
from homeassistant.components.cover import CoverDevice,\
ATTR_POSITION
from homeassistant.components.homematic import HMDevice
from homeassistant.loader import get_component
from homeassistant.components.cover import CoverDevice, ATTR_POSITION
from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['homematic']
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the platform."""
if discovery_info is None:
return
homematic = get_component("homematic")
return homematic.setup_hmdevice_discovery_helper(
hass,
HMCover,
discovery_info,
add_callback_devices
)
devices = []
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
new_device = HMCover(hass, config)
new_device.link_homematic()
devices.append(new_device)
add_devices(devices)
class HMCover(HMDevice, CoverDevice):
@ -44,18 +42,15 @@ class HMCover(HMDevice, CoverDevice):
None is unknown, 0 is closed, 100 is fully open.
"""
if self.available:
return int(self._hm_get_state() * 100)
return None
return int(self._hm_get_state() * 100)
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
if self.available:
if ATTR_POSITION in kwargs:
position = float(kwargs[ATTR_POSITION])
position = min(100, max(0, position))
level = position / 100.0
self._hmdevice.set_level(level, self._channel)
if ATTR_POSITION in kwargs:
position = float(kwargs[ATTR_POSITION])
position = min(100, max(0, position))
level = position / 100.0
self._hmdevice.set_level(level, self._channel)
@property
def is_closed(self):
@ -68,18 +63,15 @@ class HMCover(HMDevice, CoverDevice):
def open_cover(self, **kwargs):
"""Open the cover."""
if self.available:
self._hmdevice.move_up(self._channel)
self._hmdevice.move_up(self._channel)
def close_cover(self, **kwargs):
"""Close the cover."""
if self.available:
self._hmdevice.move_down(self._channel)
self._hmdevice.move_down(self._channel)
def stop_cover(self, **kwargs):
"""Stop the device if in motion."""
if self.available:
self._hmdevice.stop(self._channel)
self._hmdevice.stop(self._channel)
def _init_data_struct(self):
"""Generate a data dict (self._data) from hm metadata."""

View File

@ -4,6 +4,7 @@ Support for MQTT cover devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.mqtt/
"""
import asyncio
import logging
import voluptuous as vol
@ -46,13 +47,14 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
})
def setup_platform(hass, config, add_devices, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup the MQTT Cover."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices([MqttCover(
hass,
yield from async_add_devices([MqttCover(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_COMMAND_TOPIC),
@ -71,13 +73,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class MqttCover(CoverDevice):
"""Representation of a cover that can be controlled using MQTT."""
def __init__(self, hass, name, state_topic, command_topic, qos,
retain, state_open, state_closed, payload_open, payload_close,
def __init__(self, name, state_topic, command_topic, qos, retain,
state_open, state_closed, payload_open, payload_close,
payload_stop, optimistic, value_template):
"""Initialize the cover."""
self._position = None
self._state = None
self._hass = hass
self._name = name
self._state_topic = state_topic
self._command_topic = command_topic
@ -89,37 +90,45 @@ class MqttCover(CoverDevice):
self._state_closed = state_closed
self._retain = retain
self._optimistic = optimistic or state_topic is None
self._template = value_template
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe mqtt events.
This method is a coroutine.
"""
@callback
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = value_template.async_render_with_possible_json_value(
if self._template is not None:
payload = self._template.async_render_with_possible_json_value(
payload)
if payload == self._state_open:
self._state = False
hass.async_add_job(self.async_update_ha_state())
elif payload == self._state_closed:
self._state = True
hass.async_add_job(self.async_update_ha_state())
elif payload.isnumeric() and 0 <= int(payload) <= 100:
if int(payload) > 0:
self._state = False
else:
self._state = True
self._position = int(payload)
hass.async_add_job(self.async_update_ha_state())
else:
_LOGGER.warning(
"Payload is not True, False, or integer (0-100): %s",
payload)
return
self.hass.async_add_job(self.async_update_ha_state())
if self._state_topic is None:
# Force into optimistic mode.
self._optimistic = True
else:
mqtt.subscribe(hass, self._state_topic, message_received,
self._qos)
yield from mqtt.async_subscribe(
self.hass, self._state_topic, message_received, self._qos)
@property
def should_poll(self):
@ -144,25 +153,40 @@ class MqttCover(CoverDevice):
"""
return self._position
def open_cover(self, **kwargs):
"""Move the cover up."""
mqtt.publish(self.hass, self._command_topic, self._payload_open,
self._qos, self._retain)
@asyncio.coroutine
def async_open_cover(self, **kwargs):
"""Move the cover up.
This method is a coroutine.
"""
mqtt.async_publish(
self.hass, self._command_topic, self._payload_open, self._qos,
self._retain)
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = False
self.schedule_update_ha_state()
self.hass.async_add_job(self.async_update_ha_state())
def close_cover(self, **kwargs):
"""Move the cover down."""
mqtt.publish(self.hass, self._command_topic, self._payload_close,
self._qos, self._retain)
@asyncio.coroutine
def async_close_cover(self, **kwargs):
"""Move the cover down.
This method is a coroutine.
"""
mqtt.async_publish(
self.hass, self._command_topic, self._payload_close, self._qos,
self._retain)
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = True
self.schedule_update_ha_state()
self.hass.async_add_job(self.async_update_ha_state())
def stop_cover(self, **kwargs):
"""Stop the device."""
mqtt.publish(self.hass, self._command_topic, self._payload_stop,
self._qos, self._retain)
@asyncio.coroutine
def async_stop_cover(self, **kwargs):
"""Stop the device.
This method is a coroutine.
"""
mqtt.async_publish(
self.hass, self._command_topic, self._payload_stop, self._qos,
self._retain)

View File

@ -0,0 +1,90 @@
"""
Support for MyQ-Enabled Garage Doors.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/cover.myq/
"""
import logging
import voluptuous as vol
from homeassistant.components.cover import CoverDevice
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, CONF_TYPE, STATE_CLOSED)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = [
'https://github.com/arraylabs/pymyq/archive/v0.0.6.zip'
'#pymyq==0.0.6']
COVER_SCHEMA = vol.Schema({
vol.Required(CONF_TYPE): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
DEFAULT_NAME = 'myq'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MyQ component."""
from pymyq import MyQAPI as pymyq
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
brand = config.get(CONF_TYPE)
logger = logging.getLogger(__name__)
myq = pymyq(username, password, brand)
if not myq.is_supported_brand():
logger.error('MyQ Cover - Unsupported Type. See documentation')
return
if not myq.is_login_valid():
logger.error('MyQ Cover - Username or Password is incorrect')
return
try:
add_devices(MyQDevice(myq, door) for door in myq.get_garage_doors())
except (TypeError, KeyError, NameError) as ex:
logger.error("MyQ Cover - %s", ex)
class MyQDevice(CoverDevice):
"""Representation of a MyQ cover."""
def __init__(self, myq, device):
"""Initialize with API object, device id."""
self.myq = myq
self.device_id = device['deviceid']
self._name = device['name']
self._status = STATE_CLOSED
@property
def should_poll(self):
"""Poll for state."""
return True
@property
def name(self):
"""Return the name of the garage door if any."""
return self._name if self._name else DEFAULT_NAME
@property
def is_closed(self):
"""Return True if cover is closed, else False."""
return self._status == STATE_CLOSED
def close_cover(self):
"""Issue close command to cover."""
self.myq.close_device(self.device_id)
def open_cover(self):
"""Issue open command to cover."""
self.myq.open_device(self.device_id)
def update(self):
"""Update status of cover."""
self._status = self.myq.get_status(self.device_id)

View File

@ -7,22 +7,17 @@ https://home-assistant.io/components/cover.zwave/
# Because we do not compile openzwave on CI
# pylint: disable=import-error
import logging
from homeassistant.components.cover import DOMAIN
from homeassistant.components.cover import (
DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.zwave import workaround
from homeassistant.components.cover import CoverDevice
SOMFY = 0x47
SOMFY_ZRTSI = 0x5a52
SOMFY_ZRTSI_CONTROLLER = (SOMFY, SOMFY_ZRTSI)
WORKAROUND = 'workaround'
DEVICE_MAPPINGS = {
SOMFY_ZRTSI_CONTROLLER: WORKAROUND
}
_LOGGER = logging.getLogger(__name__)
SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and return Z-Wave covers."""
@ -58,16 +53,11 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
self._open_id = None
self._close_id = None
self._current_position = None
self._workaround = None
if (value.node.manufacturer_id.strip() and
value.node.product_id.strip()):
specific_sensor_key = (int(value.node.manufacturer_id, 16),
int(value.node.product_type, 16))
if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND:
_LOGGER.debug("Controller without positioning feedback")
self._workaround = 1
self._workaround = workaround.get_device_mapping(value)
if self._workaround:
_LOGGER.debug("Using workaround %s", self._workaround)
self.update_properties()
def update_properties(self):
"""Callback on data changes for node values."""
@ -81,6 +71,8 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
self._close_id = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Close', 'Down'], member='value_id')
if self._workaround == workaround.WORKAROUND_REVERSE_OPEN_CLOSE:
self._open_id, self._close_id = self._close_id, self._open_id
@property
def is_closed(self):
@ -95,14 +87,15 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
@property
def current_cover_position(self):
"""Return the current position of Zwave roller shutter."""
if not self._workaround:
if self._current_position is not None:
if self._current_position <= 5:
return 0
elif self._current_position >= 95:
return 100
else:
return self._current_position
if self._workaround == workaround.WORKAROUND_NO_POSITION:
return None
if self._current_position is not None:
if self._current_position <= 5:
return 0
elif self._current_position >= 95:
return 100
else:
return self._current_position
def open_cover(self, **kwargs):
"""Move the roller shutter up."""
@ -127,11 +120,16 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):
def __init__(self, value):
"""Initialize the zwave garage door."""
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
self.update_properties()
def update_properties(self):
"""Callback on data changes for node values."""
self._state = self._value.data
@property
def is_closed(self):
"""Return the current position of Zwave garage door."""
return not self._value.data
return not self._state
def close_cover(self):
"""Close the garage door."""
@ -140,3 +138,13 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):
def open_cover(self):
"""Open the garage door."""
self._value.data = True
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return 'garage'
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_GARAGE

View File

@ -25,6 +25,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import config_per_platform, discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType
import homeassistant.helpers.config_validation as cv
import homeassistant.util as util
@ -132,6 +133,12 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
devices = yield from async_load_config(yaml_path, hass, consider_home)
tracker = DeviceTracker(hass, consider_home, track_new, devices)
# added_to_hass
add_tasks = [device.async_added_to_hass() for device in devices
if device.track]
if add_tasks:
yield from asyncio.wait(add_tasks, loop=hass.loop)
# update tracked devices
update_tasks = [device.async_update_ha_state() for device in devices
if device.track]
@ -167,8 +174,8 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
raise HomeAssistantError("Invalid device_tracker platform.")
if scanner:
yield from async_setup_scanner_platform(
hass, p_config, scanner, tracker.async_see)
async_setup_scanner_platform(
hass, p_config, scanner, tracker.async_see, p_type)
return
if not setup:
@ -561,6 +568,26 @@ class Device(Entity):
if resp is not None:
yield from resp.release()
@asyncio.coroutine
def async_added_to_hass(self):
"""Called when entity about to be added to hass."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if not state:
return
self._state = state.state
for attr, var in (
(ATTR_SOURCE_TYPE, 'source_type'),
(ATTR_GPS_ACCURACY, 'gps_accuracy'),
(ATTR_BATTERY, 'battery'),
):
if attr in state.attributes:
setattr(self, var, state.attributes[attr])
if ATTR_LONGITUDE in state.attributes:
self.gps = (state.attributes[ATTR_LATITUDE],
state.attributes[ATTR_LONGITUDE])
class DeviceScanner(object):
"""Device scanner object."""
@ -638,14 +665,16 @@ def async_load_config(path: str, hass: HomeAssistantType,
return []
@asyncio.coroutine
@callback
def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType,
scanner: Any, async_see_device: Callable):
scanner: Any, async_see_device: Callable,
platform: str):
"""Helper method to connect scanner-based platform to device tracker.
This method is a coroutine.
This method must be run in the event loop.
"""
interval = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
update_lock = asyncio.Lock(loop=hass.loop)
scanner.hass = hass
# Initial scan of each mac we also tell about host name for config
@ -654,7 +683,14 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType,
@asyncio.coroutine
def async_device_tracker_scan(now: dt_util.dt.datetime):
"""Called when interval matches."""
found_devices = yield from scanner.async_scan_devices()
if update_lock.locked():
_LOGGER.warning(
"Updating device list from %s took longer than the scheduled "
"scan interval %s", platform, interval)
return
with (yield from update_lock):
found_devices = yield from scanner.async_scan_devices()
for mac in found_devices:
if mac in seen:
@ -678,7 +714,7 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType,
hass.async_add_job(async_see_device(**kwargs))
async_track_time_interval(hass, async_device_tracker_scan, interval)
hass.async_add_job(async_device_tracker_scan, None)
hass.async_add_job(async_device_tracker_scan(None))
def update_config(path: str, dev_id: str, device: Device):

View File

@ -16,7 +16,8 @@ import voluptuous as vol
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
@ -25,6 +26,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
CONF_PROTOCOL = 'protocol'
CONF_MODE = 'mode'
DEFAULT_SSH_PORT = 22
CONF_SSH_KEY = 'ssh_key'
CONF_PUB_KEY = 'pub_key'
SECRET_GROUP = 'Password or SSH Key'
@ -38,6 +40,7 @@ PLATFORM_SCHEMA = vol.All(
vol.In(['ssh', 'telnet']),
vol.Optional(CONF_MODE, default='router'):
vol.In(['router', 'ap']),
vol.Optional(CONF_PORT, default=DEFAULT_SSH_PORT): cv.port,
vol.Exclusive(CONF_PASSWORD, SECRET_GROUP): cv.string,
vol.Exclusive(CONF_SSH_KEY, SECRET_GROUP): cv.isfile,
vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile
@ -112,12 +115,16 @@ class AsusWrtDeviceScanner(DeviceScanner):
self.ssh_key = config.get('ssh_key', config.get('pub_key', ''))
self.protocol = config[CONF_PROTOCOL]
self.mode = config[CONF_MODE]
self.port = config[CONF_PORT]
self.ssh_args = {}
if self.protocol == 'ssh':
self.ssh_args['port'] = self.port
if self.ssh_key:
self.ssh_secret = {'ssh_key': self.ssh_key}
self.ssh_args['ssh_key'] = self.ssh_key
elif self.password:
self.ssh_secret = {'password': self.password}
self.ssh_args['password'] = self.password
else:
_LOGGER.error('No password or private key specified')
self.success_init = False
@ -179,7 +186,7 @@ class AsusWrtDeviceScanner(DeviceScanner):
ssh = pxssh.pxssh()
try:
ssh.login(self.host, self.username, **self.ssh_secret)
ssh.login(self.host, self.username, **self.ssh_args)
except exceptions.EOF as err:
_LOGGER.error('Connection refused. Is SSH enabled?')
return None

View File

@ -13,28 +13,31 @@ import aiohttp
import async_timeout
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.helpers.aiohttp_client import async_create_clientsession
# Configuration constant specific for tado
CONF_HOME_ID = 'home_id'
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string
vol.Optional(CONF_HOME_ID): cv.string
})
def get_scanner(hass, config):
"""Return a Tado scanner."""
scanner = TadoDeviceScanner(hass, config[DOMAIN])
return scanner if scanner.success_init else None
@ -50,8 +53,19 @@ class TadoDeviceScanner(DeviceScanner):
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.tadoapiurl = 'https://my.tado.com/api/v2/me' \
'?username={}&password={}'
# The Tado device tracker can work with or without a home_id
self.home_id = config[CONF_HOME_ID] if CONF_HOME_ID in config else None
# If there's a home_id, we need a different API URL
if self.home_id is None:
self.tadoapiurl = 'https://my.tado.com/api/v2/me'
else:
self.tadoapiurl = 'https://my.tado.com/api/v2' \
'/homes/{home_id}/mobileDevices'
# The API URL always needs a username and password
self.tadoapiurl += '?username={username}&password={password}'
self.websession = async_create_clientsession(
hass, cookie_jar=aiohttp.CookieJar(unsafe=True, loop=hass.loop))
@ -62,7 +76,11 @@ class TadoDeviceScanner(DeviceScanner):
@asyncio.coroutine
def async_scan_devices(self):
"""Scan for devices and return a list containing found device ids."""
yield from self._update_info()
info = self._update_info()
# Don't yield if we got None
if info is not None:
yield from info
return [device.mac for device in self.last_results]
@ -87,43 +105,54 @@ class TadoDeviceScanner(DeviceScanner):
_LOGGER.debug("Requesting Tado")
last_results = []
response = None
tadojson = None
tado_json = None
try:
# get first token
with async_timeout.timeout(10, loop=self.hass.loop):
url = self.tadoapiurl.format(self.username, self.password)
response = yield from self.websession.get(
url
)
# Format the URL here, so we can log the template URL if
# anything goes wrong without exposing username and password.
url = self.tadoapiurl.format(home_id=self.home_id,
username=self.username,
password=self.password)
# Go get 'em!
response = yield from self.websession.get(url)
# error on Tado webservice
if response.status != 200:
_LOGGER.warning(
"Error %d on %s.", response.status, self.tadoapiurl)
self.token = None
return
tadojson = yield from response.json()
tado_json = yield from response.json()
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.error("Can not load Tado data")
_LOGGER.error("Cannot load Tado data")
return False
finally:
if response is not None:
yield from response.release()
# Find devices that have geofencing enabled, and are currently at home
for mobiledevice in tadojson['mobileDevices']:
if 'location' in mobiledevice:
if mobiledevice['location']['atHome']:
deviceid = mobiledevice['id']
devicename = mobiledevice['name']
last_results.append(Device(deviceid, devicename))
# Without a home_id, we fetched an URL where the mobile devices can be
# found under the mobileDevices key.
if 'mobileDevices' in tado_json:
tado_json = tado_json['mobileDevices']
# Find devices that have geofencing enabled, and are currently at home.
for mobile_device in tado_json:
if 'location' in mobile_device:
if mobile_device['location']['atHome']:
device_id = mobile_device['id']
device_name = mobile_device['name']
last_results.append(Device(device_id, device_name))
self.last_results = last_results
_LOGGER.info("Tado presence query successful")
_LOGGER.info(
"Tado presence query successful, %d device(s) at home",
len(self.last_results)
)
return True

View File

@ -12,6 +12,7 @@ from homeassistant.components.device_tracker import PLATFORM_SCHEMA
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.util import slugify
_LOGGER = logging.getLogger(__name__)
@ -58,7 +59,7 @@ class TrackRDeviceScanner(object):
trackr_id = trackr.tracker_id()
trackr_device_id = trackr.id()
lost = trackr.lost()
dev_id = trackr.name().replace(" ", "_")
dev_id = slugify(trackr.name())
if dev_id is None:
dev_id = trackr_id
location = trackr.last_known_location()

View File

@ -17,7 +17,7 @@ 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.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.aiohttp_client import async_get_clientsession
_LOGGER = logging.getLogger(__name__)
@ -63,19 +63,13 @@ class UPCDeviceScanner(DeviceScanner):
"Chrome/47.0.2526.106 Safari/537.36")
}
self.websession = async_create_clientsession(
hass, auto_cleanup=False,
cookie_jar=aiohttp.CookieJar(unsafe=True, loop=hass.loop)
)
self.websession = async_get_clientsession(hass)
@asyncio.coroutine
def async_logout(event):
"""Logout from upc connect box."""
try:
yield from self._async_ws_function(CMD_LOGOUT)
self.token = None
finally:
self.websession.detach()
yield from self._async_ws_function(CMD_LOGOUT)
self.token = None
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, async_logout)
@ -92,8 +86,7 @@ class UPCDeviceScanner(DeviceScanner):
raw = yield from self._async_ws_function(CMD_DEVICES)
try:
xml_root = yield from self.hass.loop.run_in_executor(
None, ET.fromstring, raw)
xml_root = ET.fromstring(raw)
return [mac.text for mac in xml_root.iter('MACAddr')]
except (ET.ParseError, TypeError):
_LOGGER.warning("Can't read device from %s", self.host)
@ -111,7 +104,6 @@ class UPCDeviceScanner(DeviceScanner):
response = None
try:
# get first token
self.websession.cookie_jar.clear()
with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from self.websession.get(
"http://{}/common_page/login.html".format(self.host)
@ -150,27 +142,26 @@ class UPCDeviceScanner(DeviceScanner):
if additional_form:
form_data.update(additional_form)
redirects = True if function != CMD_DEVICES else False
response = None
try:
with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from self.websession.post(
"http://{}/xml/getter.xml".format(self.host),
data=form_data,
headers=self.headers
headers=self.headers,
allow_redirects=redirects
)
# error on UPC webservice
# error?
if response.status != 200:
_LOGGER.warning(
"Error %d on %s.", response.status, function)
_LOGGER.warning("Receive http code %d", response.status)
self.token = None
return
# load data, store token for next request
raw = yield from response.text()
self.token = response.cookies['sessionToken'].value
return raw
return (yield from response.text())
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.error("Error on %s", function)

View File

@ -1,97 +1,36 @@
"""
Support for Volvo On Call.
Support for tracking a Volvo.
http://www.volvocars.com/intl/own/owner-info/volvo-on-call
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.volvooncall/
"""
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.util import slugify
from homeassistant.const import (
CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME)
from homeassistant.components.device_tracker import (
DEFAULT_SCAN_INTERVAL, PLATFORM_SCHEMA)
MIN_TIME_BETWEEN_SCANS = timedelta(minutes=1)
from homeassistant.components.volvooncall import DOMAIN
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['volvooncall==0.1.1']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
def setup_scanner(hass, config, see, discovery_info=None):
"""Validate the configuration and return a scanner."""
from volvooncall import Connection
connection = Connection(
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD))
"""Setup Volvo tracker."""
if discovery_info is None:
return
interval = max(MIN_TIME_BETWEEN_SCANS,
config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL))
vin, _ = discovery_info
vehicle = hass.data[DOMAIN].vehicles[vin]
def _see_vehicle(vehicle):
position = vehicle["position"]
dev_id = "volvo_" + slugify(vehicle["registrationNumber"])
host_name = "%s (%s/%s)" % (
vehicle["registrationNumber"],
vehicle["vehicleType"],
vehicle["modelYear"])
def any_opened(door):
"""True if any door/window is opened."""
return any([door[key] for key in door if "Open" in key])
attributes = dict(
unlocked=not vehicle["carLocked"],
tank_volume=vehicle["fuelTankVolume"],
average_fuel_consumption=round(
vehicle["averageFuelConsumption"] / 10, 1), # l/100km
washer_fluid_low=vehicle["washerFluidLevel"] != "Normal",
brake_fluid_low=vehicle["brakeFluid"] != "Normal",
service_warning=vehicle["serviceWarningStatus"] != "Normal",
bulb_failures=len(vehicle["bulbFailures"]) > 0,
doors_open=any_opened(vehicle["doors"]),
windows_open=any_opened(vehicle["windows"]),
fuel=vehicle["fuelAmount"],
odometer=round(vehicle["odometer"] / 1000), # km
range=vehicle["distanceToEmpty"])
if "heater" in vehicle and \
"status" in vehicle["heater"]:
attributes.update(heater_on=vehicle["heater"]["status"] != "off")
host_name = vehicle.registration_number
dev_id = 'volvo_' + slugify(host_name)
def see_vehicle(vehicle):
"""Callback for reporting vehicle position."""
see(dev_id=dev_id,
host_name=host_name,
gps=(position["latitude"],
position["longitude"]),
attributes=attributes)
gps=(vehicle.position['latitude'],
vehicle.position['longitude']))
def update(now):
"""Update status from the online service."""
_LOGGER.info("Updating")
try:
res, vehicles = connection.update()
if not res:
_LOGGER.error("Could not query server")
return False
hass.data[DOMAIN].entities[vin].append(see_vehicle)
see_vehicle(vehicle)
for vehicle in vehicles:
_see_vehicle(vehicle)
return True
finally:
track_point_in_utc_time(hass, update, now + interval)
_LOGGER.info('Logging in to service')
return update(utcnow())
return True

View File

@ -11,6 +11,7 @@ import threading
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.discovery import load_platform, discover
@ -41,10 +42,16 @@ SERVICE_HANDLERS = {
'yeelight': ('light', 'yeelight'),
'flux_led': ('light', 'flux_led'),
'apple_tv': ('media_player', 'apple_tv'),
'openhome': ('media_player', 'openhome'),
}
CONF_IGNORE = 'ignore'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
DOMAIN: vol.Schema({
vol.Optional(CONF_IGNORE, default=[]):
vol.All(cv.ensure_list, [vol.In(SERVICE_HANDLERS)])
}),
}, extra=vol.ALLOW_EXTRA)
@ -57,10 +64,17 @@ def setup(hass, config):
# Disable zeroconf logging, it spams
logging.getLogger('zeroconf').setLevel(logging.CRITICAL)
# Platforms ignore by config
ignored_platforms = config[DOMAIN][CONF_IGNORE]
lock = threading.Lock()
def new_service_listener(service, info):
"""Called when a new service is found."""
if service in ignored_platforms:
logger.info("Ignoring service: %s %s", service, info)
return
with lock:
logger.info("Found new service: %s %s", service, info)

View File

@ -4,20 +4,24 @@ Support for Envisalink devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/envisalink/
"""
import asyncio
import logging
import time
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.entity import Entity
from homeassistant.components.discovery import load_platform
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
REQUIREMENTS = ['pyenvisalink==2.0', 'pydispatcher==2.0.5']
REQUIREMENTS = ['pyenvisalink==2.0']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'envisalink'
EVL_CONTROLLER = None
DATA_EVL = 'envisalink'
CONF_EVL_HOST = 'host'
CONF_EVL_PORT = 'port'
@ -43,9 +47,9 @@ DEFAULT_ZONEDUMP_INTERVAL = 30
DEFAULT_ZONETYPE = 'opening'
DEFAULT_PANIC = 'Police'
SIGNAL_ZONE_UPDATE = 'zones_updated'
SIGNAL_PARTITION_UPDATE = 'partition_updated'
SIGNAL_KEYPAD_UPDATE = 'keypad_updated'
SIGNAL_ZONE_UPDATE = 'envisalink.zones_updated'
SIGNAL_PARTITION_UPDATE = 'envisalink.partition_updated'
SIGNAL_KEYPAD_UPDATE = 'envisalink.keypad_updated'
ZONE_SCHEMA = vol.Schema({
vol.Required(CONF_ZONENAME): cv.string,
@ -77,119 +81,111 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument
def setup(hass, base_config):
@asyncio.coroutine
def async_setup(hass, config):
"""Common setup for Envisalink devices."""
from pyenvisalink import EnvisalinkAlarmPanel
from pydispatch import dispatcher
global EVL_CONTROLLER
conf = config.get(DOMAIN)
config = base_config.get(DOMAIN)
host = conf.get(CONF_EVL_HOST)
port = conf.get(CONF_EVL_PORT)
code = conf.get(CONF_CODE)
panel_type = conf.get(CONF_PANEL_TYPE)
panic_type = conf.get(CONF_PANIC)
version = conf.get(CONF_EVL_VERSION)
user = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASS)
keep_alive = conf.get(CONF_EVL_KEEPALIVE)
zone_dump = conf.get(CONF_ZONEDUMP_INTERVAL)
zones = conf.get(CONF_ZONES)
partitions = conf.get(CONF_PARTITIONS)
sync_connect = asyncio.Future(loop=hass.loop)
_host = config.get(CONF_EVL_HOST)
_port = config.get(CONF_EVL_PORT)
_code = config.get(CONF_CODE)
_panel_type = config.get(CONF_PANEL_TYPE)
_panic_type = config.get(CONF_PANIC)
_version = config.get(CONF_EVL_VERSION)
_user = config.get(CONF_USERNAME)
_pass = config.get(CONF_PASS)
_keep_alive = config.get(CONF_EVL_KEEPALIVE)
_zone_dump = config.get(CONF_ZONEDUMP_INTERVAL)
_zones = config.get(CONF_ZONES)
_partitions = config.get(CONF_PARTITIONS)
_connect_status = {}
EVL_CONTROLLER = EnvisalinkAlarmPanel(_host,
_port,
_panel_type,
_version,
_user,
_pass,
_zone_dump,
_keep_alive,
hass.loop)
controller = EnvisalinkAlarmPanel(
host, port, panel_type, version, user, password, zone_dump,
keep_alive, hass.loop)
hass.data[DATA_EVL] = controller
@callback
def login_fail_callback(data):
"""Callback for when the evl rejects our login."""
_LOGGER.error("The envisalink rejected your credentials.")
_connect_status['fail'] = 1
sync_connect.set_result(False)
@callback
def connection_fail_callback(data):
"""Network failure callback."""
_LOGGER.error("Could not establish a connection with the envisalink.")
_connect_status['fail'] = 1
sync_connect.set_result(False)
@callback
def connection_success_callback(data):
"""Callback for a successful connection."""
_LOGGER.info("Established a connection with the envisalink.")
_connect_status['success'] = 1
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink)
sync_connect.set_result(True)
@callback
def zones_updated_callback(data):
"""Handle zone timer updates."""
_LOGGER.info("Envisalink sent a zone update event. Updating zones...")
dispatcher.send(signal=SIGNAL_ZONE_UPDATE,
sender=None,
zone=data)
async_dispatcher_send(hass, SIGNAL_ZONE_UPDATE, data)
@callback
def alarm_data_updated_callback(data):
"""Handle non-alarm based info updates."""
_LOGGER.info("Envisalink sent new alarm info. Updating alarms...")
dispatcher.send(signal=SIGNAL_KEYPAD_UPDATE,
sender=None,
partition=data)
async_dispatcher_send(hass, SIGNAL_KEYPAD_UPDATE, data)
@callback
def partition_updated_callback(data):
"""Handle partition changes thrown by evl (including alarms)."""
_LOGGER.info("The envisalink sent a partition update event.")
dispatcher.send(signal=SIGNAL_PARTITION_UPDATE,
sender=None,
partition=data)
async_dispatcher_send(hass, SIGNAL_PARTITION_UPDATE, data)
@callback
def stop_envisalink(event):
"""Shutdown envisalink connection and thread on exit."""
_LOGGER.info("Shutting down envisalink.")
EVL_CONTROLLER.stop()
controller.stop()
def start_envisalink(event):
"""Startup process for the Envisalink."""
hass.loop.call_soon_threadsafe(EVL_CONTROLLER.start)
for _ in range(10):
if 'success' in _connect_status:
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink)
return True
elif 'fail' in _connect_status:
return False
else:
time.sleep(1)
controller.callback_zone_timer_dump = zones_updated_callback
controller.callback_zone_state_change = zones_updated_callback
controller.callback_partition_state_change = partition_updated_callback
controller.callback_keypad_update = alarm_data_updated_callback
controller.callback_login_failure = login_fail_callback
controller.callback_login_timeout = connection_fail_callback
controller.callback_login_success = connection_success_callback
_LOGGER.error("Timeout occurred while establishing evl connection.")
return False
_LOGGER.info("Start envisalink.")
controller.start()
EVL_CONTROLLER.callback_zone_timer_dump = zones_updated_callback
EVL_CONTROLLER.callback_zone_state_change = zones_updated_callback
EVL_CONTROLLER.callback_partition_state_change = partition_updated_callback
EVL_CONTROLLER.callback_keypad_update = alarm_data_updated_callback
EVL_CONTROLLER.callback_login_failure = login_fail_callback
EVL_CONTROLLER.callback_login_timeout = connection_fail_callback
EVL_CONTROLLER.callback_login_success = connection_success_callback
_result = start_envisalink(None)
if not _result:
result = yield from sync_connect
if not result:
return False
# Load sub-components for Envisalink
if _partitions:
load_platform(hass, 'alarm_control_panel', 'envisalink',
{CONF_PARTITIONS: _partitions,
CONF_CODE: _code,
CONF_PANIC: _panic_type}, base_config)
load_platform(hass, 'sensor', 'envisalink',
{CONF_PARTITIONS: _partitions,
CONF_CODE: _code}, base_config)
if _zones:
load_platform(hass, 'binary_sensor', 'envisalink',
{CONF_ZONES: _zones}, base_config)
if partitions:
hass.async_add_job(async_load_platform(
hass, 'alarm_control_panel', 'envisalink', {
CONF_PARTITIONS: partitions,
CONF_CODE: code,
CONF_PANIC: panic_type
}, config
))
hass.async_add_job(async_load_platform(
hass, 'sensor', 'envisalink', {
CONF_PARTITIONS: partitions,
CONF_CODE: code
}, config
))
if zones:
hass.async_add_job(async_load_platform(
hass, 'binary_sensor', 'envisalink', {
CONF_ZONES: zones
}, config
))
return True

View File

@ -4,10 +4,12 @@ Support for MQTT fans.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/fan.mqtt/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_STATE, STATE_ON, STATE_OFF,
@ -73,11 +75,10 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup MQTT fan platform."""
add_devices([MqttFan(
hass,
yield from async_add_devices([MqttFan(
config.get(CONF_NAME),
{
key: config.get(key) for key in (
@ -113,15 +114,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class MqttFan(FanEntity):
"""A MQTT fan component."""
def __init__(self, hass, name, topic, templates, qos, retain, payload,
def __init__(self, name, topic, templates, qos, retain, payload,
speed_list, optimistic):
"""Initialize the MQTT fan."""
self._hass = hass
self._name = name
self._topic = topic
self._qos = qos
self._retain = retain
self._payload = payload
self._templates = templates
self._speed_list = speed_list
self._optimistic = optimistic or topic[CONF_STATE_TOPIC] is None
self._optimistic_oscillation = (
@ -129,19 +130,29 @@ class MqttFan(FanEntity):
self._optimistic_speed = (
optimistic or topic[CONF_SPEED_STATE_TOPIC] is None)
self._state = False
self._speed = None
self._oscillation = None
self._supported_features = 0
self._supported_features |= (topic[CONF_OSCILLATION_STATE_TOPIC]
is not None and SUPPORT_OSCILLATE)
self._supported_features |= (topic[CONF_SPEED_STATE_TOPIC]
is not None and SUPPORT_SET_SPEED)
for key, tpl in list(templates.items()):
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe mqtt events.
This method is a coroutine.
"""
templates = {}
for key, tpl in list(self._templates.items()):
if tpl is None:
templates[key] = lambda value: value
else:
tpl.hass = hass
templates[key] = tpl.render_with_possible_json_value
tpl.hass = self.hass
templates[key] = tpl.async_render_with_possible_json_value
@callback
def state_received(topic, payload, qos):
"""A new MQTT message has been received."""
payload = templates[CONF_STATE](payload)
@ -149,13 +160,14 @@ class MqttFan(FanEntity):
self._state = True
elif payload == self._payload[STATE_OFF]:
self._state = False
self.schedule_update_ha_state()
self.hass.async_add_job(self.async_update_ha_state())
if self._topic[CONF_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass, self._topic[CONF_STATE_TOPIC],
state_received, self._qos)
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_STATE_TOPIC], state_received,
self._qos)
@callback
def speed_received(topic, payload, qos):
"""A new MQTT message for the speed has been received."""
payload = templates[ATTR_SPEED](payload)
@ -165,17 +177,15 @@ class MqttFan(FanEntity):
self._speed = SPEED_MEDIUM
elif payload == self._payload[SPEED_HIGH]:
self._speed = SPEED_HIGH
self.schedule_update_ha_state()
self.hass.async_add_job(self.async_update_ha_state())
if self._topic[CONF_SPEED_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass, self._topic[CONF_SPEED_STATE_TOPIC],
speed_received, self._qos)
self._speed = SPEED_OFF
elif self._topic[CONF_SPEED_COMMAND_TOPIC] is not None:
self._speed = SPEED_OFF
else:
self._speed = SPEED_OFF
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_SPEED_STATE_TOPIC], speed_received,
self._qos)
self._speed = SPEED_OFF
@callback
def oscillation_received(topic, payload, qos):
"""A new MQTT message has been received."""
payload = templates[OSCILLATION](payload)
@ -183,17 +193,13 @@ class MqttFan(FanEntity):
self._oscillation = True
elif payload == self._payload[OSCILLATE_OFF_PAYLOAD]:
self._oscillation = False
self.schedule_update_ha_state()
self.hass.async_add_job(self.async_update_ha_state())
if self._topic[CONF_OSCILLATION_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass,
self._topic[CONF_OSCILLATION_STATE_TOPIC],
oscillation_received, self._qos)
self._oscillation = False
if self._topic[CONF_OSCILLATION_COMMAND_TOPIC] is not None:
self._oscillation = False
else:
self._oscillation = False
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_OSCILLATION_STATE_TOPIC],
oscillation_received, self._qos)
self._oscillation = False
@property
def should_poll(self):
@ -235,43 +241,72 @@ class MqttFan(FanEntity):
"""Return the oscillation state."""
return self._oscillation
def turn_on(self, speed: str=None) -> None:
"""Turn on the entity."""
mqtt.publish(self._hass, self._topic[CONF_COMMAND_TOPIC],
self._payload[STATE_ON], self._qos, self._retain)
@asyncio.coroutine
def async_turn_on(self, speed: str=None) -> None:
"""Turn on the entity.
This method is a coroutine.
"""
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC],
self._payload[STATE_ON], self._qos, self._retain)
if speed:
self.set_speed(speed)
yield from self.async_set_speed(speed)
def turn_off(self) -> None:
"""Turn off the entity."""
mqtt.publish(self._hass, self._topic[CONF_COMMAND_TOPIC],
self._payload[STATE_OFF], self._qos, self._retain)
@asyncio.coroutine
def async_turn_off(self) -> None:
"""Turn off the entity.
def set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
if self._topic[CONF_SPEED_COMMAND_TOPIC] is not None:
mqtt_payload = SPEED_OFF
if speed == SPEED_LOW:
mqtt_payload = self._payload[SPEED_LOW]
elif speed == SPEED_MEDIUM:
mqtt_payload = self._payload[SPEED_MEDIUM]
elif speed == SPEED_HIGH:
mqtt_payload = self._payload[SPEED_HIGH]
else:
mqtt_payload = speed
This method is a coroutine.
"""
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC],
self._payload[STATE_OFF], self._qos, self._retain)
@asyncio.coroutine
def async_set_speed(self, speed: str) -> None:
"""Set the speed of the fan.
This method is a coroutine.
"""
if self._topic[CONF_SPEED_COMMAND_TOPIC] is None:
return
if speed == SPEED_LOW:
mqtt_payload = self._payload[SPEED_LOW]
elif speed == SPEED_MEDIUM:
mqtt_payload = self._payload[SPEED_MEDIUM]
elif speed == SPEED_HIGH:
mqtt_payload = self._payload[SPEED_HIGH]
else:
mqtt_payload = speed
mqtt.async_publish(
self.hass, self._topic[CONF_SPEED_COMMAND_TOPIC],
mqtt_payload, self._qos, self._retain)
if self._optimistic_speed:
self._speed = speed
mqtt.publish(self._hass, self._topic[CONF_SPEED_COMMAND_TOPIC],
mqtt_payload, self._qos, self._retain)
self.schedule_update_ha_state()
self.hass.async_add_job(self.async_update_ha_state())
def oscillate(self, oscillating: bool) -> None:
"""Set oscillation."""
if self._topic[CONF_OSCILLATION_COMMAND_TOPIC] is not None:
self._oscillation = oscillating
@asyncio.coroutine
def async_oscillate(self, oscillating: bool) -> None:
"""Set oscillation.
This method is a coroutine.
"""
if self._topic[CONF_OSCILLATION_COMMAND_TOPIC] is None:
return
if oscillating is False:
payload = self._payload[OSCILLATE_OFF_PAYLOAD]
else:
payload = self._payload[OSCILLATE_ON_PAYLOAD]
if oscillating is False:
payload = self._payload[OSCILLATE_OFF_PAYLOAD]
mqtt.publish(self._hass,
self._topic[CONF_OSCILLATION_COMMAND_TOPIC],
payload, self._qos, self._retain)
self.schedule_update_ha_state()
mqtt.async_publish(
self.hass, self._topic[CONF_OSCILLATION_COMMAND_TOPIC],
payload, self._qos, self._retain)
if self._optimistic_oscillation:
self._oscillation = oscillating
self.hass.async_add_job(self.async_update_ha_state())

View File

@ -18,7 +18,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
DOMAIN = 'ffmpeg'
REQUIREMENTS = ["ha-ffmpeg==1.4"]
REQUIREMENTS = ["ha-ffmpeg==1.5"]
_LOGGER = logging.getLogger(__name__)

View File

@ -19,7 +19,7 @@ DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api']
URL_PANEL_COMPONENT = '/frontend/panels/{}.html'
URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html'
STATIC_PATH = os.path.join(os.path.dirname(__file__), 'www_static')
STATIC_PATH = os.path.join(os.path.dirname(__file__), 'www_static/')
MANIFEST_JSON = {
"background_color": "#FFFFFF",
"description": "Open-source home automation platform running on Python 3.",
@ -51,17 +51,22 @@ _LOGGER = logging.getLogger(__name__)
def register_built_in_panel(hass, component_name, sidebar_title=None,
sidebar_icon=None, url_path=None, config=None):
"""Register a built-in panel."""
path = 'panels/ha-panel-{}.html'.format(component_name)
nondev_path = 'panels/ha-panel-{}.html'.format(component_name)
if hass.http.development:
url = ('/static/home-assistant-polymer/panels/'
'{0}/ha-panel-{0}.html'.format(component_name))
path = os.path.join(
STATIC_PATH, 'home-assistant-polymer/panels/',
'{0}/ha-panel-{0}.html'.format(component_name))
else:
url = None # use default url generate mechanism
path = os.path.join(STATIC_PATH, nondev_path)
register_panel(hass, component_name, os.path.join(STATIC_PATH, path),
FINGERPRINTS[path], sidebar_title, sidebar_icon, url_path,
url, config)
# Fingerprint doesn't exist when adding new built-in panel
register_panel(hass, component_name, path,
FINGERPRINTS.get(nondev_path, 'dev'), sidebar_title,
sidebar_icon, url_path, url, config)
def register_panel(hass, component_name, path, md5=None, sidebar_title=None,
@ -230,10 +235,14 @@ class IndexView(HomeAssistantView):
if request.app[KEY_DEVELOPMENT]:
core_url = '/static/home-assistant-polymer/build/core.js'
compatibility_url = \
'/static/home-assistant-polymer/build/compatibility.js'
ui_url = '/static/home-assistant-polymer/src/home-assistant.html'
else:
core_url = '/static/core-{}.js'.format(
FINGERPRINTS['core.js'])
compatibility_url = '/static/compatibility-{}.js'.format(
FINGERPRINTS['compatibility.js'])
ui_url = '/static/frontend-{}.html'.format(
FINGERPRINTS['frontend.html'])
@ -263,7 +272,8 @@ class IndexView(HomeAssistantView):
# pylint: disable=no-member
# This is a jinja2 template, not a HA template so we call 'render'.
resp = template.render(
core_url=core_url, ui_url=ui_url, no_auth=no_auth,
core_url=core_url, ui_url=ui_url,
compatibility_url=compatibility_url, no_auth=no_auth,
icons_url=icons_url, icons=FINGERPRINTS['mdi.html'],
panel_url=panel_url, panels=hass.data[DATA_PANELS])

View File

@ -78,6 +78,16 @@
</div>
<home-assistant icons='{{ icons }}'></home-assistant>
{# <script src='/static/home-assistant-polymer/build/_demo_data_compiled.js'></script> #}
<script>
var compatibilityRequired = (
typeof Object.assign != 'function');
if (compatibilityRequired) {
var e = document.createElement('script');
e.onerror = initError;
e.src = '{{ compatibility_url }}';
document.head.appendChild(e);
}
</script>
<script src='{{ core_url }}'></script>
<link rel='import' href='{{ ui_url }}' onerror='initError()'>
{% if panel_url -%}

View File

@ -1,18 +1,20 @@
"""DO NOT MODIFY. Auto-generated by script/fingerprint_frontend."""
FINGERPRINTS = {
"compatibility.js": "83d9c77748dafa9db49ae77d7f3d8fb0",
"core.js": "1f7f88d8f5dada08bce1d935cfa5f33e",
"frontend.html": "a179cbf60629f02e1d6f4b0034a783bf",
"frontend.html": "be258a53166b82f4ebd5232037e1cbd5",
"mdi.html": "c1dde43ccf5667f687c418fc8daf9668",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-dev-event.html": "5c82300b3cf543a92cf4297506e450e7",
"panels/ha-panel-dev-info.html": "5f8119c8aa18cba51106e1bb8ca9cba2",
"panels/ha-panel-dev-service.html": "306243e11766266c967d1163ca678fe6",
"panels/ha-panel-dev-state.html": "7d069ba8fd5379fa8f59858b8c0a7473",
"panels/ha-panel-dev-template.html": "219d66c469982b6d6ae015d21156ac82",
"panels/ha-panel-history.html": "c5cf0aa3eba31f40231b0479b08913eb",
"panels/ha-panel-config.html": "412b3e24515ffa1ee8074ce974cf4057",
"panels/ha-panel-dev-event.html": "91347dedf3b4fa9b49ccf4c0a28a03c4",
"panels/ha-panel-dev-info.html": "61610e015a411cfc84edd2c4d489e71d",
"panels/ha-panel-dev-service.html": "a9247f255174b084fad2c04bdb9ec7a9",
"panels/ha-panel-dev-state.html": "90f3bede9602241552ef7bb7958198c6",
"panels/ha-panel-dev-template.html": "c249a4fc18a3a6994de3d6330cfe6cbb",
"panels/ha-panel-history.html": "fdaa4d2402d49d4c8bd64a1708ab7a50",
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
"panels/ha-panel-logbook.html": "2af1feb30b37427f481d5437a438a3f2",
"panels/ha-panel-map.html": "ecd2ee7ff6e2837e67950d3ab1a6ec65",
"panels/ha-panel-map.html": "e10704a3469e44d1714eac9ed8e4b6a0",
"websocket_test.html": "575de64b431fe11c3785bf96d7813450"
}

View File

@ -0,0 +1 @@
!(function(){"use strict";function e(e,r){var t=arguments;if(void 0===e||null===e)throw new TypeError("Cannot convert first argument to object");for(var n=Object(e),o=1;o<arguments.length;o++){var i=t[o];if(void 0!==i&&null!==i)for(var l=Object.keys(Object(i)),a=0,c=l.length;a<c;a++){var b=l[a],f=Object.getOwnPropertyDescriptor(i,b);void 0!==f&&f.enumerable&&(n[b]=i[b])}}return n}function r(){Object.assign||Object.defineProperty(Object,"assign",{enumerable:!1,configurable:!0,writable:!0,value:e})}var t={assign:e,polyfill:r};t.polyfill()})();

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit f3808ff4d44733f5810e21131e0daa1425bf5f22
Subproject commit 5d223c8da4b4380bf79d8f0285ce7824063e89ef

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M6,19A5,5 0 0,1 1,14A5,5 0 0,1 6,9C7,6.65 9.3,5 12,5C15.43,5 18.24,7.66 18.5,11.03L19,11A4,4 0 0,1 23,15A4,4 0 0,1 19,19H6M19,13H17V12A5,5 0 0,0 12,7C9.5,7 7.45,8.82 7.06,11.19C6.73,11.07 6.37,11 6,11A3,3 0 0,0 3,14A3,3 0 0,0 6,17H19A2,2 0 0,0 21,15A2,2 0 0,0 19,13Z" /></svg>

After

Width:  |  Height:  |  Size: 561 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M3,15H13A1,1 0 0,1 14,16A1,1 0 0,1 13,17H3A1,1 0 0,1 2,16A1,1 0 0,1 3,15M16,15H21A1,1 0 0,1 22,16A1,1 0 0,1 21,17H16A1,1 0 0,1 15,16A1,1 0 0,1 16,15M1,12A5,5 0 0,1 6,7C7,4.65 9.3,3 12,3C15.43,3 18.24,5.66 18.5,9.03L19,9C21.19,9 22.97,10.76 23,13H21A2,2 0 0,0 19,11H17V10A5,5 0 0,0 12,5C9.5,5 7.45,6.82 7.06,9.19C6.73,9.07 6.37,9 6,9A3,3 0 0,0 3,12C3,12.35 3.06,12.69 3.17,13H1.1L1,12M3,19H5A1,1 0 0,1 6,20A1,1 0 0,1 5,21H3A1,1 0 0,1 2,20A1,1 0 0,1 3,19M8,19H21A1,1 0 0,1 22,20A1,1 0 0,1 21,21H8A1,1 0 0,1 7,20A1,1 0 0,1 8,19Z" /></svg>

After

Width:  |  Height:  |  Size: 820 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M6,14A1,1 0 0,1 7,15A1,1 0 0,1 6,16A5,5 0 0,1 1,11A5,5 0 0,1 6,6C7,3.65 9.3,2 12,2C15.43,2 18.24,4.66 18.5,8.03L19,8A4,4 0 0,1 23,12A4,4 0 0,1 19,16H18A1,1 0 0,1 17,15A1,1 0 0,1 18,14H19A2,2 0 0,0 21,12A2,2 0 0,0 19,10H17V9A5,5 0 0,0 12,4C9.5,4 7.45,5.82 7.06,8.19C6.73,8.07 6.37,8 6,8A3,3 0 0,0 3,11A3,3 0 0,0 6,14M10,18A2,2 0 0,1 12,20A2,2 0 0,1 10,22A2,2 0 0,1 8,20A2,2 0 0,1 10,18M14.5,16A1.5,1.5 0 0,1 16,17.5A1.5,1.5 0 0,1 14.5,19A1.5,1.5 0 0,1 13,17.5A1.5,1.5 0 0,1 14.5,16M10.5,12A1.5,1.5 0 0,1 12,13.5A1.5,1.5 0 0,1 10.5,15A1.5,1.5 0 0,1 9,13.5A1.5,1.5 0 0,1 10.5,12Z" /></svg>

After

Width:  |  Height:  |  Size: 871 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M17.75,4.09L15.22,6.03L16.13,9.09L13.5,7.28L10.87,9.09L11.78,6.03L9.25,4.09L12.44,4L13.5,1L14.56,4L17.75,4.09M21.25,11L19.61,12.25L20.2,14.23L18.5,13.06L16.8,14.23L17.39,12.25L15.75,11L17.81,10.95L18.5,9L19.19,10.95L21.25,11M18.97,15.95C19.8,15.87 20.69,17.05 20.16,17.8C19.84,18.25 19.5,18.67 19.08,19.07C15.17,23 8.84,23 4.94,19.07C1.03,15.17 1.03,8.83 4.94,4.93C5.34,4.53 5.76,4.17 6.21,3.85C6.96,3.32 8.14,4.21 8.06,5.04C7.79,7.9 8.75,10.87 10.95,13.06C13.14,15.26 16.1,16.22 18.97,15.95M17.33,17.97C14.5,17.81 11.7,16.64 9.53,14.5C7.36,12.31 6.2,9.5 6.04,6.68C3.23,9.82 3.34,14.64 6.35,17.66C9.37,20.67 14.19,20.78 17.33,17.97Z" /></svg>

After

Width:  |  Height:  |  Size: 927 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12.74,5.47C15.1,6.5 16.35,9.03 15.92,11.46C17.19,12.56 18,14.19 18,16V16.17C18.31,16.06 18.65,16 19,16A3,3 0 0,1 22,19A3,3 0 0,1 19,22H6A4,4 0 0,1 2,18A4,4 0 0,1 6,14H6.27C5,12.45 4.6,10.24 5.5,8.26C6.72,5.5 9.97,4.24 12.74,5.47M11.93,7.3C10.16,6.5 8.09,7.31 7.31,9.07C6.85,10.09 6.93,11.22 7.41,12.13C8.5,10.83 10.16,10 12,10C12.7,10 13.38,10.12 14,10.34C13.94,9.06 13.18,7.86 11.93,7.3M13.55,3.64C13,3.4 12.45,3.23 11.88,3.12L14.37,1.82L15.27,4.71C14.76,4.29 14.19,3.93 13.55,3.64M6.09,4.44C5.6,4.79 5.17,5.19 4.8,5.63L4.91,2.82L7.87,3.5C7.25,3.71 6.65,4.03 6.09,4.44M18,9.71C17.91,9.12 17.78,8.55 17.59,8L19.97,9.5L17.92,11.73C18.03,11.08 18.05,10.4 18,9.71M3.04,11.3C3.11,11.9 3.24,12.47 3.43,13L1.06,11.5L3.1,9.28C3,9.93 2.97,10.61 3.04,11.3M19,18H16V16A4,4 0 0,0 12,12A4,4 0 0,0 8,16H6A2,2 0 0,0 4,18A2,2 0 0,0 6,20H19A1,1 0 0,0 20,19A1,1 0 0,0 19,18Z" /></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M9,12C9.53,12.14 9.85,12.69 9.71,13.22L8.41,18.05C8.27,18.59 7.72,18.9 7.19,18.76C6.65,18.62 6.34,18.07 6.5,17.54L7.78,12.71C7.92,12.17 8.47,11.86 9,12M13,12C13.53,12.14 13.85,12.69 13.71,13.22L11.64,20.95C11.5,21.5 10.95,21.8 10.41,21.66C9.88,21.5 9.56,20.97 9.7,20.43L11.78,12.71C11.92,12.17 12.47,11.86 13,12M17,12C17.53,12.14 17.85,12.69 17.71,13.22L16.41,18.05C16.27,18.59 15.72,18.9 15.19,18.76C14.65,18.62 14.34,18.07 14.5,17.54L15.78,12.71C15.92,12.17 16.47,11.86 17,12M17,10V9A5,5 0 0,0 12,4C9.5,4 7.45,5.82 7.06,8.19C6.73,8.07 6.37,8 6,8A3,3 0 0,0 3,11C3,12.11 3.6,13.08 4.5,13.6V13.59C5,13.87 5.14,14.5 4.87,14.96C4.59,15.43 4,15.6 3.5,15.32V15.33C2,14.47 1,12.85 1,11A5,5 0 0,1 6,6C7,3.65 9.3,2 12,2C15.43,2 18.24,4.66 18.5,8.03L19,8A4,4 0 0,1 23,12C23,13.5 22.2,14.77 21,15.46V15.46C20.5,15.73 19.91,15.57 19.63,15.09C19.36,14.61 19.5,14 20,13.72V13.73C20.6,13.39 21,12.74 21,12A2,2 0 0,0 19,10H17Z" /></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M6,14A1,1 0 0,1 7,15A1,1 0 0,1 6,16A5,5 0 0,1 1,11A5,5 0 0,1 6,6C7,3.65 9.3,2 12,2C15.43,2 18.24,4.66 18.5,8.03L19,8A4,4 0 0,1 23,12A4,4 0 0,1 19,16H18A1,1 0 0,1 17,15A1,1 0 0,1 18,14H19A2,2 0 0,0 21,12A2,2 0 0,0 19,10H17V9A5,5 0 0,0 12,4C9.5,4 7.45,5.82 7.06,8.19C6.73,8.07 6.37,8 6,8A3,3 0 0,0 3,11A3,3 0 0,0 6,14M14.83,15.67C16.39,17.23 16.39,19.5 14.83,21.08C14.05,21.86 13,22 12,22C11,22 9.95,21.86 9.17,21.08C7.61,19.5 7.61,17.23 9.17,15.67L12,11L14.83,15.67M13.41,16.69L12,14.25L10.59,16.69C9.8,17.5 9.8,18.7 10.59,19.5C11,19.93 11.5,20 12,20C12.5,20 13,19.93 13.41,19.5C14.2,18.7 14.2,17.5 13.41,16.69Z" /></svg>

After

Width:  |  Height:  |  Size: 905 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M6,14A1,1 0 0,1 7,15A1,1 0 0,1 6,16A5,5 0 0,1 1,11A5,5 0 0,1 6,6C7,3.65 9.3,2 12,2C15.43,2 18.24,4.66 18.5,8.03L19,8A4,4 0 0,1 23,12A4,4 0 0,1 19,16H18A1,1 0 0,1 17,15A1,1 0 0,1 18,14H19A2,2 0 0,0 21,12A2,2 0 0,0 19,10H17V9A5,5 0 0,0 12,4C9.5,4 7.45,5.82 7.06,8.19C6.73,8.07 6.37,8 6,8A3,3 0 0,0 3,11A3,3 0 0,0 6,14M7.88,18.07L10.07,17.5L8.46,15.88C8.07,15.5 8.07,14.86 8.46,14.46C8.85,14.07 9.5,14.07 9.88,14.46L11.5,16.07L12.07,13.88C12.21,13.34 12.76,13.03 13.29,13.17C13.83,13.31 14.14,13.86 14,14.4L13.41,16.59L15.6,16C16.14,15.86 16.69,16.17 16.83,16.71C16.97,17.24 16.66,17.79 16.12,17.93L13.93,18.5L15.54,20.12C15.93,20.5 15.93,21.15 15.54,21.54C15.15,21.93 14.5,21.93 14.12,21.54L12.5,19.93L11.93,22.12C11.79,22.66 11.24,22.97 10.71,22.83C10.17,22.69 9.86,22.14 10,21.6L10.59,19.41L8.4,20C7.86,20.14 7.31,19.83 7.17,19.29C7.03,18.76 7.34,18.21 7.88,18.07Z" /></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,2L14.39,5.42C13.65,5.15 12.84,5 12,5C11.16,5 10.35,5.15 9.61,5.42L12,2M3.34,7L7.5,6.65C6.9,7.16 6.36,7.78 5.94,8.5C5.5,9.24 5.25,10 5.11,10.79L3.34,7M3.36,17L5.12,13.23C5.26,14 5.53,14.78 5.95,15.5C6.37,16.24 6.91,16.86 7.5,17.37L3.36,17M20.65,7L18.88,10.79C18.74,10 18.47,9.23 18.05,8.5C17.63,7.78 17.1,7.15 16.5,6.64L20.65,7M20.64,17L16.5,17.36C17.09,16.85 17.62,16.22 18.04,15.5C18.46,14.77 18.73,14 18.87,13.21L20.64,17M12,22L9.59,18.56C10.33,18.83 11.14,19 12,19C12.82,19 13.63,18.83 14.37,18.56L12,22Z" /></svg>

After

Width:  |  Height:  |  Size: 940 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M4,10A1,1 0 0,1 3,9A1,1 0 0,1 4,8H12A2,2 0 0,0 14,6A2,2 0 0,0 12,4C11.45,4 10.95,4.22 10.59,4.59C10.2,5 9.56,5 9.17,4.59C8.78,4.2 8.78,3.56 9.17,3.17C9.9,2.45 10.9,2 12,2A4,4 0 0,1 16,6A4,4 0 0,1 12,10H4M19,12A1,1 0 0,0 20,11A1,1 0 0,0 19,10C18.72,10 18.47,10.11 18.29,10.29C17.9,10.68 17.27,10.68 16.88,10.29C16.5,9.9 16.5,9.27 16.88,8.88C17.42,8.34 18.17,8 19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14H5A1,1 0 0,1 4,13A1,1 0 0,1 5,12H19M18,18H4A1,1 0 0,1 3,17A1,1 0 0,1 4,16H18A3,3 0 0,1 21,19A3,3 0 0,1 18,22C17.17,22 16.42,21.66 15.88,21.12C15.5,20.73 15.5,20.1 15.88,19.71C16.27,19.32 16.9,19.32 17.29,19.71C17.47,19.89 17.72,20 18,20A1,1 0 0,0 19,19A1,1 0 0,0 18,18Z" /></svg>

After

Width:  |  Height:  |  Size: 959 B

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