Merge pull request #5833 from home-assistant/release-038

Release 0.38
pull/5889/head 0.38
Robbie Trencheny 2017-02-11 14:38:12 -08:00 committed by GitHub
commit e0f1c8ac67
281 changed files with 10347 additions and 2480 deletions

View File

@ -5,6 +5,7 @@ omit =
homeassistant/__main__.py
homeassistant/scripts/*.py
homeassistant/helpers/typing.py
homeassistant/helpers/signal.py
# omit pieces of code that rely on external devices being present
homeassistant/components/apcupsd.py
@ -116,9 +117,6 @@ omit =
homeassistant/components/knx.py
homeassistant/components/*/knx.py
homeassistant/components/ffmpeg.py
homeassistant/components/*/ffmpeg.py
homeassistant/components/zoneminder.py
homeassistant/components/*/zoneminder.py
@ -132,6 +130,7 @@ omit =
homeassistant/components/alarm_control_panel/concord232.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
homeassistant/components/apiai.py
homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/concord232.py
homeassistant/components/binary_sensor/flic.py
@ -141,6 +140,7 @@ omit =
homeassistant/components/browser.py
homeassistant/components/camera/amcrest.py
homeassistant/components/camera/bloomsky.py
homeassistant/components/camera/ffmpeg.py
homeassistant/components/camera/foscam.py
homeassistant/components/camera/mjpeg.py
homeassistant/components/camera/rpi_camera.py
@ -210,7 +210,9 @@ omit =
homeassistant/components/light/piglow.py
homeassistant/components/light/zengge.py
homeassistant/components/lirc.py
homeassistant/components/lock/nuki.py
homeassistant/components/media_player/anthemav.py
homeassistant/components/media_player/apple_tv.py
homeassistant/components/media_player/aquostv.py
homeassistant/components/media_player/braviatv.py
homeassistant/components/media_player/cast.py
@ -226,6 +228,7 @@ omit =
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
homeassistant/components/media_player/liveboxplaytv.py
homeassistant/components/media_player/mpchc.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/nad.py
@ -256,6 +259,7 @@ omit =
homeassistant/components/notify/kodi.py
homeassistant/components/notify/lannouncer.py
homeassistant/components/notify/llamalab_automate.py
homeassistant/components/notify/mailgun.py
homeassistant/components/notify/matrix.py
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nfandroidtv.py
@ -325,11 +329,13 @@ omit =
homeassistant/components/sensor/nzbget.py
homeassistant/components/sensor/ohmconnect.py
homeassistant/components/sensor/onewire.py
homeassistant/components/sensor/openevse.py
homeassistant/components/sensor/openexchangerates.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/pi_hole.py
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/pvoutput.py
homeassistant/components/sensor/qnap.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/sensehat.py
@ -365,6 +371,7 @@ omit =
homeassistant/components/switch/digitalloggers.py
homeassistant/components/switch/dlink.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/fritzdect.py
homeassistant/components/switch/hdmi_cec.py
homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/hook.py

View File

@ -1,14 +1,22 @@
FROM python:3.5
MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
# Uncomment any of the following lines to disable the installation.
#ENV INSTALL_TELLSTICK no
#ENV INSTALL_OPENALPR no
#ENV INSTALL_FFMPEG no
#ENV INSTALL_OPENZWAVE no
#ENV INSTALL_LIBCEC no
#ENV INSTALL_PHANTOMJS no
VOLUME /config
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Copy build scripts
COPY script/setup_docker_prereqs script/build_python_openzwave script/build_libcec script/install_phantomjs script/
RUN script/setup_docker_prereqs
COPY virtualization/Docker/ virtualization/Docker/
RUN virtualization/Docker/setup_docker_prereqs
# Install hass component dependencies
COPY requirements_all.txt requirements_all.txt
@ -18,4 +26,4 @@ RUN pip3 install --no-cache-dir -r requirements_all.txt && \
# Copy source
COPY . .
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]

View File

@ -26,6 +26,7 @@ from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
event_decorators, service, config_per_platform, extract_domain_configs)
from homeassistant.helpers.signal import async_register_signal_handling
_LOGGER = logging.getLogger(__name__)
@ -435,6 +436,7 @@ def async_from_config_dict(config: Dict[str, Any],
yield from hass.async_stop_track_tasks()
async_register_signal_handling(hass)
return hass

View File

@ -12,14 +12,19 @@ import itertools as it
import logging
import homeassistant.core as ha
import homeassistant.config as conf_util
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.service import extract_entity_ids
from homeassistant.loader import get_component
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE)
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
RESTART_EXIT_CODE)
_LOGGER = logging.getLogger(__name__)
SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config'
SERVICE_CHECK_CONFIG = 'check_config'
def is_on(hass, entity_id=None):
@ -75,6 +80,21 @@ def toggle(hass, entity_id=None, **service_data):
hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
def stop(hass):
"""Stop Home Assistant."""
hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP)
def restart(hass):
"""Stop Home Assistant."""
hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART)
def check_config(hass):
"""Check the config files."""
hass.services.call(ha.DOMAIN, SERVICE_CHECK_CONFIG)
def reload_core_config(hass):
"""Reload the core config."""
hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
@ -84,7 +104,7 @@ def reload_core_config(hass):
def async_setup(hass, config):
"""Setup general services related to Home Assistant."""
@asyncio.coroutine
def handle_turn_service(service):
def async_handle_turn_service(service):
"""Method to handle calls to homeassistant.turn_on/off."""
entity_ids = extract_entity_ids(hass, service)
@ -122,18 +142,37 @@ def async_setup(hass, config):
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service)
ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_TOGGLE, handle_turn_service)
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service)
@asyncio.coroutine
def handle_reload_config(call):
"""Service handler for reloading core config."""
from homeassistant.exceptions import HomeAssistantError
from homeassistant import config as conf_util
def async_handle_core_service(call):
"""Service handler for handling core services."""
if call.service == SERVICE_HOMEASSISTANT_STOP:
hass.async_add_job(hass.async_stop())
return
try:
yield from conf_util.async_check_ha_config_file(hass)
except HomeAssistantError:
return
if call.service == SERVICE_HOMEASSISTANT_RESTART:
hass.async_add_job(hass.async_stop(RESTART_EXIT_CODE))
hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service)
@asyncio.coroutine
def async_handle_reload_config(call):
"""Service handler for reloading core config."""
try:
conf = yield from conf_util.async_hass_config_yaml(hass)
except HomeAssistantError as err:
@ -144,6 +183,6 @@ def async_setup(hass, config):
hass, conf.get(ha.DOMAIN) or {})
hass.services.async_register(
ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, handle_reload_config)
ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config)
return True

View File

@ -11,7 +11,7 @@ from homeassistant.const import (STATE_UNKNOWN,
STATE_ALARM_DISARMED,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY)
from homeassistant.components.wink import WinkDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
_LOGGER = logging.getLogger(__name__)
@ -24,7 +24,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
import pywink
for camera in pywink.get_cameras():
add_devices([WinkCameraDevice(camera, hass)])
# get_cameras returns multiple device types.
# Only add those that aren't sensors.
try:
camera.capability()
except AttributeError:
_id = camera.object_id() + camera.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkCameraDevice(camera, hass)])
class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
@ -32,7 +39,7 @@ class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
def __init__(self, wink, hass):
"""Initialize the Wink alarm."""
WinkDevice.__init__(self, wink, hass)
super().__init__(wink, hass)
@property
def state(self):

View File

@ -0,0 +1,275 @@
"""
Support for repeating alerts when conditions are met.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alert/
"""
import asyncio
from datetime import datetime, timedelta
import logging
import os
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.helpers.entity import ToggleEntity
from homeassistant.helpers import service, event
from homeassistant.util.async import run_callback_threadsafe
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'alert'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
CONF_CAN_ACK = 'can_acknowledge'
CONF_NOTIFIERS = 'notifiers'
CONF_REPEAT = 'repeat'
CONF_SKIP_FIRST = 'skip_first'
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_NOTIFIERS): cv.ensure_list})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
cv.slug: ALERT_SCHEMA,
}),
}, extra=vol.ALLOW_EXTRA)
ALERT_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
})
def is_on(hass, entity_id):
"""Return if the alert is firing and not acknowledged."""
return hass.states.is_state(entity_id, STATE_ON)
def turn_on(hass, entity_id):
"""Reset the alert."""
run_callback_threadsafe(hass.loop, async_turn_on, hass, entity_id)
@callback
def async_turn_on(hass, entity_id):
"""Async reset the alert."""
data = {ATTR_ENTITY_ID: entity_id}
hass.async_add_job(
hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
def turn_off(hass, entity_id):
"""Acknowledge alert."""
run_callback_threadsafe(hass.loop, async_turn_off, hass, entity_id)
@callback
def async_turn_off(hass, entity_id):
"""Async acknowledge the alert."""
data = {ATTR_ENTITY_ID: entity_id}
hass.async_add_job(
hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))
def toggle(hass, entity_id):
"""Toggle acknowledgement of alert."""
run_callback_threadsafe(hass.loop, async_toggle, hass, entity_id)
@callback
def async_toggle(hass, entity_id):
"""Async toggle acknowledgement of alert."""
data = {ATTR_ENTITY_ID: entity_id}
hass.async_add_job(
hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data))
@asyncio.coroutine
def async_setup(hass, config):
"""Setup alert component."""
alerts = config.get(DOMAIN)
all_alerts = {}
@asyncio.coroutine
def async_handle_alert_service(service_call):
"""Handle calls to alert services."""
alert_ids = service.extract_entity_ids(hass, service_call)
for alert_id in alert_ids:
alert = all_alerts[alert_id]
if service_call.service == SERVICE_TURN_ON:
yield from alert.async_turn_on()
elif service_call.service == SERVICE_TOGGLE:
yield from alert.async_toggle()
else:
yield from alert.async_turn_off()
# setup alerts
for entity_id, alert in alerts.items():
entity = Alert(hass, entity_id,
alert[CONF_NAME], alert[CONF_ENTITY_ID],
alert[CONF_STATE], alert[CONF_REPEAT],
alert[CONF_SKIP_FIRST], alert[CONF_NOTIFIERS],
alert[CONF_CAN_ACK])
all_alerts[entity.entity_id] = entity
# 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
hass.services.async_register(
DOMAIN, SERVICE_TURN_OFF, async_handle_alert_service,
descriptions.get(SERVICE_TURN_OFF), schema=ALERT_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_TURN_ON, async_handle_alert_service,
descriptions.get(SERVICE_TURN_ON), schema=ALERT_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_TOGGLE, async_handle_alert_service,
descriptions.get(SERVICE_TOGGLE), schema=ALERT_SERVICE_SCHEMA)
tasks = [alert.async_update_ha_state() for alert in all_alerts.values()]
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
return True
class Alert(ToggleEntity):
"""Representation of an alert."""
def __init__(self, hass, entity_id, name, watched_entity_id, state,
repeat, skip_first, notifiers, can_ack):
"""Initialize the alert."""
self.hass = hass
self._name = name
self._alert_state = state
self._skip_first = skip_first
self._notifiers = notifiers
self._can_ack = can_ack
self._delay = [timedelta(minutes=val) for val in repeat]
self._next_delay = 0
self._firing = False
self._ack = False
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)
@property
def name(self):
"""Return the name of the alert."""
return self._name
@property
def should_poll(self):
"""HASS need not poll these entities."""
return False
@property
def state(self):
"""Return the alert status."""
if self._firing:
if self._ack:
return STATE_OFF
return STATE_ON
return STATE_IDLE
@property
def hidden(self):
"""Hide the alert when it is not firing."""
return not self._can_ack or not self._firing
@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)
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:
yield from self.end_alerting()
@asyncio.coroutine
def begin_alerting(self):
"""Begin the alert procedures."""
_LOGGER.debug('Beginning Alert: %s', self._name)
self._ack = False
self._firing = True
self._next_delay = 0
if not self._skip_first:
yield from self._notify()
else:
yield from self._schedule_notify()
self.hass.async_add_job(self.async_update_ha_state)
@asyncio.coroutine
def end_alerting(self):
"""End the alert procedures."""
_LOGGER.debug('Ending Alert: %s', self._name)
self._cancel()
self._ack = False
self._firing = False
self.hass.async_add_job(self.async_update_ha_state)
@asyncio.coroutine
def _schedule_notify(self):
"""Schedule a notification."""
delay = self._delay[self._next_delay]
next_msg = datetime.now() + delay
self._cancel = \
event.async_track_point_in_time(self.hass, self._notify, next_msg)
self._next_delay = min(self._next_delay + 1, len(self._delay) - 1)
@asyncio.coroutine
def _notify(self, *args):
"""Send the alert notification."""
if not self._firing:
return
if not self._ack:
_LOGGER.info('Alerting: %s', self._name)
for target in self._notifiers:
yield from self.hass.services.async_call(
'notify', target, {'message': self._name})
yield from self._schedule_notify()
@asyncio.coroutine
def async_turn_on(self):
"""Async Unacknowledge alert."""
_LOGGER.debug('Reset Alert: %s', self._name)
self._ack = False
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_turn_off(self):
"""Async Acknowledge alert."""
_LOGGER.debug('Acknowledged Alert: %s', self._name)
self._ack = True
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_toggle(self):
"""Async toggle alert."""
if self._ack:
return self.async_turn_on()
return self.async_turn_off()

View File

@ -0,0 +1,172 @@
"""
Support for API.AI webhook.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/apiai/
"""
import asyncio
import copy
import logging
import voluptuous as vol
from homeassistant.const import PROJECT_NAME, HTTP_BAD_REQUEST
from homeassistant.helpers import template, script, config_validation as cv
from homeassistant.components.http import HomeAssistantView
_LOGGER = logging.getLogger(__name__)
INTENTS_API_ENDPOINT = '/api/apiai'
CONF_INTENTS = 'intents'
CONF_SPEECH = 'speech'
CONF_ACTION = 'action'
CONF_ASYNC_ACTION = 'async_action'
DEFAULT_CONF_ASYNC_ACTION = False
DOMAIN = 'apiai'
DEPENDENCIES = ['http']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
CONF_INTENTS: {
cv.string: {
vol.Optional(CONF_SPEECH): cv.template,
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_ASYNC_ACTION,
default=DEFAULT_CONF_ASYNC_ACTION): cv.boolean
}
}
}
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Activate API.AI component."""
intents = config[DOMAIN].get(CONF_INTENTS, {})
hass.http.register_view(ApiaiIntentsView(hass, intents))
return True
class ApiaiIntentsView(HomeAssistantView):
"""Handle API.AI requests."""
url = INTENTS_API_ENDPOINT
name = 'api:apiai'
def __init__(self, hass, intents):
"""Initialize API.AI view."""
super().__init__()
self.hass = hass
intents = copy.deepcopy(intents)
template.attach(hass, intents)
for name, intent in intents.items():
if CONF_ACTION in intent:
intent[CONF_ACTION] = script.Script(
hass, intent[CONF_ACTION], "Apiai intent {}".format(name))
self.intents = intents
@asyncio.coroutine
def post(self, request):
"""Handle API.AI."""
data = yield from request.json()
_LOGGER.debug('Received Apiai 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)
action_incomplete = req['actionIncomplete']
if action_incomplete:
return None
# use intent to no mix HASS actions with this parameter
intent = req.get('action')
parameters = req.get('parameters')
# contexts = req.get('contexts')
response = ApiaiResponse(parameters)
# Default Welcome Intent
# Maybe is better to handle this in api.ai directly?
#
# if intent == 'input.welcome':
# response.add_speech(
# "Hello, and welcome to the future. How may I help?")
# return self.json(response)
if intent == "":
_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)
config = self.intents.get(intent)
if config is None:
_LOGGER.warning('Received unknown intent %s', intent)
response.add_speech(
"Intent '%s' is not yet configured within Home Assistant." %
intent)
return self.json(response)
speech = config.get(CONF_SPEECH)
action = config.get(CONF_ACTION)
async_action = config.get(CONF_ASYNC_ACTION)
if action is not None:
# API.AI expects a response in less than 5s
if async_action:
# Do not wait for the action to be executed.
# Needed if the action will take longer than 5s to execute
self.hass.async_add_job(action.async_run(response.parameters))
else:
# Wait for the action to be executed so we can use results to
# render the answer
yield from action.async_run(response.parameters)
# pylint: disable=unsubscriptable-object
if speech is not None:
response.add_speech(speech)
return self.json(response)
class ApiaiResponse(object):
"""Help generating the response for API.AI."""
def __init__(self, parameters):
"""Initialize the response."""
self.speech = None
self.parameters = {}
# Parameter names replace '.' and '-' for '_'
for key, value in parameters.items():
underscored_key = key.replace('.', '_').replace('-', '_')
self.parameters[underscored_key] = value
def add_speech(self, text):
"""Add speech to the response."""
assert self.speech is None
if isinstance(text, template.Template):
text = text.async_render(self.parameters)
self.speech = text
def as_dict(self):
"""Return response in an API.AI valid dict."""
return {
'speech': self.speech,
'displayText': self.speech,
'source': PROJECT_NAME,
}

View File

@ -1,279 +0,0 @@
"""
Provides a binary sensor which is a collection of ffmpeg tools.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ffmpeg/
"""
import asyncio
import logging
import os
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DOMAIN)
from homeassistant.components.ffmpeg import (
DATA_FFMPEG, CONF_INPUT, CONF_OUTPUT, CONF_EXTRA_ARGUMENTS)
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, CONF_NAME,
ATTR_ENTITY_ID)
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
SERVICE_START = 'ffmpeg_start'
SERVICE_STOP = 'ffmpeg_stop'
SERVICE_RESTART = 'ffmpeg_restart'
DATA_FFMPEG_DEVICE = 'ffmpeg_binary_sensor'
FFMPEG_SENSOR_NOISE = 'noise'
FFMPEG_SENSOR_MOTION = 'motion'
MAP_FFMPEG_BIN = [
FFMPEG_SENSOR_NOISE,
FFMPEG_SENSOR_MOTION
]
CONF_INITIAL_STATE = 'initial_state'
CONF_TOOL = 'tool'
CONF_PEAK = 'peak'
CONF_DURATION = 'duration'
CONF_RESET = 'reset'
CONF_CHANGES = 'changes'
CONF_REPEAT = 'repeat'
CONF_REPEAT_TIME = 'repeat_time'
DEFAULT_NAME = 'FFmpeg'
DEFAULT_INIT_STATE = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOOL): vol.In(MAP_FFMPEG_BIN),
vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_OUTPUT): cv.string,
vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int),
vol.Optional(CONF_DURATION, default=1):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_RESET, default=10):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_CHANGES, default=10):
vol.All(vol.Coerce(float), vol.Range(min=0, max=99)),
vol.Optional(CONF_REPEAT, default=0):
vol.All(vol.Coerce(int), vol.Range(min=0)),
vol.Optional(CONF_REPEAT_TIME, default=0):
vol.All(vol.Coerce(int), vol.Range(min=0)),
})
SERVICE_FFMPEG_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
def restart(hass, entity_id=None):
"""Restart a ffmpeg process on entity."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_RESTART, data)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Create the binary sensor."""
from haffmpeg import SensorNoise, SensorMotion
# check source
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)):
return
# generate sensor object
if config.get(CONF_TOOL) == FFMPEG_SENSOR_NOISE:
entity = FFmpegNoise(hass, SensorNoise, config)
else:
entity = FFmpegMotion(hass, SensorMotion, config)
@asyncio.coroutine
def async_shutdown(event):
"""Stop ffmpeg."""
yield from entity.async_shutdown_ffmpeg()
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, async_shutdown)
# start on startup
if config.get(CONF_INITIAL_STATE):
@asyncio.coroutine
def async_start(event):
"""Start ffmpeg."""
yield from entity.async_start_ffmpeg()
yield from entity.async_update_ha_state()
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_start)
# add to system
yield from async_add_devices([entity])
# exists service?
if hass.services.has_service(DOMAIN, SERVICE_RESTART):
hass.data[DATA_FFMPEG_DEVICE].append(entity)
return
hass.data[DATA_FFMPEG_DEVICE] = [entity]
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file,
os.path.join(os.path.dirname(__file__), 'services.yaml'))
# register service
@asyncio.coroutine
def async_service_handle(service):
"""Handle service binary_sensor.ffmpeg_restart."""
entity_ids = service.data.get('entity_id')
if entity_ids:
_devices = [device for device in hass.data[DATA_FFMPEG_DEVICE]
if device.entity_id in entity_ids]
else:
_devices = hass.data[DATA_FFMPEG_DEVICE]
tasks = []
for device in _devices:
if service.service == SERVICE_START:
tasks.append(device.async_start_ffmpeg())
elif service.service == SERVICE_STOP:
tasks.append(device.async_shutdown_ffmpeg())
else:
tasks.append(device.async_restart_ffmpeg())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_START, async_service_handle,
descriptions.get(SERVICE_START), schema=SERVICE_FFMPEG_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_STOP, async_service_handle,
descriptions.get(SERVICE_STOP), schema=SERVICE_FFMPEG_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_RESTART, async_service_handle,
descriptions.get(SERVICE_RESTART), schema=SERVICE_FFMPEG_SCHEMA)
class FFmpegBinarySensor(BinarySensorDevice):
"""A binary sensor which use ffmpeg for noise detection."""
def __init__(self, hass, ffobj, config):
"""Constructor for binary sensor noise detection."""
self._manager = hass.data[DATA_FFMPEG]
self._state = False
self._config = config
self._name = config.get(CONF_NAME)
self._ffmpeg = ffobj(
self._manager.binary, hass.loop, self._async_callback)
def _async_callback(self, state):
"""HA-FFmpeg callback for noise detection."""
self._state = state
self.hass.async_add_job(self.async_update_ha_state())
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
"""
raise NotImplementedError()
def async_shutdown_ffmpeg(self):
"""For STOP event to shutdown ffmpeg.
This method must be run in the event loop and returns a coroutine.
"""
return self._ffmpeg.close()
@asyncio.coroutine
def async_restart_ffmpeg(self):
"""Restart processing."""
yield from self.async_shutdown_ffmpeg()
yield from self.async_start_ffmpeg()
@property
def is_on(self):
"""True if the binary sensor is on."""
return self._state
@property
def should_poll(self):
"""Return True if entity has to be polled for state."""
return False
@property
def name(self):
"""Return the name of the entity."""
return self._name
@property
def available(self):
"""Return True if entity is available."""
return self._ffmpeg.is_running
class FFmpegNoise(FFmpegBinarySensor):
"""A binary sensor which use ffmpeg for noise detection."""
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
"""
# init config
self._ffmpeg.set_options(
time_duration=self._config.get(CONF_DURATION),
time_reset=self._config.get(CONF_RESET),
peak=self._config.get(CONF_PEAK),
)
# run
return self._ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
output_dest=self._config.get(CONF_OUTPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return "sound"
class FFmpegMotion(FFmpegBinarySensor):
"""A binary sensor which use ffmpeg for noise detection."""
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
"""
# init config
self._ffmpeg.set_options(
time_reset=self._config.get(CONF_RESET),
time_repeat=self._config.get(CONF_REPEAT_TIME),
repeat=self._config.get(CONF_REPEAT),
changes=self._config.get(CONF_CHANGES),
)
# run
return self._ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return "motion"

View File

@ -0,0 +1,127 @@
"""
Provides a binary sensor which is a collection of ffmpeg tools.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ffmpeg_motion/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.ffmpeg import (
FFmpegBase, DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS,
CONF_INITIAL_STATE)
from homeassistant.const import CONF_NAME
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
CONF_RESET = 'reset'
CONF_CHANGES = 'changes'
CONF_REPEAT = 'repeat'
CONF_REPEAT_TIME = 'repeat_time'
DEFAULT_NAME = 'FFmpeg Motion'
DEFAULT_INIT_STATE = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_RESET, default=10):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_CHANGES, default=10):
vol.All(vol.Coerce(float), vol.Range(min=0, max=99)),
vol.Inclusive(CONF_REPEAT, 'repeat'):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Inclusive(CONF_REPEAT_TIME, 'repeat'):
vol.All(vol.Coerce(int), vol.Range(min=1)),
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Create the binary sensor."""
manager = hass.data[DATA_FFMPEG]
# check source
if not manager.async_run_test(config.get(CONF_INPUT)):
return
# generate sensor object
entity = FFmpegMotion(hass, manager, config)
# add to system
manager.async_register_device(entity)
yield from async_add_devices([entity])
class FFmpegBinarySensor(FFmpegBase, BinarySensorDevice):
"""A binary sensor which use ffmpeg for noise detection."""
def __init__(self, hass, config):
"""Constructor for binary sensor noise detection."""
super().__init__(config.get(CONF_INITIAL_STATE))
self._state = False
self._config = config
self._name = config.get(CONF_NAME)
@callback
def _async_callback(self, state):
"""HA-FFmpeg callback for noise detection."""
self._state = state
self.hass.async_add_job(self.async_update_ha_state())
@property
def is_on(self):
"""True if the binary sensor is on."""
return self._state
@property
def name(self):
"""Return the name of the entity."""
return self._name
class FFmpegMotion(FFmpegBinarySensor):
"""A binary sensor which use ffmpeg for noise detection."""
def __init__(self, hass, manager, config):
"""Initialize ffmpeg motion binary sensor."""
from haffmpeg import SensorMotion
super().__init__(hass, config)
self.ffmpeg = SensorMotion(
manager.binary, hass.loop, self._async_callback)
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
"""
# init config
self.ffmpeg.set_options(
time_reset=self._config.get(CONF_RESET),
time_repeat=self._config.get(CONF_REPEAT_TIME, 0),
repeat=self._config.get(CONF_REPEAT, 0),
changes=self._config.get(CONF_CHANGES),
)
# run
return self.ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return "motion"

View File

@ -0,0 +1,96 @@
"""
Provides a binary sensor which is a collection of ffmpeg tools.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ffmpeg_noise/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA
from homeassistant.components.binary_sensor.ffmpeg_motion import (
FFmpegBinarySensor)
from homeassistant.components.ffmpeg import (
DATA_FFMPEG, CONF_INPUT, CONF_OUTPUT, CONF_EXTRA_ARGUMENTS,
CONF_INITIAL_STATE)
from homeassistant.const import CONF_NAME
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
CONF_PEAK = 'peak'
CONF_DURATION = 'duration'
CONF_RESET = 'reset'
DEFAULT_NAME = 'FFmpeg Noise'
DEFAULT_INIT_STATE = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_OUTPUT): cv.string,
vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int),
vol.Optional(CONF_DURATION, default=1):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_RESET, default=10):
vol.All(vol.Coerce(int), vol.Range(min=1)),
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Create the binary sensor."""
manager = hass.data[DATA_FFMPEG]
# check source
if not manager.async_run_test(config.get(CONF_INPUT)):
return
# generate sensor object
entity = FFmpegNoise(hass, manager, config)
# add to system
manager.async_register_device(entity)
yield from async_add_devices([entity])
class FFmpegNoise(FFmpegBinarySensor):
"""A binary sensor which use ffmpeg for noise detection."""
def __init__(self, hass, manager, config):
"""Initialize ffmpeg noise binary sensor."""
from haffmpeg import SensorNoise
super().__init__(hass, config)
self.ffmpeg = SensorNoise(
manager.binary, hass.loop, self._async_callback)
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
"""
# init config
self.ffmpeg.set_options(
time_duration=self._config.get(CONF_DURATION),
time_reset=self._config.get(CONF_RESET),
peak=self._config.get(CONF_PEAK),
)
# run
return self.ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
output_dest=self._config.get(CONF_OUTPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return "sound"

View File

@ -36,7 +36,10 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT binary sensor."""
"""Set up the MQTT binary sensor."""
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass

View File

@ -1,25 +0,0 @@
# Describes the format for available binary_sensor services
ffmpeg_start:
description: Send a start command to a ffmpeg based sensor.
fields:
entity_id:
description: Name(s) of entites that will start. Platform dependent.
example: 'binary_sensor.ffmpeg_noise'
ffmpeg_stop:
description: Send a stop command to a ffmpeg based sensor.
fields:
entity_id:
description: Name(s) of entites that will stop. Platform dependent.
example: 'binary_sensor.ffmpeg_noise'
ffmpeg_restart:
description: Send a restart command to a ffmpeg based sensor.
fields:
entity_id:
description: Name(s) of entites that will restart. Platform dependent.
example: 'binary_sensor.ffmpeg_noise'

View File

@ -118,7 +118,8 @@ class BinarySensorTemplate(BinarySensorDevice):
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
# Common during HA startup - so just a warning
_LOGGER.warning(ex)
_LOGGER.warning('Could not render template %s,'
' the state is unknown.', self._name)
return
_LOGGER.error(ex)
_LOGGER.error('Could not render template %s: %s', self._name, ex)
self._state = False

View File

@ -4,11 +4,14 @@ Support for Wink binary sensors.
For more details about this platform, please refer to the documentation at
at https://home-assistant.io/components/binary_sensor.wink/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.sensor.wink import WinkDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['wink']
# These are the available sensors mapped to binary_sensor class
@ -17,11 +20,14 @@ SENSOR_TYPES = {
"brightness": "light",
"vibration": "vibration",
"loudness": "sound",
"noise": "sound",
"capturing_audio": "sound",
"liquid_detected": "moisture",
"motion": "motion",
"presence": "occupancy",
"co_detected": "gas",
"smoke_detected": "smoke"
"smoke_detected": "smoke",
"capturing_video": None
}
@ -30,26 +36,54 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
import pywink
for sensor in pywink.get_sensors():
if sensor.capability() in SENSOR_TYPES:
add_devices([WinkBinarySensorDevice(sensor, hass)])
_id = sensor.object_id() + sensor.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
if sensor.capability() in SENSOR_TYPES:
add_devices([WinkBinarySensorDevice(sensor, hass)])
for key in pywink.get_keys():
add_devices([WinkBinarySensorDevice(key, hass)])
_id = key.object_id() + key.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkBinarySensorDevice(key, hass)])
for sensor in pywink.get_smoke_and_co_detectors():
add_devices([WinkBinarySensorDevice(sensor, hass)])
_id = sensor.object_id() + sensor.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkSmokeDetector(sensor, hass)])
for hub in pywink.get_hubs():
add_devices([WinkHub(hub, hass)])
_id = hub.object_id() + hub.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkHub(hub, hass)])
for remote in pywink.get_remotes():
add_devices([WinkRemote(remote, hass)])
_id = remote.object_id() + remote.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkRemote(remote, hass)])
for button in pywink.get_buttons():
add_devices([WinkButton(button, hass)])
_id = button.object_id() + button.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkButton(button, hass)])
for gang in pywink.get_gangs():
add_devices([WinkGang(gang, hass)])
_id = gang.object_id() + gang.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkGang(gang, hass)])
for door_bell_sensor in pywink.get_door_bells():
_id = door_bell_sensor.object_id() + door_bell_sensor.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkBinarySensorDevice(door_bell_sensor, hass)])
for camera_sensor in pywink.get_cameras():
_id = camera_sensor.object_id() + camera_sensor.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
try:
if camera_sensor.capability() in SENSOR_TYPES:
add_devices([WinkBinarySensorDevice(camera_sensor, hass)])
except AttributeError:
_LOGGER.info("Device isn't a sensor, skipping.")
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
@ -58,8 +92,14 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
self._unit_of_measurement = self.wink.unit()
self.capability = self.wink.capability()
try:
self._unit_of_measurement = self.wink.unit()
except AttributeError:
self._unit_of_measurement = None
try:
self.capability = self.wink.capability()
except AttributeError:
self.capability = None
@property
def is_on(self):
@ -72,17 +112,27 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
return SENSOR_TYPES.get(self.capability)
class WinkHub(WinkDevice, BinarySensorDevice, Entity):
"""Representation of a Wink Hub."""
class WinkSmokeDetector(WinkBinarySensorDevice):
"""Representation of a Wink Smoke detector."""
def __init(self, wink, hass):
"""Initialize the hub sensor."""
WinkDevice.__init__(self, wink, hass)
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.wink.state()
def device_state_attributes(self):
"""Return the state attributes."""
return {
'test_activated': self.wink.test_activated()
}
class WinkHub(WinkBinarySensorDevice):
"""Representation of a Wink Hub."""
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
@ -93,17 +143,12 @@ class WinkHub(WinkDevice, BinarySensorDevice, Entity):
}
class WinkRemote(WinkDevice, BinarySensorDevice, Entity):
class WinkRemote(WinkBinarySensorDevice):
"""Representation of a Wink Lutron Connected bulb remote."""
def __init(self, wink, hass):
"""Initialize the hub sensor."""
WinkDevice.__init__(self, wink, hass)
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.wink.state()
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
@ -115,18 +160,18 @@ class WinkRemote(WinkDevice, BinarySensorDevice, Entity):
'button_down_pressed': self.wink.button_down_pressed()
}
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return None
class WinkButton(WinkDevice, BinarySensorDevice, Entity):
class WinkButton(WinkBinarySensorDevice):
"""Representation of a Wink Relay button."""
def __init(self, wink, hass):
"""Initialize the hub sensor."""
WinkDevice.__init__(self, wink, hass)
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.wink.state()
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def device_state_attributes(self):
@ -137,12 +182,12 @@ class WinkButton(WinkDevice, BinarySensorDevice, Entity):
}
class WinkGang(WinkDevice, BinarySensorDevice, Entity):
class WinkGang(WinkBinarySensorDevice):
"""Representation of a Wink Relay gang."""
def __init(self, wink, hass):
"""Initialize the gang sensor."""
WinkDevice.__init__(self, wink, hass)
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
@property
def is_on(self):

View File

@ -9,6 +9,7 @@ import datetime
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components import zwave
from homeassistant.components.zwave import workaround
from homeassistant.components.binary_sensor import (
DOMAIN,
BinarySensorDevice)
@ -16,22 +17,6 @@ from homeassistant.components.binary_sensor import (
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
PHILIO = 0x013c
PHILIO_SLIM_SENSOR = 0x0002
PHILIO_SLIM_SENSOR_MOTION = (PHILIO, PHILIO_SLIM_SENSOR, 0)
PHILIO_3_IN_1_SENSOR_GEN_4 = 0x000d
PHILIO_3_IN_1_SENSOR_GEN_4_MOTION = (PHILIO, PHILIO_3_IN_1_SENSOR_GEN_4, 0)
WENZHOU = 0x0118
WENZHOU_SLIM_SENSOR_MOTION = (WENZHOU, PHILIO_SLIM_SENSOR, 0)
WORKAROUND_NO_OFF_EVENT = 'trigger_no_off_event'
DEVICE_MAPPINGS = {
PHILIO_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT,
PHILIO_3_IN_1_SENSOR_GEN_4_MOTION: WORKAROUND_NO_OFF_EVENT,
WENZHOU_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT,
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Z-Wave platform for binary sensors."""
@ -42,23 +27,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
value.set_change_verified(False)
# 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()):
specific_sensor_key = (int(value.node.manufacturer_id, 16),
int(value.node.product_id, 16),
value.index)
device_mapping = workaround.get_device_mapping(value)
if device_mapping == workaround.WORKAROUND_NO_OFF_EVENT:
# Default the multiplier to 4
re_arm_multiplier = (zwave.get_config_value(value.node, 9) or 4)
add_devices([
ZWaveTriggerSensor(value, "motion",
hass, re_arm_multiplier * 8)
])
return
if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_NO_OFF_EVENT:
# Default the multiplier to 4
re_arm_multiplier = (zwave.get_config_value(value.node,
9) or 4)
add_devices([
ZWaveTriggerSensor(value, "motion",
hass, re_arm_multiplier * 8)
])
return
if workaround.get_device_component_mapping(value) == DOMAIN:
add_devices([ZWaveBinarySensor(value, None)])
return
if value.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY:
add_devices([ZWaveBinarySensor(value, None)])

View File

@ -122,8 +122,6 @@ def set_away_mode(hass, away_mode, entity_id=None):
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
_LOGGER.warning(
'This service has been deprecated; use climate.set_hold_mode')
hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
@ -243,14 +241,6 @@ def async_setup(hass, config):
away_mode = service.data.get(ATTR_AWAY_MODE)
if away_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE)
return
_LOGGER.warning(
'This service has been deprecated; use climate.set_hold_mode')
for climate in target_climate:
if away_mode:
yield from climate.async_turn_away_mode_on()
@ -288,12 +278,6 @@ def async_setup(hass, config):
aux_heat = service.data.get(ATTR_AUX_HEAT)
if aux_heat is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_AUX_HEAT, ATTR_AUX_HEAT)
return
for climate in target_climate:
if aux_heat:
yield from climate.async_turn_aux_heat_on()
@ -340,12 +324,6 @@ def async_setup(hass, config):
humidity = service.data.get(ATTR_HUMIDITY)
if humidity is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_HUMIDITY, ATTR_HUMIDITY)
return
for climate in target_climate:
yield from climate.async_set_humidity(humidity)
@ -363,12 +341,6 @@ def async_setup(hass, config):
fan = service.data.get(ATTR_FAN_MODE)
if fan is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_FAN_MODE, ATTR_FAN_MODE)
return
for climate in target_climate:
yield from climate.async_set_fan_mode(fan)
@ -386,12 +358,6 @@ def async_setup(hass, config):
operation_mode = service.data.get(ATTR_OPERATION_MODE)
if operation_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_OPERATION_MODE, ATTR_OPERATION_MODE)
return
for climate in target_climate:
yield from climate.async_set_operation_mode(operation_mode)
@ -409,12 +375,6 @@ def async_setup(hass, config):
swing_mode = service.data.get(ATTR_SWING_MODE)
if swing_mode is None:
_LOGGER.error(
"Received call to %s without attribute %s",
SERVICE_SET_SWING_MODE, ATTR_SWING_MODE)
return
for climate in target_climate:
yield from climate.async_set_swing_mode(swing_mode)

View File

@ -135,27 +135,27 @@ class DemoClimate(ClimateDevice):
kwargs.get(ATTR_TARGET_TEMP_LOW) is not None:
self._target_temperature_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
self._target_temperature_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
self.update_ha_state()
self.schedule_update_ha_state()
def set_humidity(self, humidity):
"""Set new target temperature."""
self._target_humidity = humidity
self.update_ha_state()
self.schedule_update_ha_state()
def set_swing_mode(self, swing_mode):
"""Set new target temperature."""
self._current_swing_mode = swing_mode
self.update_ha_state()
self.schedule_update_ha_state()
def set_fan_mode(self, fan):
"""Set new target temperature."""
self._current_fan_mode = fan
self.update_ha_state()
self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set new target temperature."""
self._current_operation = operation_mode
self.update_ha_state()
self.schedule_update_ha_state()
@property
def current_swing_mode(self):
@ -170,24 +170,24 @@ class DemoClimate(ClimateDevice):
def turn_away_mode_on(self):
"""Turn away mode on."""
self._away = True
self.update_ha_state()
self.schedule_update_ha_state()
def turn_away_mode_off(self):
"""Turn away mode off."""
self._away = False
self.update_ha_state()
self.schedule_update_ha_state()
def set_hold_mode(self, hold):
"""Update hold mode on."""
self._hold = hold
self.update_ha_state()
self.schedule_update_ha_state()
def turn_aux_heat_on(self):
"""Turn away auxillary heater on."""
self._aux = True
self.update_ha_state()
self.schedule_update_ha_state()
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
self._aux = False
self.update_ha_state()
self.schedule_update_ha_state()

View File

@ -69,7 +69,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for thermostat in target_thermostats:
thermostat.set_fan_min_on_time(str(fan_min_on_time))
thermostat.update_ha_state(True)
thermostat.schedule_update_ha_state(True)
def resume_program_set_service(service):
"""Resume the program on the target thermostats."""
@ -85,7 +85,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for thermostat in target_thermostats:
thermostat.resume_program(resume_all)
thermostat.update_ha_state(True)
thermostat.schedule_update_ha_state(True)
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))
@ -186,12 +186,27 @@ class Thermostat(ClimateDevice):
@property
def current_hold_mode(self):
"""Return current hold mode."""
if self.is_away_mode_on:
events = self.thermostat['events']
if any((event['holdClimateRef'] == 'away' and
int(event['endDate'][0:4])-int(event['startDate'][0:4]) <= 1)
or event['type'] == 'autoAway'
for event in events):
# away hold is auto away or a temporary hold from away climate
hold = 'away'
elif self.is_home_mode_on:
elif any(event['holdClimateRef'] == 'away' and
int(event['endDate'][0:4])-int(event['startDate'][0:4]) > 1
for event in events):
# a permanent away is not considered a hold, but away_mode
hold = None
elif any(event['holdClimateRef'] == 'home' or
event['type'] == 'autoHome'
for event in events):
# home mode is auto home or any home hold
hold = 'home'
elif self.is_temp_hold_on():
elif any(event['type'] == 'hold' and event['running']
for event in events):
hold = 'temp'
# temperature hold is any other hold not based on climate
else:
hold = None
return hold
@ -255,42 +270,23 @@ class Thermostat(ClimateDevice):
return any(event['type'] == 'vacation' and event['running']
for event in events)
def is_temp_hold_on(self):
"""Return true if temperature hold is on."""
events = self.thermostat['events']
return any(event['type'] == 'hold' and event['running']
for event in events)
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
events = self.thermostat['events']
return any(event['holdClimateRef'] == 'away' or
event['type'] == 'autoAway'
return any(event['holdClimateRef'] == 'away' and
int(event['endDate'][0:4])-int(event['startDate'][0:4]) > 1
for event in events)
def turn_away_mode_on(self):
"""Turn away on."""
self.data.ecobee.set_climate_hold(self.thermostat_index,
"away", self.hold_preference())
"away", 'indefinite')
self.update_without_throttle = True
def turn_away_mode_off(self):
"""Turn away off."""
self.set_hold_mode(None)
@property
def is_home_mode_on(self):
"""Return true if home mode is on."""
events = self.thermostat['events']
return any(event['holdClimateRef'] == 'home' or
event['type'] == 'autoHome'
for event in events)
def turn_home_mode_on(self):
"""Turn home on."""
self.data.ecobee.set_climate_hold(self.thermostat_index,
"home", self.hold_preference())
self.data.ecobee.resume_program(self.thermostat_index)
self.update_without_throttle = True
def set_hold_mode(self, hold_mode):
@ -298,11 +294,14 @@ class Thermostat(ClimateDevice):
hold = self.current_hold_mode
if hold == hold_mode:
# no change, so no action required
return
elif hold_mode == 'away':
self.turn_away_mode_on()
self.data.ecobee.set_climate_hold(self.thermostat_index,
"away", self.hold_preference())
elif hold_mode == 'home':
self.turn_home_mode_on()
self.data.ecobee.set_climate_hold(self.thermostat_index,
"home", self.hold_preference())
elif hold_mode == 'temp':
self.set_temp_hold(int(self.current_temperature))
else:
@ -378,17 +377,8 @@ class Thermostat(ClimateDevice):
default = self.thermostat['settings']['holdAction']
if default == 'nextTransition':
return default
elif default == 'indefinite':
return default
# add further conditions if other hold durations should be
# supported; note that this should not include 'indefinite'
# as an indefinite away hold is interpreted as away_mode
else:
return 'nextTransition'
# Sleep mode isn't used in UI yet:
# def turn_sleep_mode_on(self):
# """ Turns sleep mode on. """
# self.data.ecobee.set_climate_hold(self.thermostat_index, "sleep")
# def turn_sleep_mode_off(self):
# """ Turns sleep mode off. """
# self.data.ecobee.resume_program(self.thermostat_index)

View File

@ -10,14 +10,14 @@ import voluptuous as vol
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, PRECISION_HALVES,
STATE_UNKNOWN, STATE_AUTO, STATE_ON, STATE_OFF,
STATE_AUTO, STATE_ON, STATE_OFF,
)
from homeassistant.const import (
CONF_MAC, TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-eq3bt==0.1.4']
REQUIREMENTS = ['python-eq3bt==0.1.5']
_LOGGER = logging.getLogger(__name__)
@ -61,15 +61,12 @@ class EQ3BTSmartThermostat(ClimateDevice):
# we want to avoid name clash with this module..
import eq3bt as eq3
self.modes = {None: STATE_UNKNOWN, # When not yet connected.
eq3.Mode.Unknown: STATE_UNKNOWN,
eq3.Mode.Auto: STATE_AUTO,
# away handled separately, here just for reverse mapping
eq3.Mode.Away: STATE_AWAY,
self.modes = {eq3.Mode.Open: STATE_ON,
eq3.Mode.Closed: STATE_OFF,
eq3.Mode.Open: STATE_ON,
eq3.Mode.Auto: STATE_AUTO,
eq3.Mode.Manual: STATE_MANUAL,
eq3.Mode.Boost: STATE_BOOST}
eq3.Mode.Boost: STATE_BOOST,
eq3.Mode.Away: STATE_AWAY}
self.reverse_modes = {v: k for k, v in self.modes.items()}
@ -79,7 +76,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
@property
def available(self) -> bool:
"""Return if thermostat is available."""
return self.current_operation != STATE_UNKNOWN
return self.current_operation is not None
@property
def name(self):
@ -116,6 +113,8 @@ class EQ3BTSmartThermostat(ClimateDevice):
@property
def current_operation(self):
"""Current mode."""
if self._thermostat.mode < 0:
return None
return self.modes[self._thermostat.mode]
@property

View File

@ -4,17 +4,19 @@ Adds support for generic thermostat units.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.generic_thermostat/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components import switch
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE)
from homeassistant.helpers import condition
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -48,7 +50,8 @@ PLATFORM_SCHEMA = 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 generic thermostat."""
name = config.get(CONF_NAME)
heater_entity_id = config.get(CONF_HEATER)
@ -60,7 +63,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
min_cycle_duration = config.get(CONF_MIN_DUR)
tolerance = config.get(CONF_TOLERANCE)
add_devices([GenericThermostat(
yield from async_add_devices([GenericThermostat(
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
target_temp, ac_mode, min_cycle_duration, tolerance)])
@ -86,12 +89,14 @@ class GenericThermostat(ClimateDevice):
self._target_temp = target_temp
self._unit = hass.config.units.temperature_unit
track_state_change(hass, sensor_entity_id, self._sensor_changed)
track_state_change(hass, heater_entity_id, self._switch_changed)
async_track_state_change(
hass, sensor_entity_id, self._async_sensor_changed)
async_track_state_change(
hass, heater_entity_id, self._async_switch_changed)
sensor_state = hass.states.get(sensor_entity_id)
if sensor_state:
self._update_temp(sensor_state)
self._async_update_temp(sensor_state)
@property
def should_poll(self):
@ -128,14 +133,15 @@ class GenericThermostat(ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temp
def set_temperature(self, **kwargs):
@asyncio.coroutine
def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self._target_temp = temperature
self._control_heating()
self.schedule_update_ha_state()
self._async_control_heating()
yield from self.async_update_ha_state()
@property
def min_temp(self):
@ -157,22 +163,25 @@ class GenericThermostat(ClimateDevice):
# Get default temp from super class
return ClimateDevice.max_temp.fget(self)
def _sensor_changed(self, entity_id, old_state, new_state):
@asyncio.coroutine
def _async_sensor_changed(self, entity_id, old_state, new_state):
"""Called when temperature changes."""
if new_state is None:
return
self._update_temp(new_state)
self._control_heating()
self.schedule_update_ha_state()
self._async_update_temp(new_state)
self._async_control_heating()
yield from self.async_update_ha_state()
def _switch_changed(self, entity_id, old_state, new_state):
@callback
def _async_switch_changed(self, entity_id, old_state, new_state):
"""Called when heater switch changes state."""
if new_state is None:
return
self.schedule_update_ha_state()
self.hass.async_add_job(self.async_update_ha_state())
def _update_temp(self, state):
@callback
def _async_update_temp(self, state):
"""Update thermostat with latest state from sensor."""
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
@ -182,7 +191,8 @@ class GenericThermostat(ClimateDevice):
except ValueError as ex:
_LOGGER.error('Unable to update from sensor: %s', ex)
def _control_heating(self):
@callback
def _async_control_heating(self):
"""Check if we need to turn heating on or off."""
if not self._active and None not in (self._cur_temp,
self._target_temp):
@ -198,9 +208,9 @@ class GenericThermostat(ClimateDevice):
current_state = STATE_ON
else:
current_state = STATE_OFF
long_enough = condition.state(self.hass, self.heater_entity_id,
current_state,
self.min_cycle_duration)
long_enough = condition.state(
self.hass, self.heater_entity_id, current_state,
self.min_cycle_duration)
if not long_enough:
return
@ -210,12 +220,12 @@ class GenericThermostat(ClimateDevice):
too_cold = self._target_temp - self._cur_temp > self._tolerance
if too_cold:
_LOGGER.info('Turning off AC %s', self.heater_entity_id)
switch.turn_off(self.hass, self.heater_entity_id)
switch.async_turn_off(self.hass, self.heater_entity_id)
else:
too_hot = self._cur_temp - self._target_temp > self._tolerance
if too_hot:
_LOGGER.info('Turning on AC %s', self.heater_entity_id)
switch.turn_on(self.hass, self.heater_entity_id)
switch.async_turn_on(self.hass, self.heater_entity_id)
else:
is_heating = self._is_device_active
if is_heating:
@ -223,12 +233,12 @@ class GenericThermostat(ClimateDevice):
if too_hot:
_LOGGER.info('Turning off heater %s',
self.heater_entity_id)
switch.turn_off(self.hass, self.heater_entity_id)
switch.async_turn_off(self.hass, self.heater_entity_id)
else:
too_cold = self._target_temp - self._cur_temp > self._tolerance
if too_cold:
_LOGGER.info('Turning on heater %s', self.heater_entity_id)
switch.turn_on(self.hass, self.heater_entity_id)
switch.async_turn_on(self.hass, self.heater_entity_id)
@property
def _is_device_active(self):

View File

@ -135,7 +135,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[value_type] = value
self.update_ha_state()
self.schedule_update_ha_state()
def set_fan_mode(self, fan):
"""Set new target temperature."""
@ -145,7 +145,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[set_req.V_HVAC_SPEED] = fan
self.update_ha_state()
self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set new target temperature."""
@ -156,7 +156,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[set_req.V_HVAC_FLOW_STATE] = operation_mode
self.update_ha_state()
self.schedule_update_ha_state()
def update(self):
"""Update the controller with the latest value from a sensor."""

View File

@ -111,7 +111,6 @@ class NetatmoThermostat(ClimateDevice):
temp = None
self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None)
self._away = True
self.update_ha_state()
def turn_away_mode_off(self):
"""Turn away off."""
@ -119,7 +118,6 @@ class NetatmoThermostat(ClimateDevice):
temp = None
self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None)
self._away = False
self.update_ha_state()
def set_temperature(self, endTimeOffset=DEFAULT_TIME_OFFSET, **kwargs):
"""Set new target temperature for 2 hours."""
@ -131,7 +129,6 @@ class NetatmoThermostat(ClimateDevice):
mode, temperature, endTimeOffset)
self._target_temperature = temperature
self._away = False
self.update_ha_state()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):

View File

@ -78,7 +78,7 @@ set_fan_mode:
description: Name(s) of entities to change
example: 'climate.nest'
fan:
fan_mode:
description: New value of fan mode
example: On Low

View File

@ -4,7 +4,7 @@ Support for Wink thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.wink/
"""
from homeassistant.components.wink import WinkDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
@ -13,12 +13,16 @@ from homeassistant.components.climate import (
from homeassistant.const import (
TEMP_CELSIUS, STATE_ON,
STATE_OFF, STATE_UNKNOWN)
from homeassistant.loader import get_component
DEPENDENCIES = ['wink']
STATE_AUX = 'aux'
STATE_ECO = 'eco'
STATE_FAN = 'fan'
SPEED_LOWEST = 'lowest'
SPEED_LOW = 'low'
SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high'
ATTR_EXTERNAL_TEMPERATURE = "external_temperature"
ATTR_SMART_TEMPERATURE = "smart_temperature"
@ -30,8 +34,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink thermostat."""
import pywink
temp_unit = hass.config.units.temperature_unit
add_devices(WinkThermostat(thermostat, hass, temp_unit)
for thermostat in pywink.get_thermostats())
for climate in pywink.get_thermostats():
_id = climate.object_id() + climate.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkThermostat(climate, hass, temp_unit)])
for climate in pywink.get_air_conditioners():
_id = climate.object_id() + climate.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkAC(climate, hass, temp_unit)])
# pylint: disable=abstract-method,too-many-public-methods, too-many-branches
@ -41,7 +51,6 @@ class WinkThermostat(WinkDevice, ClimateDevice):
def __init__(self, wink, hass, temp_unit):
"""Initialize the Wink device."""
super().__init__(wink, hass)
wink = get_component('wink')
self._config_temp_unit = temp_unit
@property
@ -329,3 +338,131 @@ class WinkThermostat(WinkDevice, ClimateDevice):
else:
return_value = maximum
return return_value
class WinkAC(WinkDevice, ClimateDevice):
"""Representation of a Wink air conditioner."""
def __init__(self, wink, hass, temp_unit):
"""Initialize the Wink device."""
super().__init__(wink, hass)
self._config_temp_unit = temp_unit
@property
def temperature_unit(self):
"""Return the unit of measurement."""
# The Wink API always returns temp in Celsius
return TEMP_CELSIUS
@property
def device_state_attributes(self):
"""Return the optional state attributes."""
data = {}
target_temp_high = self.target_temperature_high
target_temp_low = self.target_temperature_low
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display(
self.target_temperature_high)
if target_temp_low is not None:
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display(
self.target_temperature_low)
data["total_consumption"] = self.wink.toatl_consumption()
data["schedule_enabled"] = self.wink.toatl_consumption()
return data
@property
def current_temperature(self):
"""Return the current temperature."""
return self.wink.current_temperature()
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if not self.wink.is_on():
current_op = STATE_OFF
elif self.wink.current_mode() == 'cool_only':
current_op = STATE_COOL
elif self.wink.current_mode() == 'auto_eco':
current_op = STATE_ECO
elif self.wink.current_mode() == 'fan_only':
current_op = STATE_FAN
else:
current_op = STATE_UNKNOWN
return current_op
@property
def operation_list(self):
"""List of available operation modes."""
op_list = ['off']
modes = self.wink.modes()
if 'cool_only' in modes:
op_list.append(STATE_COOL)
if 'auto_eco' in modes:
op_list.append(STATE_ECO)
if 'fan_eco' in modes:
op_list.append(STATE_FAN)
return op_list
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
self.wink.set_temperature(target_temp)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
if operation_mode == STATE_COOL:
self.wink.set_operation_mode('cool_only')
elif operation_mode == STATE_ECO:
self.wink.set_operation_mode('auto_eco')
elif operation_mode == STATE_OFF:
self.wink.set_operation_mode('off')
elif operation_mode == STATE_FAN:
self.wink.set_operation_mode('fan_only')
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.wink.current_max_set_point()
@property
def target_temperature_low(self):
"""Only supports cool."""
return None
@property
def target_temperature_high(self):
"""Only supports cool."""
return None
@property
def current_fan_mode(self):
"""Return the current fan mode."""
speed = self.wink.current_fan_speed()
if speed <= 0.3 and speed >= 0.0:
return SPEED_LOWEST
elif speed <= 0.5 and speed > 0.3:
return SPEED_LOW
elif speed <= 0.8 and speed > 0.5:
return SPEED_MEDIUM
elif speed <= 1.0 and speed > 0.8:
return SPEED_HIGH
else:
return STATE_UNKNOWN
@property
def fan_list(self):
"""List of available fan modes."""
return [SPEED_LOWEST, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def set_fan_mode(self, mode):
"""Set fan speed."""
if mode == SPEED_LOWEST:
speed = 0.3
elif mode == SPEED_LOW:
speed = 0.5
elif mode == SPEED_MEDIUM:
speed = 0.8
elif mode == SPEED_HIGH:
speed = 1.0
self.wink.set_ac_fan_speed(speed)

View File

@ -83,45 +83,52 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def update_properties(self):
"""Callback on data changes for node values."""
# Operation Mode
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
self._current_operation = value.data
self._operation_list = list(value.data_items)
_LOGGER.debug("self._operation_list=%s", self._operation_list)
_LOGGER.debug("self._current_operation=%s",
self._current_operation)
self._current_operation = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, member='data')
operation_list = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE,
member='data_items')
if operation_list:
self._operation_list = list(operation_list)
_LOGGER.debug("self._operation_list=%s", self._operation_list)
_LOGGER.debug("self._current_operation=%s", self._current_operation)
# Current Temp
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL)
.values()):
if value.label == 'Temperature':
self._current_temperature = round((float(value.data)), 1)
self._unit = value.units
self._current_temperature = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL,
label=['Temperature'], member='data')
self._unit = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL,
label=['Temperature'], member='units')
# Fan Mode
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE)
.values()):
self._current_fan_mode = value.data
self._fan_list = list(value.data_items)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
_LOGGER.debug("self._current_fan_mode=%s",
self._current_fan_mode)
self._current_fan_mode = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE,
member='data')
fan_list = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE,
member='data_items')
if fan_list:
self._fan_list = list(fan_list)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
_LOGGER.debug("self._current_fan_mode=%s",
self._current_fan_mode)
# Swing mode
if self._zxt_120 == 1:
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION)
.values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_CONFIGURATION and \
value.index == 33:
self._current_swing_mode = value.data
self._swing_list = list(value.data_items)
_LOGGER.debug("self._swing_list=%s", self._swing_list)
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
self._current_swing_mode = (
self.get_value(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION,
index=33,
member='data'))
swing_list = self.get_value(class_id=zwave.const
.COMMAND_CLASS_CONFIGURATION,
index=33,
member='data_items')
if swing_list:
self._swing_list = list(swing_list)
_LOGGER.debug("self._swing_list=%s", self._swing_list)
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
# Set point
temps = []
for value in (
@ -139,19 +146,16 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
break
else:
self._target_temperature = round((float(value.data)), 1)
# Operating state
for value in (
self._node.get_values(
class_id=zwave.const
.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE).values()):
self._operating_state = value.data
self._operating_state = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE,
member='data')
# Fan operating state
for value in (
self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_STATE)
.values()):
self._fan_state = value.data
self._fan_state = self.get_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_STATE,
member='data')
@property
def should_poll(self):
@ -215,50 +219,29 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
else:
return
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if value.index == self._index:
if self._zxt_120:
# ZXT-120 responds only to whole int
value.data = round(temperature, 0)
self._target_temperature = temperature
self.update_ha_state()
else:
value.data = temperature
self.update_ha_state()
break
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT,
index=self._index, data=temperature)
self.update_ha_state()
def set_fan_mode(self, fan):
"""Set new target fan mode."""
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE).
values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE and \
value.index == 0:
value.data = bytes(fan, 'utf-8')
break
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE,
index=0, data=bytes(fan, 'utf-8'))
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_THERMOSTAT_MODE and value.index == 0:
value.data = bytes(operation_mode, 'utf-8')
break
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE,
index=0, data=bytes(operation_mode, 'utf-8'))
def set_swing_mode(self, swing_mode):
"""Set new target swing mode."""
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_CONFIGURATION and \
value.index == 33:
value.data = bytes(swing_mode, 'utf-8')
break
self.set_value(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION,
index=33, data=bytes(swing_mode, 'utf-8'))
@property
def device_state_attributes(self):

View File

@ -4,9 +4,11 @@ Support for Cover devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover/
"""
import os
import asyncio
from datetime import timedelta
import functools as ft
import logging
import os
import voluptuous as vol
@ -53,17 +55,17 @@ COVER_SET_COVER_TILT_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend({
})
SERVICE_TO_METHOD = {
SERVICE_OPEN_COVER: {'method': 'open_cover'},
SERVICE_CLOSE_COVER: {'method': 'close_cover'},
SERVICE_OPEN_COVER: {'method': 'async_open_cover'},
SERVICE_CLOSE_COVER: {'method': 'async_close_cover'},
SERVICE_SET_COVER_POSITION: {
'method': 'set_cover_position',
'method': 'async_set_cover_position',
'schema': COVER_SET_COVER_POSITION_SCHEMA},
SERVICE_STOP_COVER: {'method': 'stop_cover'},
SERVICE_OPEN_COVER_TILT: {'method': 'open_cover_tilt'},
SERVICE_CLOSE_COVER_TILT: {'method': 'close_cover_tilt'},
SERVICE_STOP_COVER_TILT: {'method': 'stop_cover_tilt'},
SERVICE_STOP_COVER: {'method': 'async_stop_cover'},
SERVICE_OPEN_COVER_TILT: {'method': 'async_open_cover_tilt'},
SERVICE_CLOSE_COVER_TILT: {'method': 'async_close_cover_tilt'},
SERVICE_STOP_COVER_TILT: {'method': 'async_stop_cover_tilt'},
SERVICE_SET_COVER_TILT_POSITION: {
'method': 'set_cover_tilt_position',
'method': 'async_set_cover_tilt_position',
'schema': COVER_SET_COVER_TILT_POSITION_SCHEMA},
}
@ -124,40 +126,53 @@ def stop_cover_tilt(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_STOP_COVER_TILT, data)
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Track states and offer events for covers."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_COVERS)
component.setup(config)
def handle_cover_service(service):
yield from component.async_setup(config)
@asyncio.coroutine
def async_handle_cover_service(service):
"""Handle calls to the cover services."""
covers = component.async_extract_from_service(service)
method = SERVICE_TO_METHOD.get(service.service)
params = service.data.copy()
params.pop(ATTR_ENTITY_ID, None)
if not method:
return
covers = component.extract_from_service(service)
# call method
for cover in covers:
getattr(cover, method['method'])(**params)
yield from getattr(cover, method['method'])(**params)
update_tasks = []
for cover in covers:
if not cover.should_poll:
continue
cover.update_ha_state(True)
update_coro = hass.loop.create_task(
cover.async_update_ha_state(True))
if hasattr(cover, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
for service_name in SERVICE_TO_METHOD:
schema = SERVICE_TO_METHOD[service_name].get(
'schema', COVER_SERVICE_SCHEMA)
hass.services.register(DOMAIN, service_name, handle_cover_service,
descriptions.get(service_name), schema=schema)
hass.services.async_register(
DOMAIN, service_name, async_handle_cover_service,
descriptions.get(service_name), schema=schema)
return True
@ -215,30 +230,94 @@ class CoverDevice(Entity):
"""Open the cover."""
raise NotImplementedError()
def async_open_cover(self, **kwargs):
"""Open the cover.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.open_cover, **kwargs))
def close_cover(self, **kwargs):
"""Close cover."""
raise NotImplementedError()
def async_close_cover(self, **kwargs):
"""Close cover.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.close_cover, **kwargs))
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
pass
def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.set_cover_position, **kwargs))
def stop_cover(self, **kwargs):
"""Stop the cover."""
pass
def async_stop_cover(self, **kwargs):
"""Stop the cover.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.stop_cover, **kwargs))
def open_cover_tilt(self, **kwargs):
"""Open the cover tilt."""
pass
def async_open_cover_tilt(self, **kwargs):
"""Open the cover tilt.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.open_cover_tilt, **kwargs))
def close_cover_tilt(self, **kwargs):
"""Close the cover tilt."""
pass
def async_close_cover_tilt(self, **kwargs):
"""Close the cover tilt.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.close_cover_tilt, **kwargs))
def set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
pass
def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.set_cover_tilt_position, **kwargs))
def stop_cover_tilt(self, **kwargs):
"""Stop the cover."""
pass
def async_stop_cover_tilt(self, **kwargs):
"""Stop the cover.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, ft.partial(self.stop_cover_tilt, **kwargs))

View File

@ -149,7 +149,7 @@ class DemoCover(CoverDevice):
if self._position in (100, 0, self._set_position):
self.stop_cover()
self.update_ha_state()
self.schedule_update_ha_state()
def _listen_cover_tilt(self):
"""Listen for changes in cover tilt."""
@ -167,4 +167,4 @@ class DemoCover(CoverDevice):
if self._tilt_position in (100, 0, self._set_tilt_position):
self.stop_cover_tilt()
self.update_ha_state()
self.schedule_update_ha_state()

View File

@ -199,8 +199,7 @@ class GaradgetCover(CoverDevice):
def _check_state(self, now):
"""Check the state of the service during an operation."""
self.update()
self.update_ha_state()
self.schedule_update_ha_state(True)
def close_cover(self):
"""Close the cover."""

View File

@ -151,7 +151,7 @@ class MqttCover(CoverDevice):
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = False
self.update_ha_state()
self.schedule_update_ha_state()
def close_cover(self, **kwargs):
"""Move the cover down."""
@ -160,7 +160,7 @@ class MqttCover(CoverDevice):
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = True
self.update_ha_state()
self.schedule_update_ha_state()
def stop_cover(self, **kwargs):
"""Stop the device."""

View File

@ -75,7 +75,7 @@ class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
self._values[set_req.V_DIMMER] = 100
else:
self._values[set_req.V_LIGHT] = STATE_ON
self.update_ha_state()
self.schedule_update_ha_state()
def close_cover(self, **kwargs):
"""Move the cover down."""
@ -88,7 +88,7 @@ class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
self._values[set_req.V_DIMMER] = 0
else:
self._values[set_req.V_LIGHT] = STATE_OFF
self.update_ha_state()
self.schedule_update_ha_state()
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
@ -99,7 +99,7 @@ class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
if self.gateway.optimistic:
# Optimistically assume that cover has changed state.
self._values[set_req.V_DIMMER] = position
self.update_ha_state()
self.schedule_update_ha_state()
def stop_cover(self, **kwargs):
"""Stop the device."""

View File

@ -6,7 +6,7 @@ https://home-assistant.io/components/cover.wink/
"""
from homeassistant.components.cover import CoverDevice
from homeassistant.components.wink import WinkDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
DEPENDENCIES = ['wink']
@ -15,10 +15,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink cover platform."""
import pywink
add_devices(WinkCoverDevice(shade, hass) for shade in
pywink.get_shades())
add_devices(WinkCoverDevice(door, hass) for door in
pywink.get_garage_doors())
for shade in pywink.get_shades():
_id = shade.object_id() + shade.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkCoverDevice(shade, hass)])
for door in pywink.get_garage_doors():
_id = door.object_id() + door.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkCoverDevice(door, hass)])
class WinkCoverDevice(WinkDevice, CoverDevice):
@ -26,7 +30,7 @@ class WinkCoverDevice(WinkDevice, CoverDevice):
def __init__(self, wink, hass):
"""Initialize the cover."""
WinkDevice.__init__(self, wink, hass)
super().__init__(wink, hass)
def close_cover(self):
"""Close the shade."""
@ -36,13 +40,17 @@ class WinkCoverDevice(WinkDevice, CoverDevice):
"""Open the shade."""
self.wink.set_state(1)
def set_cover_position(self, position, **kwargs):
"""Move the roller shutter to a specific position."""
self.wink.set_state(float(position)/100)
@property
def current_cover_position(self):
"""Return the current position of roller shutter."""
return int(self.wink.state()*100)
@property
def is_closed(self):
"""Return if the cover is closed."""
state = self.wink.state()
if state == 0:
return True
elif state == 1:
return False
else:
return None
return bool(state == 0)

View File

@ -52,12 +52,11 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def __init__(self, value):
"""Initialize the zwave rollershutter."""
import libopenzwave
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
# pylint: disable=no-member
self._lozwmgr = libopenzwave.PyManager()
self._lozwmgr.create()
self._node = value.node
self._open_id = None
self._close_id = None
self._current_position = None
self._workaround = None
if (value.node.manufacturer_id.strip() and
@ -73,12 +72,15 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def update_properties(self):
"""Callback on data changes for node values."""
# Position value
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and \
value.label == 'Level':
self._current_position = value.data
self._current_position = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Level'], member='data')
self._open_id = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Open', 'Up'], member='value_id')
self._close_id = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Close', 'Down'], member='value_id')
@property
def is_closed(self):
@ -104,27 +106,11 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def open_cover(self, **kwargs):
"""Move the roller shutter up."""
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Up':
self._lozwmgr.pressButton(value.value_id)
break
zwave.NETWORK.manager.pressButton(self._open_id)
def close_cover(self, **kwargs):
"""Move the roller shutter down."""
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Close':
self._lozwmgr.pressButton(value.value_id)
break
zwave.NETWORK.manager.pressButton(self._close_id)
def set_cover_position(self, position, **kwargs):
"""Move the roller shutter to a specific position."""
@ -132,15 +118,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def stop_cover(self, **kwargs):
"""Stop the roller shutter."""
for value in self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
self._lozwmgr.releaseButton(value.value_id)
break
zwave.NETWORK.manager.releaseButton(self._open_id)
class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):

View File

@ -4,6 +4,7 @@ Provides functionality to turn on lights based on the states.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/device_sun_light_trigger/
"""
import asyncio
import logging
from datetime import timedelta
@ -12,8 +13,8 @@ import voluptuous as vol
from homeassistant.core import callback
import homeassistant.util.dt as dt_util
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
from homeassistant.helpers.event import track_point_in_time
from homeassistant.helpers.event_decorators import track_state_change
from homeassistant.helpers.event import (
async_track_point_in_time, async_track_state_change)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
@ -42,20 +43,20 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""The triggers to turn lights on or off based on device presence."""
logger = logging.getLogger(__name__)
device_tracker = get_component('device_tracker')
group = get_component('group')
light = get_component('light')
sun = get_component('sun')
disable_turn_off = config[DOMAIN].get(CONF_DISABLE_TURN_OFF)
light_group = config[DOMAIN].get(CONF_LIGHT_GROUP,
light.ENTITY_ID_ALL_LIGHTS)
light_profile = config[DOMAIN].get(CONF_LIGHT_PROFILE)
device_group = config[DOMAIN].get(CONF_DEVICE_GROUP,
device_tracker.ENTITY_ID_ALL_DEVICES)
conf = config[DOMAIN]
disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF)
light_group = conf.get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS)
light_profile = conf.get(CONF_LIGHT_PROFILE)
device_group = conf.get(CONF_DEVICE_GROUP,
device_tracker.ENTITY_ID_ALL_DEVICES)
device_entity_ids = group.get_entity_ids(hass, device_group,
device_tracker.DOMAIN)
@ -74,6 +75,8 @@ def setup(hass, config):
"""Calculate the time when to start fading lights in when sun sets.
Returns None if no next_setting data available.
Async friendly.
"""
next_setting = sun.next_setting(hass)
if not next_setting:
@ -81,22 +84,26 @@ def setup(hass, config):
return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)
def async_turn_on_before_sunset(light_id):
"""Helper function to turn on lights.
Speed is slow if there are devices home and the light is not on yet.
"""
"""Helper function to turn on lights."""
if not device_tracker.is_on(hass) or light.is_on(hass, light_id):
return
light.async_turn_on(hass, light_id,
transition=LIGHT_TRANSITION_TIME.seconds,
profile=light_profile)
def async_turn_on_factory(light_id):
"""Factory to generate turn on callbacks."""
@callback
def async_turn_on_light(now):
"""Turn on specific light."""
async_turn_on_before_sunset(light_id)
return async_turn_on_light
# Track every time sun rises so we can schedule a time-based
# pre-sun set event
@track_state_change(sun.ENTITY_ID, sun.STATE_BELOW_HORIZON,
sun.STATE_ABOVE_HORIZON)
@callback
def schedule_lights_at_sun_set(hass, entity, old_state, new_state):
def schedule_light_turn_on(entity, old_state, new_state):
"""The moment sun sets we want to have all the lights on.
We will schedule to have each light start after one another
@ -106,35 +113,23 @@ def setup(hass, config):
if not start_point:
return
def async_turn_on_factory(light_id):
"""Lambda can keep track of function parameters.
No local parameters. If we put the lambda directly in the below
statement only the last light will be turned on.
"""
@callback
def async_turn_on_light(now):
"""Turn on specific light."""
async_turn_on_before_sunset(light_id)
return async_turn_on_light
for index, light_id in enumerate(light_ids):
track_point_in_time(hass, async_turn_on_factory(light_id),
start_point + index * LIGHT_TRANSITION_TIME)
async_track_point_in_time(
hass, async_turn_on_factory(light_id),
start_point + index * LIGHT_TRANSITION_TIME)
async_track_state_change(hass, sun.ENTITY_ID, schedule_light_turn_on,
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
# If the sun is already above horizon schedule the time-based pre-sun set
# event.
if sun.is_on(hass):
schedule_lights_at_sun_set(hass, None, None, None)
schedule_light_turn_on(None, None, None)
@track_state_change(device_entity_ids, STATE_NOT_HOME, STATE_HOME)
@callback
def check_light_on_dev_state_change(hass, entity, old_state, new_state):
def check_light_on_dev_state_change(entity, old_state, new_state):
"""Handle tracked device state changes."""
# pylint: disable=unused-variable
lights_are_on = group.is_on(hass, light_group)
light_needed = not (lights_are_on or sun.is_on(hass))
# These variables are needed for the elif check
@ -164,17 +159,25 @@ def setup(hass, config):
# will all the following then, break.
break
if not disable_turn_off:
@track_state_change(device_group, STATE_HOME, STATE_NOT_HOME)
@callback
def turn_off_lights_when_all_leave(hass, entity, old_state, new_state):
"""Handle device group state change."""
# pylint: disable=unused-variable
if not group.is_on(hass, light_group):
return
async_track_state_change(
hass, device_entity_ids, check_light_on_dev_state_change,
STATE_NOT_HOME, STATE_HOME)
logger.info(
"Everyone has left but there are lights on. Turning them off")
light.async_turn_off(hass, light_ids)
if disable_turn_off:
return True
@callback
def turn_off_lights_when_all_leave(entity, old_state, new_state):
"""Handle device group state change."""
if not group.is_on(hass, light_group):
return
logger.info(
"Everyone has left but there are lights on. Turning them off")
light.async_turn_off(hass, light_ids)
async_track_state_change(
hass, device_group, turn_off_lights_when_all_leave,
STATE_HOME, STATE_NOT_HOME)
return True

View File

@ -158,10 +158,11 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
None, platform.get_scanner, hass, {DOMAIN: p_config})
elif hasattr(platform, 'async_setup_scanner'):
setup = yield from platform.async_setup_scanner(
hass, p_config, tracker.async_see)
hass, p_config, tracker.async_see, disc_info)
elif hasattr(platform, 'setup_scanner'):
setup = yield from hass.loop.run_in_executor(
None, platform.setup_scanner, hass, p_config, tracker.see)
None, platform.setup_scanner, hass, p_config, tracker.see,
disc_info)
else:
raise HomeAssistantError("Invalid device_tracker platform.")
@ -193,6 +194,13 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
discovery.async_listen(
hass, DISCOVERY_PLATFORMS.keys(), async_device_tracker_discovered)
@asyncio.coroutine
def async_platform_discovered(platform, info):
"""Callback to load a platform."""
yield from async_setup_platform(platform, {}, disc_info=info)
discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered)
# Clean up stale devices
async_track_utc_time_change(
hass, tracker.async_update_stale, second=range(0, 60, 5))

View File

@ -76,6 +76,7 @@ _IP_NEIGH_REGEX = re.compile(
r'\w+\s'
r'\w+\s'
r'(\w+\s(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))))?\s'
r'\s?(router)?'
r'(?P<status>(\w+))')
_NVRAM_CMD = 'nvram get client_info_tmp'

View File

@ -49,7 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_scanner(hass, config: dict, see):
def setup_scanner(hass, config: dict, see, discovery_info=None):
"""Validate the configuration and return an Automatic scanner."""
try:
AutomaticDeviceScanner(hass, config, see)

View File

@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the Bluetooth LE Scanner."""
# pylint: disable=import-error
from gattlib import DiscoveryService

View File

@ -21,7 +21,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the Bluetooth Scanner."""
# pylint: disable=import-error
import bluetooth

View File

@ -4,7 +4,7 @@ import random
from homeassistant.components.device_tracker import DOMAIN
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the demo tracker."""
def offset():
"""Return random offset."""

View File

@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup an endpoint for the GPSLogger application."""
hass.http.register_view(GPSLoggerView(see))

View File

@ -71,7 +71,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_scanner(hass, config: dict, see):
def setup_scanner(hass, config: dict, see, discovery_info=None):
"""Set up the iCloud Scanner."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)

View File

@ -20,7 +20,7 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup an endpoint for the Locative application."""
hass.http.register_view(LocativeView(see))

View File

@ -23,7 +23,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(mqtt.SCHEMA_BASE).extend({
})
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the MQTT tracker."""
devices = config[CONF_DEVICES]
qos = config[CONF_QOS]

View File

@ -0,0 +1,60 @@
"""
Support for tracking MySensors devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.util import slugify
DEPENDENCIES = ['mysensors']
_LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the MySensors tracker."""
def mysensors_callback(gateway, node_id):
"""Callback for mysensors platform."""
node = gateway.sensors[node_id]
if node.sketch_name is None:
_LOGGER.info('No sketch_name: node %s', node_id)
return
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
for child in node.children.values():
position = child.values.get(set_req.V_POSITION)
if child.type != pres.S_GPS or position is None:
continue
try:
latitude, longitude, _ = position.split(',')
except ValueError:
_LOGGER.error('Payload for V_POSITION %s is not of format '
'latitude,longitude,altitude', position)
continue
name = '{} {} {}'.format(
node.sketch_name, node_id, child.id)
attr = {
mysensors.ATTR_CHILD_ID: child.id,
mysensors.ATTR_DESCRIPTION: child.description,
mysensors.ATTR_DEVICE: gateway.device,
mysensors.ATTR_NODE_ID: node_id,
}
see(
dev_id=slugify(name),
host_name=name,
gps=(latitude, longitude),
battery=node.battery_level,
attributes=attr
)
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
for gateway in gateways:
gateway.platform_callbacks.append(mysensors_callback)
return True

View File

@ -71,7 +71,7 @@ def get_cipher():
return (KEYLEN, decrypt)
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Set up an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
waypoint_import = config.get(CONF_WAYPOINT_IMPORT)
@ -190,7 +190,7 @@ def setup_scanner(hass, config, see):
return
# OwnTracks uses - at the start of a beacon zone
# to switch on 'hold mode' - ignore this
location = slugify(data['desc'].lstrip("-"))
location = data['desc'].lstrip("-")
if location.lower() == 'home':
location = STATE_HOME
@ -198,7 +198,7 @@ def setup_scanner(hass, config, see):
def enter_event():
"""Execute enter event."""
zone = hass.states.get("zone.{}".format(location))
zone = hass.states.get("zone.{}".format(slugify(location)))
with LOCK:
if zone is None and data.get('t') == 'b':
# Not a HA zone, and a beacon so assume mobile
@ -227,7 +227,8 @@ def setup_scanner(hass, config, see):
if new_region:
# Exit to previous region
zone = hass.states.get("zone.{}".format(new_region))
zone = hass.states.get(
"zone.{}".format(slugify(new_region)))
_set_gps_from_zone(kwargs, new_region, zone)
_LOGGER.info("Exit to %s", new_region)
see(**kwargs)

View File

@ -19,7 +19,7 @@ from datetime import timedelta
import voluptuous as vol
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, DEFAULT_SCAN_INTERVAL)
PLATFORM_SCHEMA, DEFAULT_SCAN_INTERVAL, SOURCE_TYPE_ROUTER)
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant import util
from homeassistant import const
@ -66,14 +66,14 @@ class Host:
failed = 0
while failed < self._count: # check more times if host in unreachable
if self.ping():
see(dev_id=self.dev_id)
see(dev_id=self.dev_id, source_type=SOURCE_TYPE_ROUTER)
return True
failed += 1
_LOGGER.debug("ping KO on ip=%s failed=%d", self.ip_address, failed)
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the Host objects and return the update function."""
hosts = [Host(ip, dev_id, hass, config) for (dev_id, ip) in
config[const.CONF_HOSTS].items()]

View File

@ -30,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Return a Sky Hub 5 scanner if successful."""
"""Return a Sky Hub scanner if successful."""
scanner = SkyHubDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@ -111,6 +111,9 @@ def _get_skyhub_data(url):
def _parse_skyhub_response(data_str):
"""Parse the Sky Hub data format."""
pattmatch = re.search('attach_dev = \'(.*)\'', data_str)
if pattmatch is None:
raise IOError('Error: Impossible to fetch data from' +
' Sky Hub. Try to reboot the router.')
patt = pattmatch.group(1)
dev = [patt1.split(',') for patt1 in patt.split('<lf>')]

View File

@ -17,24 +17,24 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.3.2']
CONF_COMMUNITY = "community"
CONF_AUTHKEY = "authkey"
CONF_PRIVKEY = "privkey"
CONF_BASEOID = "baseoid"
REQUIREMENTS = ['pysnmp==4.3.3']
DEFAULT_COMMUNITY = "public"
CONF_COMMUNITY = 'community'
CONF_AUTHKEY = 'authkey'
CONF_PRIVKEY = 'privkey'
CONF_BASEOID = 'baseoid'
DEFAULT_COMMUNITY = 'public'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_COMMUNITY, default=DEFAULT_COMMUNITY): cv.string,
vol.Inclusive(CONF_AUTHKEY, "keys"): cv.string,
vol.Inclusive(CONF_PRIVKEY, "keys"): cv.string,
vol.Inclusive(CONF_AUTHKEY, 'keys'): cv.string,
vol.Inclusive(CONF_PRIVKEY, 'keys'): cv.string,
vol.Required(CONF_BASEOID): cv.string
})
@ -119,14 +119,14 @@ class SnmpScanner(DeviceScanner):
return
# pylint: disable=no-member
if errstatus:
_LOGGER.error('SNMP error: %s at %s', errstatus.prettyPrint(),
_LOGGER.error("SNMP error: %s at %s", errstatus.prettyPrint(),
errindex and restable[int(errindex) - 1][0] or '?')
return
for resrow in restable:
for _, val in resrow:
mac = binascii.hexlify(val.asOctets()).decode('utf-8')
_LOGGER.debug('Found mac %s', mac)
_LOGGER.debug("Found MAC %s", mac)
mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)])
devices.append({'mac': mac})
return devices

View File

@ -23,7 +23,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_scanner(hass, config: dict, see):
def setup_scanner(hass, config: dict, see, discovery_info=None):
"""Validate the configuration and return a TrackR scanner."""
TrackRDeviceScanner(hass, config, see)
return True

View File

@ -30,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_scanner(hass, config, see):
def setup_scanner(hass, config, see, discovery_info=None):
"""Validate the configuration and return a scanner."""
from volvooncall import Connection
connection = Connection(

View File

@ -14,7 +14,7 @@ import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.discovery import load_platform, discover
REQUIREMENTS = ['netdisco==0.8.1']
REQUIREMENTS = ['netdisco==0.8.2']
DOMAIN = 'discovery'
@ -39,6 +39,8 @@ SERVICE_HANDLERS = {
'denonavr': ('media_player', 'denonavr'),
'samsung_tv': ('media_player', 'samsungtv'),
'yeelight': ('light', 'yeelight'),
'flux_led': ('light', 'flux_led'),
'apple_tv': ('media_player', 'apple_tv'),
}
CONFIG_SCHEMA = vol.Schema({

View File

@ -8,14 +8,13 @@ from homeassistant import core
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_SET,
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, STATE_ON, STATE_OFF,
HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
HTTP_BAD_REQUEST, HTTP_NOT_FOUND, ATTR_SUPPORTED_FEATURES,
)
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_SUPPORTED_FEATURES, SUPPORT_BRIGHTNESS
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS
)
from homeassistant.components.media_player import (
ATTR_MEDIA_VOLUME_LEVEL, ATTR_SUPPORTED_MEDIA_COMMANDS,
SUPPORT_VOLUME_SET,
ATTR_MEDIA_VOLUME_LEVEL, SUPPORT_VOLUME_SET,
)
from homeassistant.components.fan import (
ATTR_SPEED, SUPPORT_SET_SPEED, SPEED_OFF, SPEED_LOW,
@ -178,11 +177,10 @@ class HueOneLightChangeView(HomeAssistantView):
# Make sure the entity actually supports brightness
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if (entity_features &
SUPPORT_BRIGHTNESS &
(entity.domain == "light")) == SUPPORT_BRIGHTNESS:
if brightness is not None:
data[ATTR_BRIGHTNESS] = brightness
if entity.domain == "light":
if entity_features & SUPPORT_BRIGHTNESS:
if brightness is not None:
data[ATTR_BRIGHTNESS] = brightness
# If the requested entity is a script add some variables
elif entity.domain == "script":
@ -195,9 +193,7 @@ class HueOneLightChangeView(HomeAssistantView):
# If the requested entity is a media player, convert to volume
elif entity.domain == "media_player":
media_commands = entity.attributes.get(
ATTR_SUPPORTED_MEDIA_COMMANDS, 0)
if media_commands & SUPPORT_VOLUME_SET == SUPPORT_VOLUME_SET:
if entity_features & SUPPORT_VOLUME_SET:
if brightness is not None:
turn_on_needed = True
domain = entity.domain
@ -215,9 +211,7 @@ class HueOneLightChangeView(HomeAssistantView):
# If the requested entity is a fan, convert to speed
elif entity.domain == "fan":
functions = entity.attributes.get(
ATTR_SUPPORTED_FEATURES, 0)
if (functions & SUPPORT_SET_SPEED) == SUPPORT_SET_SPEED:
if entity_features & SUPPORT_SET_SPEED:
if brightness is not None:
domain = entity.domain
# Convert 0-100 to a fan speed
@ -288,9 +282,10 @@ def parse_hue_api_put_light_body(request_json, entity):
# Make sure the entity actually supports brightness
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if (entity_features & SUPPORT_BRIGHTNESS) == SUPPORT_BRIGHTNESS:
report_brightness = True
result = (brightness > 0)
if entity.domain == "light":
if entity_features & SUPPORT_BRIGHTNESS:
report_brightness = True
result = (brightness > 0)
elif (entity.domain == "script" or
entity.domain == "media_player" or
@ -316,8 +311,9 @@ def get_entity_state(config, entity):
# Make sure the entity actually supports brightness
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if (entity_features & SUPPORT_BRIGHTNESS) == SUPPORT_BRIGHTNESS:
pass
if entity.domain == "light":
if entity_features & SUPPORT_BRIGHTNESS:
pass
elif entity.domain == "media_player":
level = entity.attributes.get(

View File

@ -4,7 +4,9 @@ Provides functionality to interact with fans.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/fan/
"""
import asyncio
from datetime import timedelta
import functools as ft
import logging
import os
@ -24,13 +26,14 @@ import homeassistant.helpers.config_validation as cv
DOMAIN = 'fan'
SCAN_INTERVAL = timedelta(seconds=30)
_LOGGER = logging.getLogger(__name__)
GROUP_NAME_ALL_FANS = 'all fans'
ENTITY_ID_ALL_FANS = group.ENTITY_ID_FORMAT.format(GROUP_NAME_ALL_FANS)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Bitfield of features supported by the fan entity
ATTR_SUPPORTED_FEATURES = 'supported_features'
SUPPORT_SET_SPEED = 1
SUPPORT_OSCILLATE = 2
SUPPORT_DIRECTION = 4
@ -56,7 +59,6 @@ PROP_TO_ATTR = {
'speed': ATTR_SPEED,
'speed_list': ATTR_SPEED_LIST,
'oscillating': ATTR_OSCILLATING,
'supported_features': ATTR_SUPPORTED_FEATURES,
'direction': ATTR_DIRECTION,
} # type: dict
@ -88,7 +90,32 @@ FAN_SET_DIRECTION_SCHEMA = vol.Schema({
vol.Optional(ATTR_DIRECTION): cv.string
}) # type: dict
_LOGGER = logging.getLogger(__name__)
SERVICE_TO_METHOD = {
SERVICE_TURN_ON: {
'method': 'async_turn_on',
'schema': FAN_TURN_ON_SCHEMA,
},
SERVICE_TURN_OFF: {
'method': 'async_turn_off',
'schema': FAN_TURN_OFF_SCHEMA,
},
SERVICE_TOGGLE: {
'method': 'async_toggle',
'schema': FAN_TOGGLE_SCHEMA,
},
SERVICE_SET_SPEED: {
'method': 'async_set_speed',
'schema': FAN_SET_SPEED_SCHEMA,
},
SERVICE_OSCILLATE: {
'method': 'async_oscillate',
'schema': FAN_OSCILLATE_SCHEMA,
},
SERVICE_SET_DIRECTION: {
'method': 'async_set_direction',
'schema': FAN_SET_DIRECTION_SCHEMA,
},
}
def is_on(hass, entity_id: str=None) -> bool:
@ -164,60 +191,53 @@ def set_direction(hass, entity_id: str=None, direction: str=None) -> None:
hass.services.call(DOMAIN, SERVICE_SET_DIRECTION, data)
def setup(hass, config: dict) -> None:
@asyncio.coroutine
def async_setup(hass, config: dict):
"""Expose fan control via statemachine and services."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_FANS)
component.setup(config)
def handle_fan_service(service: str) -> None:
yield from component.async_setup(config)
@asyncio.coroutine
def async_handle_fan_service(service):
"""Hande service call for fans."""
# Get the validated data
method = SERVICE_TO_METHOD.get(service.service)
params = service.data.copy()
# Convert the entity ids to valid fan ids
target_fans = component.extract_from_service(service)
target_fans = component.async_extract_from_service(service)
params.pop(ATTR_ENTITY_ID, None)
service_fun = None
for service_def in [SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_SET_SPEED, SERVICE_OSCILLATE,
SERVICE_SET_DIRECTION]:
if service_def == service.service:
service_fun = service_def
break
for fan in target_fans:
yield from getattr(fan, method['method'])(**params)
if service_fun:
for fan in target_fans:
getattr(fan, service_fun)(**params)
update_tasks = []
for fan in target_fans:
if fan.should_poll:
fan.update_ha_state(True)
return
for fan in target_fans:
if not fan.should_poll:
continue
update_coro = hass.loop.create_task(
fan.async_update_ha_state(True))
if hasattr(fan, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
# Listen for fan service calls.
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_fan_service,
descriptions.get(SERVICE_TURN_ON),
schema=FAN_TURN_ON_SCHEMA)
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_fan_service,
descriptions.get(SERVICE_TURN_OFF),
schema=FAN_TURN_OFF_SCHEMA)
hass.services.register(DOMAIN, SERVICE_SET_SPEED, handle_fan_service,
descriptions.get(SERVICE_SET_SPEED),
schema=FAN_SET_SPEED_SCHEMA)
hass.services.register(DOMAIN, SERVICE_OSCILLATE, handle_fan_service,
descriptions.get(SERVICE_OSCILLATE),
schema=FAN_OSCILLATE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_SET_DIRECTION, handle_fan_service,
descriptions.get(SERVICE_SET_DIRECTION),
schema=FAN_SET_DIRECTION_SCHEMA)
for service_name in SERVICE_TO_METHOD:
schema = SERVICE_TO_METHOD[service_name].get('schema')
hass.services.async_register(
DOMAIN, service_name, async_handle_fan_service,
descriptions.get(service_name), schema=schema)
return True
@ -225,34 +245,57 @@ def setup(hass, config: dict) -> None:
class FanEntity(ToggleEntity):
"""Representation of a fan."""
# pylint: disable=no-self-use
def set_speed(self: ToggleEntity, speed: str) -> None:
"""Set the speed of the fan."""
if speed is SPEED_OFF:
self.turn_off()
return
raise NotImplementedError()
def async_set_speed(self: ToggleEntity, speed: str):
"""Set the speed of the fan.
This method must be run in the event loop and returns a coroutine.
"""
if speed is SPEED_OFF:
return self.async_turn_off()
return self.hass.loop.run_in_executor(None, self.set_speed, speed)
def set_direction(self: ToggleEntity, direction: str) -> None:
"""Set the direction of the fan."""
raise NotImplementedError()
def async_set_direction(self: ToggleEntity, direction: str):
"""Set the direction of the fan.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.set_direction, direction)
def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None:
"""Turn on the fan."""
if speed is SPEED_OFF:
self.turn_off()
return
raise NotImplementedError()
def turn_off(self: ToggleEntity, **kwargs) -> None:
"""Turn off the fan."""
raise NotImplementedError()
def async_turn_on(self: ToggleEntity, speed: str=None, **kwargs):
"""Turn on the fan.
This method must be run in the event loop and returns a coroutine.
"""
if speed is SPEED_OFF:
return self.async_turn_off()
return self.hass.loop.run_in_executor(
None, ft.partial(self.turn_on, speed, **kwargs))
def oscillate(self: ToggleEntity, oscillating: bool) -> None:
"""Oscillate the fan."""
pass
def async_oscillate(self: ToggleEntity, oscillating: bool):
"""Oscillate the fan.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.oscillate, oscillating)
@property
def is_on(self):
"""Return true if the entity is on."""

View File

@ -56,8 +56,10 @@ class DemoFan(FanEntity):
"""Get the list of available speeds."""
return [STATE_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def turn_on(self, speed: str=SPEED_MEDIUM) -> None:
def turn_on(self, speed: str=None) -> None:
"""Turn on the entity."""
if speed is None:
speed = SPEED_MEDIUM
self.set_speed(speed)
def turn_off(self) -> None:
@ -68,17 +70,17 @@ class DemoFan(FanEntity):
def set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
self._speed = speed
self.update_ha_state()
self.schedule_update_ha_state()
def set_direction(self, direction: str) -> None:
"""Set the direction of the fan."""
self.direction = direction
self.update_ha_state()
self.schedule_update_ha_state()
def oscillate(self, oscillating: bool) -> None:
"""Set oscillation."""
self.oscillating = oscillating
self.update_ha_state()
self.schedule_update_ha_state()
@property
def current_direction(self) -> str:

View File

@ -29,7 +29,7 @@ STATE_TO_VALUE = {}
for key in VALUE_TO_STATE:
STATE_TO_VALUE[VALUE_TO_STATE[key]] = key
STATES = [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
STATES = [SPEED_OFF, SPEED_LOW, 'med', SPEED_HIGH]
# pylint: disable=unused-argument

View File

@ -150,7 +150,7 @@ class MqttFan(FanEntity):
elif payload == self._payload[STATE_OFF]:
self._state = False
self.update_ha_state()
self.schedule_update_ha_state()
if self._topic[CONF_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass, self._topic[CONF_STATE_TOPIC],
@ -165,7 +165,7 @@ class MqttFan(FanEntity):
self._speed = SPEED_MEDIUM
elif payload == self._payload[SPEED_HIGH]:
self._speed = SPEED_HIGH
self.update_ha_state()
self.schedule_update_ha_state()
if self._topic[CONF_SPEED_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass, self._topic[CONF_SPEED_STATE_TOPIC],
@ -183,7 +183,7 @@ class MqttFan(FanEntity):
self._oscillation = True
elif payload == self._payload[OSCILLATE_OFF_PAYLOAD]:
self._oscillation = False
self.update_ha_state()
self.schedule_update_ha_state()
if self._topic[CONF_OSCILLATION_STATE_TOPIC] is not None:
mqtt.subscribe(self._hass,
@ -262,7 +262,7 @@ class MqttFan(FanEntity):
self._speed = speed
mqtt.publish(self._hass, self._topic[CONF_SPEED_COMMAND_TOPIC],
mqtt_payload, self._qos, self._retain)
self.update_ha_state()
self.schedule_update_ha_state()
def oscillate(self, oscillating: bool) -> None:
"""Set oscillation."""
@ -274,4 +274,4 @@ class MqttFan(FanEntity):
mqtt.publish(self._hass,
self._topic[CONF_OSCILLATION_COMMAND_TOPIC],
payload, self._qos, self._retain)
self.update_ha_state()
self.schedule_update_ha_state()

View File

@ -10,7 +10,7 @@ from homeassistant.components.fan import (FanEntity, SPEED_HIGH,
SPEED_LOW, SPEED_MEDIUM,
STATE_UNKNOWN)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.components.wink import WinkDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
_LOGGER = logging.getLogger(__name__)
@ -22,7 +22,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink platform."""
import pywink
add_devices(WinkFanDevice(fan, hass) for fan in pywink.get_fans())
for fan in pywink.get_fans():
if fan.object_id() + fan.name() not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkFanDevice(fan, hass)])
class WinkFanDevice(WinkDevice, FanEntity):
@ -30,7 +32,7 @@ class WinkFanDevice(WinkDevice, FanEntity):
def __init__(self, wink, hass):
"""Initialize the fan."""
WinkDevice.__init__(self, wink, hass)
super().__init__(wink, hass)
def set_direction(self: ToggleEntity, direction: str) -> None:
"""Set the direction of the fan."""

View File

@ -6,18 +6,29 @@ https://home-assistant.io/components/ffmpeg/
"""
import asyncio
import logging
import os
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from homeassistant.config import load_yaml_config_file
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
DOMAIN = 'ffmpeg'
REQUIREMENTS = ["ha-ffmpeg==1.2"]
REQUIREMENTS = ["ha-ffmpeg==1.4"]
_LOGGER = logging.getLogger(__name__)
SERVICE_START = 'start'
SERVICE_STOP = 'stop'
SERVICE_RESTART = 'restart'
DATA_FFMPEG = 'ffmpeg'
CONF_INITIAL_STATE = 'initial_state'
CONF_INPUT = 'input'
CONF_FFMPEG_BIN = 'ffmpeg_bin'
CONF_EXTRA_ARGUMENTS = 'extra_arguments'
@ -34,18 +45,89 @@ CONFIG_SCHEMA = vol.Schema({
}),
}, extra=vol.ALLOW_EXTRA)
SERVICE_FFMPEG_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
def start(hass, entity_id=None):
"""Start a ffmpeg process on entity."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_START, data)
def stop(hass, entity_id=None):
"""Stop a ffmpeg process on entity."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_STOP, data)
def restart(hass, entity_id=None):
"""Restart a ffmpeg process on entity."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_RESTART, data)
@asyncio.coroutine
def async_setup(hass, config):
"""Setup the FFmpeg component."""
conf = config.get(DOMAIN, {})
hass.data[DATA_FFMPEG] = FFmpegManager(
manager = FFmpegManager(
hass,
conf.get(CONF_FFMPEG_BIN, DEFAULT_BINARY),
conf.get(CONF_RUN_TEST, DEFAULT_RUN_TEST)
)
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file,
os.path.join(os.path.dirname(__file__), 'services.yaml'))
# register service
@asyncio.coroutine
def async_service_handle(service):
"""Handle service ffmpeg process."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
if entity_ids:
devices = [device for device in manager.entities
if device.entity_id in entity_ids]
else:
devices = manager.entities
tasks = []
for device in devices:
if service.service == SERVICE_START:
tasks.append(device.async_start_ffmpeg())
elif service.service == SERVICE_STOP:
tasks.append(device.async_stop_ffmpeg())
else:
tasks.append(device.async_restart_ffmpeg())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
tasks.clear()
for device in devices:
tasks.append(device.async_update_ha_state())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_START, async_service_handle,
descriptions[DOMAIN].get(SERVICE_START), schema=SERVICE_FFMPEG_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_STOP, async_service_handle,
descriptions[DOMAIN].get(SERVICE_STOP), schema=SERVICE_FFMPEG_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_RESTART, async_service_handle,
descriptions[DOMAIN].get(SERVICE_RESTART),
schema=SERVICE_FFMPEG_SCHEMA)
hass.data[DATA_FFMPEG] = manager
return True
@ -58,12 +140,42 @@ class FFmpegManager(object):
self._cache = {}
self._bin = ffmpeg_bin
self._run_test = run_test
self._entities = []
@property
def binary(self):
"""Return ffmpeg binary from config."""
return self._bin
@property
def entities(self):
"""Return ffmpeg entities for services."""
return self._entities
@callback
def async_register_device(self, device):
"""Register a ffmpeg process/device."""
self._entities.append(device)
@asyncio.coroutine
def async_shutdown(event):
"""Stop ffmpeg process."""
yield from device.async_stop_ffmpeg()
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, async_shutdown)
# start on startup
if device.initial_state:
@asyncio.coroutine
def async_start(event):
"""Start ffmpeg process."""
yield from device.async_start_ffmpeg()
yield from device.async_update_ha_state()
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_start)
@asyncio.coroutine
def async_run_test(self, input_source):
"""Run test on this input. TRUE is deactivate or run correct.
@ -86,3 +198,42 @@ class FFmpegManager(object):
return False
self._cache[input_source] = True
return True
class FFmpegBase(Entity):
"""Interface object for ffmpeg."""
def __init__(self, initial_state=True):
"""Initialize ffmpeg base object."""
self.ffmpeg = None
self.initial_state = initial_state
@property
def available(self):
"""Return True if entity is available."""
return self.ffmpeg.is_running
@property
def should_poll(self):
"""Return True if entity has to be polled for state."""
return False
def async_start_ffmpeg(self):
"""Start a ffmpeg process.
This method must be run in the event loop and returns a coroutine.
"""
raise NotImplementedError()
def async_stop_ffmpeg(self):
"""Stop a ffmpeg process.
This method must be run in the event loop and returns a coroutine.
"""
return self.ffmpeg.close()
@asyncio.coroutine
def async_restart_ffmpeg(self):
"""Stop a ffmpeg process."""
yield from self.async_stop_ffmpeg()
yield from self.async_start_ffmpeg()

View File

@ -9,6 +9,7 @@
<link rel='apple-touch-icon' sizes='180x180'
href='/static/icons/favicon-apple-180x180.png'>
<link rel="mask-icon" href="/static/icons/home-assistant-icon.svg" color="#3fbbf4">
<link rel='preload' href='{{ core_url }}' as='script'/>
{% for panel in panels.values() -%}
<link rel='prefetch' href='{{ panel.url }}'>
{% endfor -%}
@ -24,35 +25,28 @@
<style>
body {
font-family: 'Roboto', 'Noto', sans-serif;
font-weight: 300;
font-weight: 400;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
margin: 0;
padding: 0;
}
#ha-init-skeleton {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
-webkit-justify-content: center;
-webkit-align-items: center;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin-bottom: 83px;
font-family: Roboto, sans-serif;
font-size: 0pt;
#ha-init-skeleton::before {
display: block;
content: "";
height: 48px;
background-color: #03A9F4;
}
#ha-init-skeleton .message {
transition: font-size 2s;
font-size: 0;
padding: 24px;
}
#ha-init-skeleton paper-spinner {
height: 28px;
margin-top: 16px;
#ha-init-skeleton.error .message {
font-size: 16px;
}
#ha-init-skeleton a {
@ -60,31 +54,27 @@
text-decoration: none;
font-weight: bold;
}
#ha-init-skeleton.error {
font-size: 16px;
}
#ha-init-skeleton.error img,
#ha-init-skeleton.error paper-spinner {
display: none;
}
</style>
<script>
function initError() {
document
.getElementById('ha-init-skeleton')
.classList.add('error');
document.getElementById('ha-init-skeleton').classList.add('error');
};
window.noAuth = {{ no_auth }};
window.Polymer = {lazyRegister: true, useNativeCSSProperties: true, dom: 'shady'};
window.Polymer = {
lazyRegister: true,
useNativeCSSProperties: true,
dom: 'shady',
suppressTemplateNotifications: true,
suppressBindingNotifications: true,
};
</script>
</head>
<body>
<div id='ha-init-skeleton'>
<img src='/static/icons/favicon-192x192.png' height='192'>
<paper-spinner active></paper-spinner>
Home Assistant had trouble<br>connecting to the server.<br><br><a href='/'>TRY AGAIN</a>
<div class='message'>
Home Assistant had trouble<br>connecting to the server.<br><br>
<a href='/'>TRY AGAIN</a>
</div>
</div>
<home-assistant icons='{{ icons }}'></home-assistant>
{# <script src='/static/home-assistant-polymer/build/_demo_data_compiled.js'></script> #}

View File

@ -1,18 +1,18 @@
"""DO NOT MODIFY. Auto-generated by script/fingerprint_frontend."""
FINGERPRINTS = {
"core.js": "769f3fdd4e04b34bd66c7415743cf7b5",
"frontend.html": "d48d9a13f7d677e59b1d22c6db051207",
"mdi.html": "7a0f14bbf3822449f9060b9c53bd7376",
"core.js": "adfeb513cf650acf763e284d76a48d6b",
"frontend.html": "43340b2369646b779e04a9925c225ab4",
"mdi.html": "c1dde43ccf5667f687c418fc8daf9668",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-dev-event.html": "f19840b9a6a46f57cb064b384e1353f5",
"panels/ha-panel-dev-info.html": "3765a371478cc66d677cf6dcc35267c6",
"panels/ha-panel-dev-service.html": "1d223225c1c75083738033895ea3e4b5",
"panels/ha-panel-dev-state.html": "8257d99a38358a150eafdb23fa6727e0",
"panels/ha-panel-dev-template.html": "cbb251acabd5e7431058ed507b70522b",
"panels/ha-panel-history.html": "9f2c72574fb6135beb1b381a4b8b7703",
"panels/ha-panel-dev-event.html": "5c82300b3cf543a92cf4297506e450e7",
"panels/ha-panel-dev-info.html": "0469024d94d6270a8680df2be44ba916",
"panels/ha-panel-dev-service.html": "9f749635e518a4ca7991975bdefdb10a",
"panels/ha-panel-dev-state.html": "7d069ba8fd5379fa8f59858b8c0a7473",
"panels/ha-panel-dev-template.html": "2b618508510afa5281c9ecae0c3a3dbd",
"panels/ha-panel-history.html": "8955c1d093a2c417c89ed90dd627c7d3",
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
"panels/ha-panel-logbook.html": "313f2ac57aaa5ad55933c9bbf8d8a1e5",
"panels/ha-panel-map.html": "13f120066c0b5faa2ce1db2c3f3cc486",
"panels/ha-panel-logbook.html": "f36297a894524518fa70883f264492b0",
"panels/ha-panel-map.html": "9c8c7924ba8f731560c9f4093835cc26",
"websocket_test.html": "575de64b431fe11c3785bf96d7813450"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 5159326a7b3d1ba29ae17a7861fa2eaa8c2c95f6
Subproject commit f3808ff4d44733f5810e21131e0daa1425bf5f22

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
<html><head><meta charset="UTF-8"></head><body><dom-module id="ha-panel-dev-info"><template><style include="iron-positioning ha-style">:host{background-color:#fff;-ms-user-select:initial;-webkit-user-select:initial;-moz-user-select:initial}.content{padding:16px}.about{text-align:center;line-height:2em}.version{@apply(--paper-font-headline)}.develop{@apply(--paper-font-subhead)}.about a{color:var(--dark-primary-color)}.error-log-intro{margin-top:16px;border-top:1px solid var(--light-primary-color);padding-top:16px}paper-icon-button{float:right}.error-log{@apply(--paper-font-code1)
clear: both;white-space:pre-wrap}</style><app-header-layout has-scrolling-region=""><app-header fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">About</div></app-toolbar></app-header><div class="content fit"><div class="about"><p class="version"><a href="https://home-assistant.io"><img src="/static/icons/favicon-192x192.png" height="192"></a><br>Home Assistant<br>[[hassVersion]]</p><p>Path to configuration.yaml: [[hassConfigDir]]</p><p class="develop"><a href="https://home-assistant.io/developers/credits/" target="_blank">Developed by a bunch of awesome people.</a></p><p>Published under the MIT license<br>Source: <a href="https://github.com/home-assistant/home-assistant" target="_blank">server</a><a href="https://github.com/home-assistant/home-assistant-polymer" target="_blank">frontend-ui</a> <a href="https://github.com/home-assistant/home-assistant-js" target="_blank">frontend-core</a></p><p>Built using <a href="https://www.python.org">Python 3</a>, <a href="https://www.polymer-project.org" target="_blank">Polymer [[polymerVersion]]</a>, <a href="https://optimizely.github.io/nuclear-js/" target="_blank">NuclearJS [[nuclearVersion]]</a><br>Icons by <a href="https://www.google.com/design/icons/" target="_blank">Google</a> and <a href="https://MaterialDesignIcons.com" target="_blank">MaterialDesignIcons.com</a>.</p></div><p class="error-log-intro">The following errors have been logged this session:<paper-icon-button icon="mdi:refresh" on-tap="refreshErrorLog"></paper-icon-button></p><div class="error-log">[[errorLog]]</div></div></app-header-layout></template></dom-module><script>Polymer({is:"ha-panel-dev-info",behaviors:[window.hassBehavior],properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},hassVersion:{type:String,bindNuclear:function(r){return r.configGetters.serverVersion}},hassConfigDir:{type:String,bindNuclear:function(r){return r.configGetters.configDir}},polymerVersion:{type:String,value:Polymer.version},nuclearVersion:{type:String,value:"1.4.0"},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(r){r&&r.preventDefault(),this.errorLog="Loading error log…",this.hass.errorLogActions.fetchErrorLog().then(function(r){this.errorLog=r||"No errors have been reported."}.bind(this))}})</script></body></html>
clear: both;white-space:pre-wrap}</style><app-header-layout has-scrolling-region=""><app-header fixed=""><app-toolbar><ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button><div main-title="">About</div></app-toolbar></app-header><div class="content fit"><div class="about"><p class="version"><a href="https://home-assistant.io"><img src="/static/icons/favicon-192x192.png" height="192"></a><br>Home Assistant<br>[[hass.config.core.version]]</p><p>Path to configuration.yaml: [[hass.config.core.config_dir]]</p><p class="develop"><a href="https://home-assistant.io/developers/credits/" target="_blank">Developed by a bunch of awesome people.</a></p><p>Published under the MIT license<br>Source: <a href="https://github.com/home-assistant/home-assistant" target="_blank">server</a><a href="https://github.com/home-assistant/home-assistant-polymer" target="_blank">frontend-ui</a></p><p>Built using <a href="https://www.python.org">Python 3</a>, <a href="https://www.polymer-project.org" target="_blank">Polymer [[polymerVersion]]</a>, Icons by <a href="https://www.google.com/design/icons/" target="_blank">Google</a> and <a href="https://MaterialDesignIcons.com" target="_blank">MaterialDesignIcons.com</a>.</p></div><p class="error-log-intro">The following errors have been logged this session:<paper-icon-button icon="mdi:refresh" on-tap="refreshErrorLog"></paper-icon-button></p><div class="error-log">[[errorLog]]</div></div></app-header-layout></template></dom-module><script>Polymer({is:"ha-panel-dev-info",properties:{hass:{type:Object},narrow:{type:Boolean,value:!1},showMenu:{type:Boolean,value:!1},polymerVersion:{type:String,value:Polymer.version},errorLog:{type:String,value:""}},attached:function(){this.refreshErrorLog()},refreshErrorLog:function(e){e&&e.preventDefault(),this.errorLog="Loading error log…",this.hass.callApi("GET","error_log").then(function(e){this.errorLog=e||"No errors have been reported."}.bind(this))}})</script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -26,7 +26,7 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START, STATE_UNKNOWN,
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['pyCEC==0.4.12']
REQUIREMENTS = ['pyCEC==0.4.13']
DOMAIN = 'hdmi_cec'

View File

@ -64,7 +64,7 @@ def get_significant_states(start_time, end_time=None, entity_id=None,
"""
entity_ids = (entity_id.lower(), ) if entity_id is not None else None
states = recorder.get_model('States')
query = recorder.query('States').filter(
query = recorder.query(states).filter(
(states.domain.in_(SIGNIFICANT_DOMAINS) |
(states.last_changed == states.last_updated)) &
(states.last_updated > start_time))
@ -221,12 +221,17 @@ class HistoryPeriodView(HomeAssistantView):
if datetime is None:
return self.json_message('Invalid datetime', HTTP_BAD_REQUEST)
now = dt_util.utcnow()
one_day = timedelta(days=1)
if datetime:
start_time = dt_util.as_utc(datetime)
else:
start_time = dt_util.utcnow() - one_day
start_time = now - one_day
if start_time > now:
return self.json([])
end_time = start_time + one_day
entity_id = request.GET.get('filter_entity_id')

View File

@ -4,53 +4,22 @@ Support for the demo image processing.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/demo/
"""
from homeassistant.components.image_processing import ImageProcessingEntity
from homeassistant.components.image_processing import ATTR_CONFIDENCE
from homeassistant.components.image_processing.openalpr_local import (
ImageProcessingAlprEntity)
from homeassistant.components.image_processing.microsoft_face_identify import (
ImageProcessingFaceIdentifyEntity)
ImageProcessingFaceEntity, ATTR_NAME, ATTR_AGE, ATTR_GENDER)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the demo image_processing platform."""
add_devices([
DemoImageProcessing('camera.demo_camera', "Demo"),
DemoImageProcessingAlpr('camera.demo_camera', "Demo Alpr"),
DemoImageProcessingFaceIdentify(
'camera.demo_camera', "Demo Face Identify")
DemoImageProcessingFace(
'camera.demo_camera', "Demo Face")
])
class DemoImageProcessing(ImageProcessingEntity):
"""Demo alpr image processing entity."""
def __init__(self, camera_entity, name):
"""Initialize demo alpr."""
self._name = name
self._camera = camera_entity
self._count = 0
@property
def camera_entity(self):
"""Return camera entity id from process pictures."""
return self._camera
@property
def name(self):
"""Return the name of the entity."""
return self._name
@property
def state(self):
"""Return the state of the entity."""
return self._count
def process_image(self, image):
"""Process image."""
self._count += 1
class DemoImageProcessingAlpr(ImageProcessingAlprEntity):
"""Demo alpr image processing entity."""
@ -88,7 +57,7 @@ class DemoImageProcessingAlpr(ImageProcessingAlprEntity):
self.process_plates(demo_data, 1)
class DemoImageProcessingFaceIdentify(ImageProcessingFaceIdentifyEntity):
class DemoImageProcessingFace(ImageProcessingFaceEntity):
"""Demo face identify image processing entity."""
def __init__(self, camera_entity, name):
@ -115,10 +84,22 @@ class DemoImageProcessingFaceIdentify(ImageProcessingFaceIdentifyEntity):
def process_image(self, image):
"""Process image."""
demo_data = {
'Hans': 98.34,
'Helena': 82.53,
'Luna': 62.53,
}
demo_data = [
{
ATTR_CONFIDENCE: 98.34,
ATTR_NAME: 'Hans',
ATTR_AGE: 16.0,
ATTR_GENDER: 'male',
},
{
ATTR_NAME: 'Helena',
ATTR_AGE: 28.0,
ATTR_GENDER: 'female',
},
{
ATTR_CONFIDENCE: 62.53,
ATTR_NAME: 'Luna',
},
]
self.process_faces(demo_data, 4)

View File

@ -0,0 +1,122 @@
"""
Component that will help set the microsoft face detect processing.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/image_processing.microsoft_face_detect/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import split_entity_id
from homeassistant.exceptions import HomeAssistantError
from homeassistant.components.microsoft_face import DATA_MICROSOFT_FACE
from homeassistant.components.image_processing import (
PLATFORM_SCHEMA, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME)
from homeassistant.components.image_processing.microsoft_face_identify import (
ImageProcessingFaceEntity, ATTR_GENDER, ATTR_AGE, ATTR_GLASSES)
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['microsoft_face']
_LOGGER = logging.getLogger(__name__)
EVENT_IDENTIFY_FACE = 'detect_face'
SUPPORTED_ATTRIBUTES = [
ATTR_AGE,
ATTR_GENDER,
ATTR_GLASSES
]
CONF_ATTRIBUTES = 'attributes'
DEFAULT_ATTRIBUTES = [ATTR_AGE, ATTR_GENDER]
def validate_attributes(list_attributes):
"""Validate face attributes."""
for attr in list_attributes:
if attr not in SUPPORTED_ATTRIBUTES:
raise vol.Invalid("Invalid attribtue {0}".format(attr))
return list_attributes
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ATTRIBUTES, default=DEFAULT_ATTRIBUTES):
vol.All(cv.ensure_list, validate_attributes),
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the microsoft face detection platform."""
api = hass.data[DATA_MICROSOFT_FACE]
attributes = config[CONF_ATTRIBUTES]
entities = []
for camera in config[CONF_SOURCE]:
entities.append(MicrosoftFaceDetectEntity(
camera[CONF_ENTITY_ID], api, attributes, camera.get(CONF_NAME)
))
yield from async_add_devices(entities)
class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity):
"""Microsoft face api entity for identify."""
def __init__(self, camera_entity, api, attributes, name=None):
"""Initialize openalpr local api."""
super().__init__()
self._api = api
self._camera = camera_entity
self._attributes = attributes
if name:
self._name = name
else:
self._name = "MicrosoftFace {0}".format(
split_entity_id(camera_entity)[1])
@property
def camera_entity(self):
"""Return camera entity id from process pictures."""
return self._camera
@property
def name(self):
"""Return the name of the entity."""
return self._name
@asyncio.coroutine
def async_process_image(self, image):
"""Process image.
This method is a coroutine.
"""
face_data = None
try:
face_data = yield from self._api.call_api(
'post', 'detect', image, binary=True,
params={'returnFaceAttributes': ",".join(self._attributes)})
except HomeAssistantError as err:
_LOGGER.error("Can't process image on microsoft face: %s", err)
return
if face_data is None or len(face_data) < 1:
return
faces = []
for face in face_data:
face_attr = {}
for attr in self._attributes:
if attr in face['faceAttributes']:
face_attr[attr] = face['faceAttributes'][attr]
if face_attr:
faces.append(face_attr)
self.async_process_faces(faces, len(face_data))

View File

@ -23,11 +23,16 @@ DEPENDENCIES = ['microsoft_face']
_LOGGER = logging.getLogger(__name__)
EVENT_IDENTIFY_FACE = 'identify_face'
EVENT_DETECT_FACE = 'image_processing.detect_face'
ATTR_NAME = 'name'
ATTR_TOTAL_FACES = 'total_faces'
ATTR_KNOWN_FACES = 'known_faces'
ATTR_AGE = 'age'
ATTR_GENDER = 'gender'
ATTR_MOTION = 'motion'
ATTR_GLASSES = 'glasses'
ATTR_FACES = 'faces'
CONF_GROUP = 'group'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -52,71 +57,90 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
yield from async_add_devices(entities)
class ImageProcessingFaceIdentifyEntity(ImageProcessingEntity):
"""Base entity class for face identify/verify image processing."""
class ImageProcessingFaceEntity(ImageProcessingEntity):
"""Base entity class for face image processing."""
def __init__(self):
"""Initialize base face identify/verify entity."""
self.known_faces = {} # last scan data
self.faces = [] # last scan data
self.total_faces = 0 # face count
@property
def state(self):
"""Return the state of the entity."""
confidence = 0
face_name = STATE_UNKNOWN
state = STATE_UNKNOWN
# search high verify face
for i_name, i_co in self.known_faces.items():
if i_co > confidence:
confidence = i_co
face_name = i_name
return face_name
# no confidence support
if not self.confidence:
return self.total_faces
# search high confidence
for face in self.faces:
if ATTR_CONFIDENCE not in face:
continue
f_co = face[ATTR_CONFIDENCE]
if f_co > confidence:
confidence = f_co
for attr in [ATTR_NAME, ATTR_MOTION]:
if attr in face:
state = face[attr]
break
return state
@property
def state_attributes(self):
"""Return device specific state attributes."""
attr = {
ATTR_KNOWN_FACES: self.known_faces,
ATTR_FACES: self.faces,
ATTR_TOTAL_FACES: self.total_faces,
}
return attr
def process_faces(self, known, total):
def process_faces(self, faces, total):
"""Send event with detected faces and store data."""
run_callback_threadsafe(
self.hass.loop, self.async_process_faces, known, total
self.hass.loop, self.async_process_faces, faces, total
).result()
@callback
def async_process_faces(self, known, total):
def async_process_faces(self, faces, total):
"""Send event with detected faces and store data.
known are a dict in follow format:
{ 'name': confidence }
[
{
ATTR_CONFIDENCE: 80,
ATTR_NAME: 'Name',
ATTR_AGE: 12.0,
ATTR_GENDER: 'man',
ATTR_MOTION: 'smile',
ATTR_GLASSES: 'sunglasses'
},
]
This method must be run in the event loop.
"""
detect = {name: confidence for name, confidence in known.items()
if confidence >= self.confidence}
# send events
for name, confidence in detect.items():
for face in faces:
if ATTR_CONFIDENCE in face and self.confidence:
if face[ATTR_CONFIDENCE] < self.confidence:
continue
face.update({ATTR_ENTITY_ID: self.entity_id})
self.hass.async_add_job(
self.hass.bus.async_fire, EVENT_IDENTIFY_FACE, {
ATTR_NAME: name,
ATTR_ENTITY_ID: self.entity_id,
ATTR_CONFIDENCE: confidence,
}
self.hass.bus.async_fire, EVENT_DETECT_FACE, face
)
# update entity store
self.known_faces = detect
self.faces = faces
self.total_faces = total
class MicrosoftFaceIdentifyEntity(ImageProcessingFaceIdentifyEntity):
class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity):
"""Microsoft face api entity for identify."""
def __init__(self, camera_entity, api, face_group, confidence, name=None):
@ -173,7 +197,7 @@ class MicrosoftFaceIdentifyEntity(ImageProcessingFaceIdentifyEntity):
return
# parse data
knwon_faces = {}
knwon_faces = []
total = 0
for face in detect:
total += 1
@ -187,7 +211,10 @@ class MicrosoftFaceIdentifyEntity(ImageProcessingFaceIdentifyEntity):
name = s_name
break
knwon_faces[name] = data['confidence'] * 100
knwon_faces.append({
ATTR_NAME: name,
ATTR_CONFIDENCE: data['confidence'] * 100,
})
# process data
self.async_process_faces(knwon_faces, total)

View File

@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
RE_ALPR_PLATE = re.compile(r"^plate\d*:")
RE_ALPR_RESULT = re.compile(r"- (\w*)\s*confidence: (\d*.\d*)")
EVENT_FOUND_PLATE = 'found_plate'
EVENT_FOUND_PLATE = 'image_processing.found_plate'
ATTR_PLATE = 'plate'
ATTR_PLATES = 'plates'

View File

@ -45,6 +45,15 @@ SERVICE_SELECT_PREVIOUS_SCHEMA = vol.Schema({
})
SERVICE_SET_OPTIONS = 'set_options'
SERVICE_SET_OPTIONS_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_OPTIONS):
vol.All(cv.ensure_list, vol.Length(min=1), [cv.string]),
})
def _cv_input_select(cfg):
"""Config validation helper for input select (Voluptuous)."""
options = cfg[CONF_OPTIONS]
@ -89,6 +98,14 @@ def select_previous(hass, entity_id):
})
def set_options(hass, entity_id, options):
"""Set options of input_select."""
hass.services.call(DOMAIN, SERVICE_SET_OPTIONS, {
ATTR_ENTITY_ID: entity_id,
ATTR_OPTIONS: options,
})
@asyncio.coroutine
def async_setup(hass, config):
"""Setup input select."""
@ -148,6 +165,20 @@ def async_setup(hass, config):
DOMAIN, SERVICE_SELECT_PREVIOUS, async_select_previous_service,
schema=SERVICE_SELECT_PREVIOUS_SCHEMA)
@asyncio.coroutine
def async_set_options_service(call):
"""Handle a calls to the set options service."""
target_inputs = component.async_extract_from_service(call)
tasks = [input_select.async_set_options(call.data[ATTR_OPTIONS])
for input_select in target_inputs]
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_OPTIONS, async_set_options_service,
schema=SERVICE_SET_OPTIONS_SCHEMA)
yield from component.async_add_entities(entities)
return True
@ -207,3 +238,10 @@ class InputSelect(Entity):
new_index = (current_index + offset) % len(self._options)
self._current_option = self._options[new_index]
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_set_options(self, options):
"""Set options."""
self._current_option = options[0]
self._options = options
yield from self.async_update_ha_state()

View File

@ -35,7 +35,6 @@ ENTITY_ID_ALL_LIGHTS = group.ENTITY_ID_FORMAT.format('all_lights')
ENTITY_ID_FORMAT = DOMAIN + ".{}"
# Bitfield of features supported by the light entity
ATTR_SUPPORTED_FEATURES = 'supported_features'
SUPPORT_BRIGHTNESS = 1
SUPPORT_COLOR_TEMP = 2
SUPPORT_EFFECT = 4
@ -85,7 +84,6 @@ PROP_TO_ATTR = {
'white_value': ATTR_WHITE_VALUE,
'effect_list': ATTR_EFFECT_LIST,
'effect': ATTR_EFFECT,
'supported_features': ATTR_SUPPORTED_FEATURES,
}
# Service call validation schemas
@ -364,8 +362,6 @@ class Light(ToggleEntity):
data[ATTR_RGB_COLOR] = color_util.color_xy_brightness_to_RGB(
data[ATTR_XY_COLOR][0], data[ATTR_XY_COLOR][1],
data[ATTR_BRIGHTNESS])
else:
data[ATTR_SUPPORTED_FEATURES] = self.supported_features
return data

View File

@ -28,19 +28,21 @@ SUPPORT_DEMO = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT |
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the demo light platform."""
add_devices_callback([
DemoLight("Bed Light", False, effect_list=LIGHT_EFFECT_LIST,
DemoLight("Bed Light", False, True, effect_list=LIGHT_EFFECT_LIST,
effect=LIGHT_EFFECT_LIST[0]),
DemoLight("Ceiling Lights", True, LIGHT_COLORS[0], LIGHT_TEMPS[1]),
DemoLight("Kitchen Lights", True, LIGHT_COLORS[1], LIGHT_TEMPS[0])
DemoLight("Ceiling Lights", True, True,
LIGHT_COLORS[0], LIGHT_TEMPS[1]),
DemoLight("Kitchen Lights", True, True,
LIGHT_COLORS[1], LIGHT_TEMPS[0])
])
class DemoLight(Light):
"""Represenation of a demo light."""
"""Representation of a demo light."""
def __init__(
self, name, state, rgb=None, ct=None, brightness=180,
xy_color=(.5, .5), white=200, effect_list=None, effect=None):
def __init__(self, name, state, available=False, rgb=None, ct=None,
brightness=180, xy_color=(.5, .5), white=200,
effect_list=None, effect=None):
"""Initialize the light."""
self._name = name
self._state = state
@ -53,61 +55,68 @@ class DemoLight(Light):
self._effect = effect
@property
def should_poll(self):
def should_poll(self) -> bool:
"""No polling needed for a demo light."""
return False
@property
def name(self):
def name(self) -> str:
"""Return the name of the light if any."""
return self._name
@property
def brightness(self):
def available(self) -> bool:
"""Return availability."""
# This demo light is always available, but well-behaving components
# should implement this to inform Home Assistant accordingly.
return True
@property
def brightness(self) -> int:
"""Return the brightness of this light between 0..255."""
return self._brightness
@property
def xy_color(self):
def xy_color(self) -> tuple:
"""Return the XY color value [float, float]."""
return self._xy_color
@property
def rgb_color(self):
def rgb_color(self) -> tuple:
"""Return the RBG color value."""
return self._rgb
@property
def color_temp(self):
def color_temp(self) -> int:
"""Return the CT color temperature."""
return self._ct
@property
def white_value(self):
def white_value(self) -> int:
"""Return the white value of this light between 0..255."""
return self._white
@property
def effect_list(self):
def effect_list(self) -> list:
"""Return the list of supported effects."""
return self._effect_list
@property
def effect(self):
def effect(self) -> str:
"""Return the current effect."""
return self._effect
@property
def is_on(self):
def is_on(self) -> bool:
"""Return true if light is on."""
return self._state
@property
def supported_features(self):
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_DEMO
def turn_on(self, **kwargs):
def turn_on(self, **kwargs) -> None:
"""Turn the light on."""
self._state = True
@ -129,9 +138,14 @@ class DemoLight(Light):
if ATTR_EFFECT in kwargs:
self._effect = kwargs[ATTR_EFFECT]
# As we have disabled polling, we need to inform
# Home Assistant about updates in our state ourselves.
self.schedule_update_ha_state()
def turn_off(self, **kwargs):
def turn_off(self, **kwargs) -> None:
"""Turn the light off."""
self._state = False
# As we have disabled polling, we need to inform
# Home Assistant about updates in our state ourselves.
self.schedule_update_ha_state()

View File

@ -17,7 +17,7 @@ from homeassistant.components.light import (
PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['flux_led==0.12']
REQUIREMENTS = ['flux_led==0.13']
_LOGGER = logging.getLogger(__name__)
@ -29,10 +29,13 @@ DOMAIN = 'flux_led'
SUPPORT_FLUX_LED = (SUPPORT_BRIGHTNESS | SUPPORT_EFFECT |
SUPPORT_RGB_COLOR)
MODE_RGB = 'rgb'
MODE_RGBW = 'rgbw'
DEVICE_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(ATTR_MODE, default='rgbw'):
vol.All(cv.string, vol.In(['rgbw', 'rgb'])),
vol.Optional(ATTR_MODE, default=MODE_RGBW):
vol.All(cv.string, vol.In([MODE_RGBW, MODE_RGB])),
vol.Optional(CONF_PROTOCOL, default=None):
vol.All(cv.string, vol.In(['ledenet'])),
})
@ -48,7 +51,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
import flux_led
lights = []
light_ips = []
for ipaddr, device_config in config[CONF_DEVICES].items():
for ipaddr, device_config in config.get(CONF_DEVICES, {}).items():
device = {}
device['name'] = device_config[CONF_NAME]
device['ipaddr'] = ipaddr
@ -59,7 +63,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
lights.append(light)
light_ips.append(ipaddr)
if not config[CONF_AUTOMATIC_ADD]:
if discovery_info:
device = {}
# discovery_info: ip address,device id,device type
device['ipaddr'] = discovery_info[0]
device['name'] = discovery_info[1]
# As we don't know protocol and mode set to none to autodetect.
device[CONF_PROTOCOL] = None
device[ATTR_MODE] = None
light = FluxLight(device)
if light.is_valid:
lights.append(light)
light_ips.append(device['ipaddr'])
if not config.get(CONF_AUTOMATIC_ADD, False):
add_devices(lights)
return
@ -94,10 +112,20 @@ class FluxLight(Light):
self._mode = device[ATTR_MODE]
self.is_valid = True
self._bulb = None
try:
self._bulb = flux_led.WifiLedBulb(self._ipaddr)
if self._protocol:
self._bulb.setProtocol(self._protocol)
# After bulb object is created the status is updated. We can
# now set the correct mode if it was not explicitly defined.
if not self._mode:
if self._bulb.rgbwcapable:
self._mode = MODE_RGBW
else:
self._mode = MODE_RGB
except socket.error:
self.is_valid = False
_LOGGER.error(
@ -121,7 +149,7 @@ class FluxLight(Light):
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
return self._bulb.getWarmWhite255()
return self._bulb.brightness
@property
def rgb_color(self):

View File

@ -200,8 +200,7 @@ def setup_bridge(host, hass, add_devices, filename, allow_unreachable,
for light_id, info in api_lights.items():
if light_id not in lights:
lights[light_id] = HueLight(hass,
int(light_id), info,
lights[light_id] = HueLight(int(light_id), info,
bridge, update_lights,
bridge_type, allow_unreachable,
allow_in_emulated_hue)
@ -219,7 +218,6 @@ def setup_bridge(host, hass, add_devices, filename, allow_unreachable,
if lightgroup_id not in lightgroups:
lightgroups[lightgroup_id] = HueLight(
hass,
int(lightgroup_id), info, bridge, update_lights,
bridge_type, allow_unreachable, allow_in_emulated_hue,
True)
@ -282,11 +280,10 @@ def request_configuration(host, hass, add_devices, filename,
class HueLight(Light):
"""Representation of a Hue light."""
def __init__(self, hass, light_id, info, bridge, update_lights,
def __init__(self, light_id, info, bridge, update_lights,
bridge_type, allow_unreachable, allow_in_emulated_hue,
is_group=False):
"""Initialize the light."""
self.hass = hass
self.light_id = light_id
self.info = info
self.bridge = bridge
@ -304,8 +301,14 @@ class HueLight(Light):
@property
def unique_id(self):
"""Return the ID of this Hue light."""
return "{}.{}".format(
self.__class__, self.info.get('uniqueid', self.name))
lid = self.info.get('uniqueid')
if lid is None:
default_type = 'Group' if self.is_group else 'Light'
ltype = self.info.get('type', default_type)
lid = '{}.{}.{}'.format(self.name, ltype, self.light_id)
return '{}.{}'.format(self.__class__, lid)
@property
def name(self):

View File

@ -28,7 +28,9 @@ SUPPORT_HYPERION = SUPPORT_RGB_COLOR
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_DEFAULT_COLOR, default=DEFAULT_COLOR): cv.string,
vol.Optional(CONF_DEFAULT_COLOR, default=DEFAULT_COLOR):
vol.All(list, vol.Length(min=3, max=3),
[vol.All(vol.Coerce(int), vol.Range(min=0, max=255))]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})

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