Merge pull request #3486 from home-assistant/dev

0.29 - 🎈 anniversary edition
This commit is contained in:
Paulus Schoutsen 2016-09-28 20:59:50 -07:00 committed by GitHub
commit c4d817146f
357 changed files with 11185 additions and 5050 deletions

View File

@ -93,19 +93,18 @@ omit =
homeassistant/components/*/pilight.py homeassistant/components/*/pilight.py
homeassistant/components/knx.py homeassistant/components/knx.py
homeassistant/components/switch/knx.py homeassistant/components/*/knx.py
homeassistant/components/binary_sensor/knx.py
homeassistant/components/thermostat/knx.py homeassistant/components/ffmpeg.py
homeassistant/components/*/ffmpeg.py
homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/nx584.py homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py homeassistant/components/alarm_control_panel/simplisafe.py
homeassistant/components/binary_sensor/arest.py homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/ffmpeg.py
homeassistant/components/binary_sensor/rest.py homeassistant/components/binary_sensor/rest.py
homeassistant/components/browser.py homeassistant/components/browser.py
homeassistant/components/camera/bloomsky.py homeassistant/components/camera/bloomsky.py
homeassistant/components/camera/ffmpeg.py
homeassistant/components/camera/foscam.py homeassistant/components/camera/foscam.py
homeassistant/components/camera/mjpeg.py homeassistant/components/camera/mjpeg.py
homeassistant/components/camera/rpi_camera.py homeassistant/components/camera/rpi_camera.py
@ -147,6 +146,7 @@ omit =
homeassistant/components/ifttt.py homeassistant/components/ifttt.py
homeassistant/components/joaoapps_join.py homeassistant/components/joaoapps_join.py
homeassistant/components/keyboard.py homeassistant/components/keyboard.py
homeassistant/components/keyboard_remote.py
homeassistant/components/light/blinksticklight.py homeassistant/components/light/blinksticklight.py
homeassistant/components/light/flux_led.py homeassistant/components/light/flux_led.py
homeassistant/components/light/hue.py homeassistant/components/light/hue.py
@ -188,6 +188,7 @@ omit =
homeassistant/components/notify/group.py homeassistant/components/notify/group.py
homeassistant/components/notify/instapush.py homeassistant/components/notify/instapush.py
homeassistant/components/notify/joaoapps_join.py homeassistant/components/notify/joaoapps_join.py
homeassistant/components/notify/kodi.py
homeassistant/components/notify/llamalab_automate.py homeassistant/components/notify/llamalab_automate.py
homeassistant/components/notify/message_bird.py homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nma.py homeassistant/components/notify/nma.py
@ -196,6 +197,7 @@ omit =
homeassistant/components/notify/pushover.py homeassistant/components/notify/pushover.py
homeassistant/components/notify/rest.py homeassistant/components/notify/rest.py
homeassistant/components/notify/sendgrid.py homeassistant/components/notify/sendgrid.py
homeassistant/components/notify/simplepush.py
homeassistant/components/notify/slack.py homeassistant/components/notify/slack.py
homeassistant/components/notify/smtp.py homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py homeassistant/components/notify/syslog.py
@ -203,9 +205,12 @@ omit =
homeassistant/components/notify/twilio_sms.py homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twitter.py homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py homeassistant/components/notify/xmpp.py
homeassistant/components/nuimo_controller.py
homeassistant/components/openalpr.py
homeassistant/components/scene/hunterdouglas_powerview.py homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/sensor/arest.py homeassistant/components/sensor/arest.py
homeassistant/components/sensor/bitcoin.py homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/coinmarketcap.py homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/cpuspeed.py homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/deutsche_bahn.py homeassistant/components/sensor/deutsche_bahn.py
@ -213,6 +218,7 @@ omit =
homeassistant/components/sensor/dte_energy_bridge.py homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/efergy.py homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/eliqonline.py homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/emoncms.py
homeassistant/components/sensor/fastdotcom.py homeassistant/components/sensor/fastdotcom.py
homeassistant/components/sensor/fitbit.py homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py homeassistant/components/sensor/fixer.py
@ -224,10 +230,12 @@ omit =
homeassistant/components/sensor/gtfs.py homeassistant/components/sensor/gtfs.py
homeassistant/components/sensor/hp_ilo.py homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/imap.py homeassistant/components/sensor/imap.py
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/lastfm.py homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/linux_battery.py homeassistant/components/sensor/linux_battery.py
homeassistant/components/sensor/loopenergy.py homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/mhz19.py homeassistant/components/sensor/mhz19.py
homeassistant/components/sensor/miflora.py
homeassistant/components/sensor/mqtt_room.py homeassistant/components/sensor/mqtt_room.py
homeassistant/components/sensor/neurio_energy.py homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nzbget.py homeassistant/components/sensor/nzbget.py
@ -255,6 +263,7 @@ omit =
homeassistant/components/sensor/uber.py homeassistant/components/sensor/uber.py
homeassistant/components/sensor/worldclock.py homeassistant/components/sensor/worldclock.py
homeassistant/components/sensor/xbox_live.py homeassistant/components/sensor/xbox_live.py
homeassistant/components/sensor/yahoo_finance.py
homeassistant/components/sensor/yweather.py homeassistant/components/sensor/yweather.py
homeassistant/components/switch/acer_projector.py homeassistant/components/switch/acer_projector.py
homeassistant/components/switch/arest.py homeassistant/components/switch/arest.py

3
.gitignore vendored
View File

@ -100,3 +100,6 @@ virtualization/vagrant/config
# Built docs # Built docs
docs/build docs/build
# Windows Explorer
desktop.ini

View File

@ -1,4 +1,4 @@
FROM python:3.4 FROM python:3.5
MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
VOLUME /config VOLUME /config
@ -10,7 +10,7 @@ RUN pip3 install --no-cache-dir colorlog cython
# For the nmap tracker, bluetooth tracker, Z-Wave # For the nmap tracker, bluetooth tracker, Z-Wave
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev && \ apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev bluetooth libbluetooth-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY script/build_python_openzwave script/build_python_openzwave COPY script/build_python_openzwave script/build_python_openzwave
@ -21,7 +21,7 @@ RUN script/build_python_openzwave && \
COPY requirements_all.txt requirements_all.txt COPY requirements_all.txt requirements_all.txt
# certifi breaks Debian based installs # certifi breaks Debian based installs
RUN pip3 install --no-cache-dir -r requirements_all.txt && pip3 uninstall -y certifi && \ RUN pip3 install --no-cache-dir -r requirements_all.txt && pip3 uninstall -y certifi && \
pip3 install mysqlclient psycopg2 pip3 install mysqlclient psycopg2 uvloop
# Copy source # Copy source
COPY . . COPY . .

View File

@ -16,16 +16,14 @@ from homeassistant.const import (
REQUIRED_PYTHON_VER, REQUIRED_PYTHON_VER,
RESTART_EXIT_CODE, RESTART_EXIT_CODE,
) )
from homeassistant.util.async import run_callback_threadsafe
def validate_python() -> None: def validate_python() -> None:
"""Validate we're running the right Python version.""" """Validate we're running the right Python version."""
major, minor = sys.version_info[:2] if sys.version_info[:3] < REQUIRED_PYTHON_VER:
req_major, req_minor = REQUIRED_PYTHON_VER print("Home Assistant requires at least Python {}.{}.{}".format(
*REQUIRED_PYTHON_VER))
if major < req_major or (major == req_major and minor < req_minor):
print("Home Assistant requires at least Python {}.{}".format(
req_major, req_minor))
sys.exit(1) sys.exit(1)
@ -256,12 +254,14 @@ def setup_and_run_hass(config_dir: str,
import webbrowser import webbrowser
webbrowser.open(hass.config.api.base_url) webbrowser.open(hass.config.api.base_url)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser) run_callback_threadsafe(
hass.loop,
hass.bus.async_listen_once,
EVENT_HOMEASSISTANT_START, open_browser
)
hass.start() hass.start()
exit_code = int(hass.block_till_stopped()) return hass.exit_code
return exit_code
def try_to_restart() -> None: def try_to_restart() -> None:

View File

@ -118,11 +118,13 @@ def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool:
# Assumption: if a component does not depend on groups # Assumption: if a component does not depend on groups
# it communicates with devices # it communicates with devices
if 'group' not in getattr(component, 'DEPENDENCIES', []): if 'group' not in getattr(component, 'DEPENDENCIES', []) and \
hass.pool.worker_count <= 10:
hass.pool.add_worker() hass.pool.add_worker()
hass.bus.fire( hass.bus.fire(
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}) EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}
)
return True return True
@ -144,7 +146,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
if hasattr(component, 'CONFIG_SCHEMA'): if hasattr(component, 'CONFIG_SCHEMA'):
try: try:
config = component.CONFIG_SCHEMA(config) config = component.CONFIG_SCHEMA(config)
except vol.MultipleInvalid as ex: except vol.Invalid as ex:
log_exception(ex, domain, config) log_exception(ex, domain, config)
return None return None
@ -154,8 +156,8 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
# Validate component specific platform schema # Validate component specific platform schema
try: try:
p_validated = component.PLATFORM_SCHEMA(p_config) p_validated = component.PLATFORM_SCHEMA(p_config)
except vol.MultipleInvalid as ex: except vol.Invalid as ex:
log_exception(ex, domain, p_config) log_exception(ex, domain, config)
return None return None
# Not all platform components follow same pattern for platforms # Not all platform components follow same pattern for platforms
@ -175,7 +177,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
if hasattr(platform, 'PLATFORM_SCHEMA'): if hasattr(platform, 'PLATFORM_SCHEMA'):
try: try:
p_validated = platform.PLATFORM_SCHEMA(p_validated) p_validated = platform.PLATFORM_SCHEMA(p_validated)
except vol.MultipleInvalid as ex: except vol.Invalid as ex:
log_exception(ex, '{}.{}'.format(domain, p_name), log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated) p_validated)
return None return None
@ -278,6 +280,9 @@ def from_config_dict(config: Dict[str, Any],
components = set(key.split(' ')[0] for key in config.keys() components = set(key.split(' ')[0] for key in config.keys()
if key != core.DOMAIN) if key != core.DOMAIN)
# Setup in a thread to avoid blocking
def component_setup():
"""Set up a component."""
if not core_components.setup(hass, config): if not core_components.setup(hass, config):
_LOGGER.error('Home Assistant core failed to initialize. ' _LOGGER.error('Home Assistant core failed to initialize. '
'Further initialization aborted.') 'Further initialization aborted.')
@ -295,6 +300,9 @@ def from_config_dict(config: Dict[str, Any],
for domain in loader.load_order_components(components): for domain in loader.load_order_components(components):
_setup_component(hass, domain, config) _setup_component(hass, domain, config)
hass.loop.run_until_complete(
hass.loop.run_in_executor(None, component_setup)
)
return hass return hass
@ -390,16 +398,21 @@ def _ensure_loader_prepared(hass: core.HomeAssistant) -> None:
def log_exception(ex, domain, config): def log_exception(ex, domain, config):
"""Generate log exception for config validation.""" """Generate log exception for config validation."""
message = 'Invalid config for [{}]: '.format(domain) message = 'Invalid config for [{}]: '.format(domain)
if 'extra keys not allowed' in ex.error_message: if 'extra keys not allowed' in ex.error_message:
message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\ message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\
.format(ex.path[-1], domain, domain, .format(ex.path[-1], domain, domain,
'->'.join('%s' % m for m in ex.path)) '->'.join('%s' % m for m in ex.path))
else: else:
message += humanize_error(config, ex) message += '{}.'.format(humanize_error(config, ex))
if hasattr(config, '__line__'): if hasattr(config, '__line__'):
message += " (See {}:{})".format(config.__config_file__, message += " (See {}:{})".format(
config.__line__ or '?') config.__config_file__, config.__line__ or '?')
if domain != 'homeassistant':
message += (' Please check the docs at '
'https://home-assistant.io/components/{}/'.format(domain))
_LOGGER.error(message) _LOGGER.error(message)

View File

@ -10,6 +10,7 @@ from homeassistant.components.envisalink import (EVL_CONTROLLER,
EnvisalinkDevice, EnvisalinkDevice,
PARTITION_SCHEMA, PARTITION_SCHEMA,
CONF_CODE, CONF_CODE,
CONF_PANIC,
CONF_PARTITIONNAME, CONF_PARTITIONNAME,
SIGNAL_PARTITION_UPDATE, SIGNAL_PARTITION_UPDATE,
SIGNAL_KEYPAD_UPDATE) SIGNAL_KEYPAD_UPDATE)
@ -26,6 +27,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Perform the setup for Envisalink alarm panels.""" """Perform the setup for Envisalink alarm panels."""
_configured_partitions = discovery_info['partitions'] _configured_partitions = discovery_info['partitions']
_code = discovery_info[CONF_CODE] _code = discovery_info[CONF_CODE]
_panic_type = discovery_info[CONF_PANIC]
for part_num in _configured_partitions: for part_num in _configured_partitions:
_device_config_data = PARTITION_SCHEMA( _device_config_data = PARTITION_SCHEMA(
_configured_partitions[part_num]) _configured_partitions[part_num])
@ -33,6 +35,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
part_num, part_num,
_device_config_data[CONF_PARTITIONNAME], _device_config_data[CONF_PARTITIONNAME],
_code, _code,
_panic_type,
EVL_CONTROLLER.alarm_state['partition'][part_num], EVL_CONTROLLER.alarm_state['partition'][part_num],
EVL_CONTROLLER) EVL_CONTROLLER)
add_devices_callback([_device]) add_devices_callback([_device])
@ -44,11 +47,13 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
"""Represents the Envisalink-based alarm panel.""" """Represents the Envisalink-based alarm panel."""
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, partition_number, alarm_name, code, info, controller): def __init__(self, partition_number, alarm_name,
code, panic_type, info, controller):
"""Initialize the alarm panel.""" """Initialize the alarm panel."""
from pydispatch import dispatcher from pydispatch import dispatcher
self._partition_number = partition_number self._partition_number = partition_number
self._code = code self._code = code
self._panic_type = panic_type
_LOGGER.debug('Setting up alarm: ' + alarm_name) _LOGGER.debug('Setting up alarm: ' + alarm_name)
EnvisalinkDevice.__init__(self, alarm_name, info, controller) EnvisalinkDevice.__init__(self, alarm_name, info, controller)
dispatcher.connect(self._update_callback, dispatcher.connect(self._update_callback,
@ -61,7 +66,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
def _update_callback(self, partition): def _update_callback(self, partition):
"""Update HA state, if needed.""" """Update HA state, if needed."""
if partition is None or int(partition) == self._partition_number: if partition is None or int(partition) == self._partition_number:
self.update_ha_state() self.hass.async_add_job(self.update_ha_state)
@property @property
def code_format(self): def code_format(self):
@ -101,5 +106,6 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
self._partition_number) self._partition_number)
def alarm_trigger(self, code=None): def alarm_trigger(self, code=None):
"""Alarm trigger command. Not possible for us.""" """Alarm trigger command. Will be used to trigger a panic alarm."""
raise NotImplementedError() if self._code:
EVL_CONTROLLER.panic_alarm(self._panic_type)

View File

@ -28,7 +28,7 @@ PLATFORM_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string,
vol.Optional(CONF_CODE): cv.string, vol.Optional(CONF_CODE): cv.string,
vol.Optional(CONF_PENDING_TIME, default=DEFAULT_PENDING_TIME): vol.Optional(CONF_PENDING_TIME, default=DEFAULT_PENDING_TIME):
vol.All(vol.Coerce(int), vol.Range(min=1)), vol.All(vol.Coerce(int), vol.Range(min=0)),
vol.Optional(CONF_TRIGGER_TIME, default=DEFAULT_TRIGGER_TIME): vol.Optional(CONF_TRIGGER_TIME, default=DEFAULT_TRIGGER_TIME):
vol.All(vol.Coerce(int), vol.Range(min=1)), vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_DISARM_AFTER_TRIGGER, vol.Optional(CONF_DISARM_AFTER_TRIGGER,

View File

@ -4,11 +4,14 @@ Support for Alexa skill service end point.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alexa/ https://home-assistant.io/components/alexa/
""" """
import copy
import enum import enum
import logging import logging
import voluptuous as vol
from homeassistant.const import HTTP_BAD_REQUEST from homeassistant.const import HTTP_BAD_REQUEST
from homeassistant.helpers import template, script from homeassistant.helpers import template, script, config_validation as cv
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -20,10 +23,49 @@ CONF_CARD = 'card'
CONF_INTENTS = 'intents' CONF_INTENTS = 'intents'
CONF_SPEECH = 'speech' CONF_SPEECH = 'speech'
CONF_TYPE = 'type'
CONF_TITLE = 'title'
CONF_CONTENT = 'content'
CONF_TEXT = 'text'
DOMAIN = 'alexa' DOMAIN = 'alexa'
DEPENDENCIES = ['http'] DEPENDENCIES = ['http']
class SpeechType(enum.Enum):
"""The Alexa speech types."""
plaintext = "PlainText"
ssml = "SSML"
class CardType(enum.Enum):
"""The Alexa card types."""
simple = "Simple"
link_account = "LinkAccount"
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
CONF_INTENTS: {
cv.string: {
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_CARD): {
vol.Required(CONF_TYPE): cv.enum(CardType),
vol.Required(CONF_TITLE): cv.template,
vol.Required(CONF_CONTENT): cv.template,
},
vol.Optional(CONF_SPEECH): {
vol.Required(CONF_TYPE): cv.enum(SpeechType),
vol.Required(CONF_TEXT): cv.template,
}
}
}
}
})
def setup(hass, config): def setup(hass, config):
"""Activate Alexa component.""" """Activate Alexa component."""
hass.wsgi.register_view(AlexaView(hass, hass.wsgi.register_view(AlexaView(hass,
@ -42,6 +84,9 @@ class AlexaView(HomeAssistantView):
"""Initialize Alexa view.""" """Initialize Alexa view."""
super().__init__(hass) super().__init__(hass)
intents = copy.deepcopy(intents)
template.attach(hass, intents)
for name, intent in intents.items(): for name, intent in intents.items():
if CONF_ACTION in intent: if CONF_ACTION in intent:
intent[CONF_ACTION] = script.Script( intent[CONF_ACTION] = script.Script(
@ -101,29 +146,15 @@ class AlexaView(HomeAssistantView):
# pylint: disable=unsubscriptable-object # pylint: disable=unsubscriptable-object
if speech is not None: if speech is not None:
response.add_speech(SpeechType[speech['type']], speech['text']) response.add_speech(speech[CONF_TYPE], speech[CONF_TEXT])
if card is not None: if card is not None:
response.add_card(CardType[card['type']], card['title'], response.add_card(card[CONF_TYPE], card[CONF_TITLE],
card['content']) card[CONF_CONTENT])
return self.json(response) return self.json(response)
class SpeechType(enum.Enum):
"""The Alexa speech types."""
plaintext = "PlainText"
ssml = "SSML"
class CardType(enum.Enum):
"""The Alexa card types."""
simple = "Simple"
link_account = "LinkAccount"
class AlexaResponse(object): class AlexaResponse(object):
"""Help generating the response for Alexa.""" """Help generating the response for Alexa."""
@ -153,8 +184,8 @@ class AlexaResponse(object):
self.card = card self.card = card
return return
card["title"] = self._render(title), card["title"] = title.render(self.variables)
card["content"] = self._render(content) card["content"] = content.render(self.variables)
self.card = card self.card = card
def add_speech(self, speech_type, text): def add_speech(self, speech_type, text):
@ -163,9 +194,12 @@ class AlexaResponse(object):
key = 'ssml' if speech_type == SpeechType.ssml else 'text' key = 'ssml' if speech_type == SpeechType.ssml else 'text'
if isinstance(text, template.Template):
text = text.render(self.variables)
self.speech = { self.speech = {
'type': speech_type.value, 'type': speech_type.value,
key: self._render(text) key: text
} }
def add_reprompt(self, speech_type, text): def add_reprompt(self, speech_type, text):
@ -176,7 +210,7 @@ class AlexaResponse(object):
self.reprompt = { self.reprompt = {
'type': speech_type.value, 'type': speech_type.value,
key: self._render(text) key: text.render(self.variables)
} }
def as_dict(self): def as_dict(self):
@ -201,7 +235,3 @@ class AlexaResponse(object):
'sessionAttributes': self.session_attributes, 'sessionAttributes': self.session_attributes,
'response': response, 'response': response,
} }
def _render(self, template_string):
"""Render a response, adding data from intent if available."""
return template.render(self.hass, template_string, self.variables)

View File

@ -4,6 +4,7 @@ Rest API for Home Assistant.
For more details about the RESTful API, please refer to the documentation at For more details about the RESTful API, please refer to the documentation at
https://home-assistant.io/developers/api/ https://home-assistant.io/developers/api/
""" """
import asyncio
import json import json
import logging import logging
import queue import queue
@ -79,6 +80,7 @@ class APIEventStream(HomeAssistantView):
if restrict: if restrict:
restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP] restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP]
@asyncio.coroutine
def forward_events(event): def forward_events(event):
"""Forward events to the open request.""" """Forward events to the open request."""
if event.event_type == EVENT_TIME_CHANGED: if event.event_type == EVENT_TIME_CHANGED:
@ -376,8 +378,8 @@ class APITemplateView(HomeAssistantView):
def post(self, request): def post(self, request):
"""Render a template.""" """Render a template."""
try: try:
return template.render(self.hass, request.json['template'], tpl = template.Template(request.json['template'], self.hass)
request.json.get('variables')) return tpl.render(request.json.get('variables'))
except TemplateError as ex: except TemplateError as ex:
return self.json_message('Error rendering template: {}'.format(ex), return self.json_message('Error rendering template: {}'.format(ex),
HTTP_BAD_REQUEST) HTTP_BAD_REQUEST)

View File

@ -13,7 +13,7 @@ from homeassistant.const import (
from homeassistant.const import CONF_PORT from homeassistant.const import CONF_PORT
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['PyMata==2.12'] REQUIREMENTS = ['PyMata==2.13']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -30,6 +30,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
DEPENDENCIES = ['group'] DEPENDENCIES = ['group']
CONF_ALIAS = 'alias' CONF_ALIAS = 'alias'
CONF_HIDE_ENTITY = 'hide_entity'
CONF_CONDITION = 'condition' CONF_CONDITION = 'condition'
CONF_ACTION = 'action' CONF_ACTION = 'action'
@ -41,6 +42,7 @@ CONDITION_TYPE_AND = 'and'
CONDITION_TYPE_OR = 'or' CONDITION_TYPE_OR = 'or'
DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
DEFAULT_HIDE_ENTITY = False
METHOD_TRIGGER = 'trigger' METHOD_TRIGGER = 'trigger'
METHOD_IF_ACTION = 'if_action' METHOD_IF_ACTION = 'if_action'
@ -99,6 +101,7 @@ _CONDITION_SCHEMA = vol.Any(
PLATFORM_SCHEMA = vol.Schema({ PLATFORM_SCHEMA = vol.Schema({
CONF_ALIAS: cv.string, CONF_ALIAS: cv.string,
vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA, vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
vol.Required(CONF_CONDITION_TYPE, default=DEFAULT_CONDITION_TYPE): vol.Required(CONF_CONDITION_TYPE, default=DEFAULT_CONDITION_TYPE):
vol.All(vol.Lower, vol.Any(CONDITION_TYPE_AND, CONDITION_TYPE_OR)), vol.All(vol.Lower, vol.Any(CONDITION_TYPE_AND, CONDITION_TYPE_OR)),
@ -206,7 +209,8 @@ def setup(hass, config):
class AutomationEntity(ToggleEntity): class AutomationEntity(ToggleEntity):
"""Entity to show status of entity.""" """Entity to show status of entity."""
def __init__(self, name, attach_triggers, cond_func, action): # pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, name, attach_triggers, cond_func, action, hidden):
"""Initialize an automation entity.""" """Initialize an automation entity."""
self._name = name self._name = name
self._attach_triggers = attach_triggers self._attach_triggers = attach_triggers
@ -215,6 +219,7 @@ class AutomationEntity(ToggleEntity):
self._action = action self._action = action
self._enabled = True self._enabled = True
self._last_triggered = None self._last_triggered = None
self._hidden = hidden
@property @property
def name(self): def name(self):
@ -233,6 +238,11 @@ class AutomationEntity(ToggleEntity):
ATTR_LAST_TRIGGERED: self._last_triggered ATTR_LAST_TRIGGERED: self._last_triggered
} }
@property
def hidden(self) -> bool:
"""Return True if the automation entity should be hidden from UIs."""
return self._hidden
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
"""Return True if entity is on.""" """Return True if entity is on."""
@ -281,6 +291,8 @@ def _process_config(hass, config, component):
name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key, name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key,
list_no) list_no)
hidden = config_block[CONF_HIDE_ENTITY]
action = _get_action(hass, config_block.get(CONF_ACTION, {}), name) action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)
if CONF_CONDITION in config_block: if CONF_CONDITION in config_block:
@ -295,7 +307,8 @@ def _process_config(hass, config, component):
attach_triggers = partial(_process_trigger, hass, config, attach_triggers = partial(_process_trigger, hass, config,
config_block.get(CONF_TRIGGER, []), name) config_block.get(CONF_TRIGGER, []), name)
entity = AutomationEntity(name, attach_triggers, cond_func, action) entity = AutomationEntity(name, attach_triggers, cond_func, action,
hidden)
component.add_entities((entity,)) component.add_entities((entity,))
success = True success = True

View File

@ -4,6 +4,7 @@ Offer event listening automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#event-trigger at https://home-assistant.io/components/automation/#event-trigger
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -28,11 +29,12 @@ def trigger(hass, config, action):
event_type = config.get(CONF_EVENT_TYPE) event_type = config.get(CONF_EVENT_TYPE)
event_data = config.get(CONF_EVENT_DATA) event_data = config.get(CONF_EVENT_DATA)
@asyncio.coroutine
def handle_event(event): def handle_event(event):
"""Listen for events and calls the action when data matches.""" """Listen for events and calls the action when data matches."""
if not event_data or all(val == event.data.get(key) for key, val if not event_data or all(val == event.data.get(key) for key, val
in event_data.items()): in event_data.items()):
action({ hass.async_add_job(action, {
'trigger': { 'trigger': {
'platform': 'event', 'platform': 'event',
'event': event, 'event': event,

View File

@ -31,6 +31,8 @@ def trigger(hass, config, action):
below = config.get(CONF_BELOW) below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE) above = config.get(CONF_ABOVE)
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
# pylint: disable=unused-argument # pylint: disable=unused-argument
def state_automation_listener(entity, from_s, to_s): def state_automation_listener(entity, from_s, to_s):

View File

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

View File

@ -9,18 +9,15 @@ from datetime import timedelta
import requests import requests
from homeassistant.components.binary_sensor import (BinarySensorDevice, from homeassistant.components.binary_sensor import (
SENSOR_CLASSES) BinarySensorDevice, SENSOR_CLASSES)
from homeassistant.const import CONF_RESOURCE, CONF_PIN
from homeassistant.util import Throttle from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
CONF_RESOURCE = 'resource'
CONF_PIN = 'pin'
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the aREST binary sensor.""" """Setup the aREST binary sensor."""

View File

@ -15,7 +15,6 @@ from homeassistant.components.sensor.command_line import CommandSensorData
from homeassistant.const import ( from homeassistant.const import (
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_COMMAND) CONF_SENSOR_CLASS, CONF_COMMAND)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -45,7 +44,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
payload_on = config.get(CONF_PAYLOAD_ON) payload_on = config.get(CONF_PAYLOAD_ON)
sensor_class = config.get(CONF_SENSOR_CLASS) sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
data = CommandSensorData(command) data = CommandSensorData(command)
add_devices([CommandBinarySensor( add_devices([CommandBinarySensor(
@ -91,8 +91,8 @@ class CommandBinarySensor(BinarySensorDevice):
value = self.data.value value = self.data.value
if self._value_template is not None: if self._value_template is not None:
value = template.render_with_possible_json_value( value = self._value_template.render_with_possible_json_value(
self._hass, self._value_template, value, False) value, False)
if value == self._payload_on: if value == self._payload_on:
self._state = True self._state = True
elif value == self._payload_off: elif value == self._payload_off:

View File

@ -68,4 +68,4 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
def _update_callback(self, zone): def _update_callback(self, zone):
"""Update the zone's state, if needed.""" """Update the zone's state, if needed."""
if zone is None or int(zone) == self._zone_number: if zone is None or int(zone) == self._zone_number:
self.update_ha_state() self.hass.async_add_job(self.update_ha_state)

View File

@ -10,13 +10,17 @@ from os import path
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (BinarySensorDevice, from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA, DOMAIN) BinarySensorDevice, PLATFORM_SCHEMA, DOMAIN)
from homeassistant.components.ffmpeg import (
get_binary, run_test, CONF_INPUT, CONF_OUTPUT, CONF_EXTRA_ARGUMENTS)
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME, from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME,
ATTR_ENTITY_ID) ATTR_ENTITY_ID)
REQUIREMENTS = ["ha-ffmpeg==0.10"] DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
SERVICE_RESTART = 'ffmpeg_restart' SERVICE_RESTART = 'ffmpeg_restart'
@ -29,10 +33,6 @@ MAP_FFMPEG_BIN = [
] ]
CONF_TOOL = 'tool' CONF_TOOL = 'tool'
CONF_INPUT = 'input'
CONF_FFMPEG_BIN = 'ffmpeg_bin'
CONF_EXTRA_ARGUMENTS = 'extra_arguments'
CONF_OUTPUT = 'output'
CONF_PEAK = 'peak' CONF_PEAK = 'peak'
CONF_DURATION = 'duration' CONF_DURATION = 'duration'
CONF_RESET = 'reset' CONF_RESET = 'reset'
@ -40,11 +40,12 @@ CONF_CHANGES = 'changes'
CONF_REPEAT = 'repeat' CONF_REPEAT = 'repeat'
CONF_REPEAT_TIME = 'repeat_time' CONF_REPEAT_TIME = 'repeat_time'
DEFAULT_NAME = 'FFmpeg'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOOL): vol.In(MAP_FFMPEG_BIN), vol.Required(CONF_TOOL): vol.In(MAP_FFMPEG_BIN),
vol.Required(CONF_INPUT): cv.string, vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_FFMPEG_BIN, default="ffmpeg"): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_NAME, default="FFmpeg"): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_OUTPUT): cv.string, vol.Optional(CONF_OUTPUT): cv.string,
vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int), vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int),
@ -65,16 +66,25 @@ SERVICE_RESTART_SCHEMA = vol.Schema({
}) })
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)
# list of all ffmpeg sensors # list of all ffmpeg sensors
DEVICES = [] DEVICES = []
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Create the binary sensor.""" """Create the binary sensor."""
from haffmpeg import SensorNoise, SensorMotion from haffmpeg import SensorNoise, SensorMotion
# check source
if not run_test(config.get(CONF_INPUT)):
return
# generate sensor object
if config.get(CONF_TOOL) == FFMPEG_SENSOR_NOISE: if config.get(CONF_TOOL) == FFMPEG_SENSOR_NOISE:
entity = FFmpegNoise(SensorNoise, config) entity = FFmpegNoise(SensorNoise, config)
else: else:
@ -88,7 +98,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
# exists service? # exists service?
if hass.services.has_service(DOMAIN, SERVICE_RESTART): if hass.services.has_service(DOMAIN, SERVICE_RESTART):
return True return
descriptions = load_yaml_config_file( descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml')) path.join(path.dirname(__file__), 'services.yaml'))
@ -105,13 +115,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
_devices = DEVICES _devices = DEVICES
for device in _devices: for device in _devices:
device.reset_ffmpeg() device.restart_ffmpeg()
hass.services.register(DOMAIN, SERVICE_RESTART, hass.services.register(DOMAIN, SERVICE_RESTART,
_service_handle_restart, _service_handle_restart,
descriptions.get(SERVICE_RESTART), descriptions.get(SERVICE_RESTART),
schema=SERVICE_RESTART_SCHEMA) schema=SERVICE_RESTART_SCHEMA)
return True
class FFmpegBinarySensor(BinarySensorDevice): class FFmpegBinarySensor(BinarySensorDevice):
@ -122,7 +131,7 @@ class FFmpegBinarySensor(BinarySensorDevice):
self._state = False self._state = False
self._config = config self._config = config
self._name = config.get(CONF_NAME) self._name = config.get(CONF_NAME)
self._ffmpeg = ffobj(config.get(CONF_FFMPEG_BIN), self._callback) self._ffmpeg = ffobj(get_binary(), self._callback)
self._start_ffmpeg(config) self._start_ffmpeg(config)
@ -139,7 +148,7 @@ class FFmpegBinarySensor(BinarySensorDevice):
"""For STOP event to shutdown ffmpeg.""" """For STOP event to shutdown ffmpeg."""
self._ffmpeg.close() self._ffmpeg.close()
def reset_ffmpeg(self): def restart_ffmpeg(self):
"""Restart ffmpeg with new config.""" """Restart ffmpeg with new config."""
self._ffmpeg.close() self._ffmpeg.close()
self._start_ffmpeg(self._config) self._start_ffmpeg(self._config)

View File

@ -0,0 +1,71 @@
"""
Support for ISY994 binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.isy994/
"""
import logging
from typing import Callable # noqa
from homeassistant.components.binary_sensor import BinarySensorDevice, DOMAIN
import homeassistant.components.isy994 as isy
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
VALUE_TO_STATE = {
False: STATE_OFF,
True: STATE_ON,
}
UOM = ['2', '78']
STATES = [STATE_OFF, STATE_ON, 'true', 'false']
# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None):
"""Setup the ISY994 binary sensor platform."""
if isy.ISY is None or not isy.ISY.connected:
_LOGGER.error('A connection has not been made to the ISY controller.')
return False
devices = []
for node in isy.filter_nodes(isy.SENSOR_NODES, units=UOM,
states=STATES):
devices.append(ISYBinarySensorDevice(node))
for program in isy.PROGRAMS.get(DOMAIN, []):
try:
status = program[isy.KEY_STATUS]
except (KeyError, AssertionError):
pass
else:
devices.append(ISYBinarySensorProgram(program.name, status))
add_devices(devices)
class ISYBinarySensorDevice(isy.ISYDevice, BinarySensorDevice):
"""Representation of an ISY994 binary sensor device."""
def __init__(self, node) -> None:
"""Initialize the ISY994 binary sensor device."""
isy.ISYDevice.__init__(self, node)
@property
def is_on(self) -> bool:
"""Get whether the ISY994 binary sensor device is on."""
return bool(self.value)
class ISYBinarySensorProgram(ISYBinarySensorDevice):
"""Representation of an ISY994 binary sensor program."""
def __init__(self, name, node) -> None:
"""Initialize the ISY994 binary sensor program."""
ISYBinarySensorDevice.__init__(self, node)
self._name = name

View File

@ -5,17 +5,14 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.knx/ https://home-assistant.io/components/binary_sensor.knx/
""" """
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.knx import ( from homeassistant.components.knx import (KNXConfig, KNXGroupAddress)
KNXConfig, KNXGroupAddress)
DEPENDENCIES = ["knx"] DEPENDENCIES = ['knx']
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the KNX binary sensor platform.""" """Setup the KNX binary sensor platform."""
add_entities([ add_devices([KNXSwitch(hass, KNXConfig(config))])
KNXSwitch(hass, KNXConfig(config))
])
class KNXSwitch(KNXGroupAddress, BinarySensorDevice): class KNXSwitch(KNXGroupAddress, BinarySensorDevice):

View File

@ -0,0 +1,61 @@
"""
Support for Modbus Coil sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.modbus/
"""
import logging
import voluptuous as vol
import homeassistant.components.modbus as modbus
from homeassistant.const import CONF_NAME
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.helpers import config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['modbus']
CONF_COIL = "coil"
CONF_COILS = "coils"
CONF_SLAVE = "slave"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COILS): [{
vol.Required(CONF_COIL): cv.positive_int,
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_SLAVE): cv.positive_int
}]
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Modbus binary sensors."""
sensors = []
for coil in config.get(CONF_COILS):
sensors.append(ModbusCoilSensor(
coil.get(CONF_NAME),
coil.get(CONF_SLAVE),
coil.get(CONF_COIL)))
add_devices(sensors)
class ModbusCoilSensor(BinarySensorDevice):
"""Modbus coil sensor."""
def __init__(self, name, slave, coil):
"""Initialize the modbus coil sensor."""
self._name = name
self._slave = int(slave) if slave else None
self._coil = int(coil)
self._value = None
@property
def is_on(self):
"""Return the state of the sensor."""
return self._value
def update(self):
"""Update the state of the sensor."""
result = modbus.HUB.read_coils(self._slave, self._coil, 1)
self._value = result.bits[0]

View File

@ -15,7 +15,6 @@ from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
CONF_SENSOR_CLASS) CONF_SENSOR_CLASS)
from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS) from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -37,6 +36,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT binary sensor.""" """Setup the MQTT binary sensor."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices([MqttBinarySensor( add_devices([MqttBinarySensor(
hass, hass,
config.get(CONF_NAME), config.get(CONF_NAME),
@ -45,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
config.get(CONF_QOS), config.get(CONF_QOS),
config.get(CONF_PAYLOAD_ON), config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF), config.get(CONF_PAYLOAD_OFF),
config.get(CONF_VALUE_TEMPLATE) value_template
)]) )])
@ -68,8 +70,8 @@ class MqttBinarySensor(BinarySensorDevice):
def message_received(topic, payload, qos): def message_received(topic, payload, qos):
"""A new MQTT message has been received.""" """A new MQTT message has been received."""
if value_template is not None: if value_template is not None:
payload = template.render_with_possible_json_value( payload = value_template.render_with_possible_json_value(
hass, value_template, payload) payload)
if payload == self._payload_on: if payload == self._payload_on:
self._state = True self._state = True
self.update_ha_state() self.update_ha_state()

View File

@ -14,7 +14,6 @@ from homeassistant.components.sensor.rest import RestData
from homeassistant.const import ( from homeassistant.const import (
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE, CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
CONF_SENSOR_CLASS, CONF_VERIFY_SSL) CONF_SENSOR_CLASS, CONF_VERIFY_SSL)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -44,7 +43,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
verify_ssl = config.get(CONF_VERIFY_SSL) verify_ssl = config.get(CONF_VERIFY_SSL)
sensor_class = config.get(CONF_SENSOR_CLASS) sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
rest = RestData(method, resource, payload, verify_ssl) rest = RestData(method, resource, payload, verify_ssl)
rest.update() rest.update()
@ -88,8 +88,8 @@ class RestBinarySensor(BinarySensorDevice):
return False return False
if self._value_template is not None: if self._value_template is not None:
response = template.render_with_possible_json_value( response = self._value_template.render_with_possible_json_value(
self._hass, self._value_template, self.rest.data, False) self.rest.data, False)
try: try:
return bool(int(response)) return bool(int(response))

View File

@ -6,16 +6,37 @@ https://home-assistant.io/components/binary_sensor.rpi_gpio/
""" """
import logging import logging
import homeassistant.components.rpi_gpio as rpi_gpio import voluptuous as vol
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.components.rpi_gpio as rpi_gpio
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import DEVICE_DEFAULT_NAME
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_BOUNCETIME = 'bouncetime'
CONF_INVERT_LOGIC = 'invert_logic'
CONF_PORTS = 'ports'
CONF_PULL_MODE = 'pull_mode'
DEFAULT_PULL_MODE = "UP"
DEFAULT_BOUNCETIME = 50 DEFAULT_BOUNCETIME = 50
DEFAULT_INVERT_LOGIC = False DEFAULT_INVERT_LOGIC = False
DEFAULT_PULL_MODE = 'UP'
DEPENDENCIES = ['rpi_gpio'] DEPENDENCIES = ['rpi_gpio']
_LOGGER = logging.getLogger(__name__)
_SENSORS_SCHEMA = vol.Schema({
cv.positive_int: cv.string,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PORTS): _SENSORS_SCHEMA,
vol.Optional(CONF_BOUNCETIME, default=DEFAULT_BOUNCETIME): cv.positive_int,
vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean,
vol.Optional(CONF_PULL_MODE, default=DEFAULT_PULL_MODE): cv.string,
})
# pylint: disable=unused-argument # pylint: disable=unused-argument

View File

@ -0,0 +1,53 @@
"""
Support for SleepIQ sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.sleepiq/
"""
from homeassistant.components import sleepiq
from homeassistant.components.binary_sensor import BinarySensorDevice
DEPENDENCIES = ['sleepiq']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the SleepIQ sensors."""
if discovery_info is None:
return
data = sleepiq.DATA
data.update()
dev = list()
for bed_id, _ in data.beds.items():
for side in sleepiq.SIDES:
dev.append(IsInBedBinarySensor(data, bed_id, side))
add_devices(dev)
# pylint: disable=too-many-instance-attributes
class IsInBedBinarySensor(sleepiq.SleepIQSensor, BinarySensorDevice):
"""Implementation of a SleepIQ presence sensor."""
def __init__(self, sleepiq_data, bed_id, side):
"""Initialize the sensor."""
sleepiq.SleepIQSensor.__init__(self, sleepiq_data, bed_id, side)
self.type = sleepiq.IS_IN_BED
self._state = None
self._name = sleepiq.SENSOR_TYPES[self.type]
self.update()
@property
def is_on(self):
"""Return the status of the sensor."""
return self._state is True
@property
def sensor_class(self):
"""Return the class of this sensor."""
return "occupancy"
def update(self):
"""Get the latest data from SleepIQ and updates the states."""
sleepiq.SleepIQSensor.update(self)
self._state = self.side.is_in_bed

View File

@ -12,10 +12,9 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA, BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
SENSOR_CLASSES_SCHEMA) SENSOR_CLASSES_SCHEMA)
from homeassistant.const import ( from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, MATCH_ALL, CONF_VALUE_TEMPLATE, ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_SENSORS) CONF_SENSOR_CLASS, CONF_SENSORS)
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -25,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
SENSOR_SCHEMA = vol.Schema({ SENSOR_SCHEMA = vol.Schema({
vol.Required(CONF_VALUE_TEMPLATE): cv.template, vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string, vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_ENTITY_ID, default=MATCH_ALL): cv.entity_ids, vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA
}) })
@ -40,10 +39,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for device, device_config in config[CONF_SENSORS].items(): for device, device_config in config[CONF_SENSORS].items():
value_template = device_config[CONF_VALUE_TEMPLATE] value_template = device_config[CONF_VALUE_TEMPLATE]
entity_ids = device_config[ATTR_ENTITY_ID] entity_ids = (device_config.get(ATTR_ENTITY_ID) or
value_template.extract_entities())
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device) friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
sensor_class = device_config.get(CONF_SENSOR_CLASS) sensor_class = device_config.get(CONF_SENSOR_CLASS)
if value_template is not None:
value_template.hass = hass
sensors.append( sensors.append(
BinarySensorTemplate( BinarySensorTemplate(
hass, hass,
@ -107,8 +110,7 @@ class BinarySensorTemplate(BinarySensorDevice):
def update(self): def update(self):
"""Get the latest data and update the state.""" """Get the latest data and update the state."""
try: try:
self._state = template.render( self._state = self._template.render().lower() == 'true'
self.hass, self._template).lower() == 'true'
except TemplateError as ex: except TemplateError as ex:
if ex.args and ex.args[0].startswith( if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"): "UndefinedError: 'None' has no attribute"):

View File

@ -2,7 +2,7 @@
A sensor that monitors trands in other components. A sensor that monitors trands in other components.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.template/ https://home-assistant.io/components/sensor.trend/
""" """
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the template sensors.""" """Setup the trend sensors."""
sensors = [] sensors = []
for device, device_config in config[CONF_SENSORS].items(): for device, device_config in config[CONF_SENSORS].items():
@ -70,7 +70,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class SensorTrend(BinarySensorDevice): class SensorTrend(BinarySensorDevice):
"""Representation of a Template Sensor.""" """Representation of a trend Sensor."""
# pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, hass, device_id, friendly_name, def __init__(self, hass, device_id, friendly_name,
@ -90,14 +90,14 @@ class SensorTrend(BinarySensorDevice):
self.update() self.update()
def template_sensor_state_listener(entity, old_state, new_state): def trend_sensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state.""" """Called when the target device changes state."""
self.from_state = old_state self.from_state = old_state
self.to_state = new_state self.to_state = new_state
self.update_ha_state(True) self.update_ha_state(True)
track_state_change(hass, target_entity, track_state_change(hass, target_entity,
template_sensor_state_listener) trend_sensor_state_listener)
@property @property
def name(self): def name(self):

View File

@ -1,19 +1,17 @@
""" """
Support for Wink sensors. Support for Wink binary sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
at https://home-assistant.io/components/sensor.wink/ at https://home-assistant.io/components/binary_sensor.wink/
""" """
import logging
import json import json
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.sensor.wink import WinkDevice from homeassistant.components.sensor.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component from homeassistant.loader import get_component
REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] DEPENDENCIES = ['wink']
# These are the available sensors mapped to binary_sensor class # These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = { SENSOR_TYPES = {
@ -21,7 +19,8 @@ SENSOR_TYPES = {
"brightness": "light", "brightness": "light",
"vibration": "vibration", "vibration": "vibration",
"loudness": "sound", "loudness": "sound",
"liquid_detected": "moisture" "liquid_detected": "moisture",
"motion": "motion"
} }
@ -29,17 +28,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink binary sensor platform.""" """Setup the Wink binary sensor platform."""
import pywink import pywink
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token. "
"Get one at https://winkbearertoken.appspot.com/")
return
pywink.set_bearer_token(token)
for sensor in pywink.get_sensors(): for sensor in pywink.get_sensors():
if sensor.capability() in SENSOR_TYPES: if sensor.capability() in SENSOR_TYPES:
add_devices([WinkBinarySensorDevice(sensor)]) add_devices([WinkBinarySensorDevice(sensor)])
@ -77,6 +65,8 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
return self.wink.brightness_boolean() return self.wink.brightness_boolean()
elif self.capability == "liquid_detected": elif self.capability == "liquid_detected":
return self.wink.liquid_boolean() return self.wink.liquid_boolean()
elif self.capability == "motion":
return self.wink.motion_boolean()
else: else:
return self.wink.state() return self.wink.state()

View File

@ -65,7 +65,7 @@ class BloomSky(object):
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def refresh_devices(self): def refresh_devices(self):
"""Use the API to retreive a list of devices.""" """Use the API to retrieve a list of devices."""
_LOGGER.debug("Fetching BloomSky update") _LOGGER.debug("Fetching BloomSky update")
response = requests.get(self.API_URL, response = requests.get(self.API_URL,
headers={"Authorization": self._api_key}, headers={"Authorization": self._api_key},

View File

@ -9,30 +9,28 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.components.ffmpeg import (
run_test, get_binary, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
REQUIREMENTS = ['ha-ffmpeg==0.10'] DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_INPUT = 'input'
CONF_FFMPEG_BIN = 'ffmpeg_bin'
CONF_EXTRA_ARGUMENTS = 'extra_arguments'
DEFAULT_BINARY = 'ffmpeg'
DEFAULT_NAME = 'FFmpeg' DEFAULT_NAME = 'FFmpeg'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_INPUT): cv.string, vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_FFMPEG_BIN, default=DEFAULT_BINARY): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}) })
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a FFmpeg Camera.""" """Setup a FFmpeg Camera."""
if not run_test(config.get(CONF_INPUT)):
return
add_devices([FFmpegCamera(config)]) add_devices([FFmpegCamera(config)])
@ -45,12 +43,11 @@ class FFmpegCamera(Camera):
self._name = config.get(CONF_NAME) self._name = config.get(CONF_NAME)
self._input = config.get(CONF_INPUT) self._input = config.get(CONF_INPUT)
self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS) self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS)
self._ffmpeg_bin = config.get(CONF_FFMPEG_BIN)
def camera_image(self): def camera_image(self):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
from haffmpeg import ImageSingle, IMAGE_JPEG from haffmpeg import ImageSingle, IMAGE_JPEG
ffmpeg = ImageSingle(self._ffmpeg_bin) ffmpeg = ImageSingle(get_binary())
return ffmpeg.get_image(self._input, output_format=IMAGE_JPEG, return ffmpeg.get_image(self._input, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments) extra_cmd=self._extra_arguments)
@ -59,7 +56,7 @@ class FFmpegCamera(Camera):
"""Generate an HTTP MJPEG stream from the camera.""" """Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._ffmpeg_bin) stream = CameraMjpeg(get_binary())
stream.open_camera(self._input, extra_cmd=self._extra_arguments) stream.open_camera(self._input, extra_cmd=self._extra_arguments)
return response( return response(
stream, stream,

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION) HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera) from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.helpers import config_validation as cv, template from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -25,7 +25,7 @@ CONF_STILL_IMAGE_URL = 'still_image_url'
DEFAULT_NAME = 'Generic Camera' DEFAULT_NAME = 'Generic Camera'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_STILL_IMAGE_URL): vol.Any(cv.url, cv.template), vol.Required(CONF_STILL_IMAGE_URL): cv.template,
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]), vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
vol.Optional(CONF_LIMIT_REFETCH_TO_URL_CHANGE, default=False): cv.boolean, vol.Optional(CONF_LIMIT_REFETCH_TO_URL_CHANGE, default=False): cv.boolean,
@ -38,18 +38,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a generic IP Camera.""" """Setup a generic IP Camera."""
add_devices([GenericCamera(config)]) add_devices([GenericCamera(hass, config)])
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class GenericCamera(Camera): class GenericCamera(Camera):
"""A generic implementation of an IP camera.""" """A generic implementation of an IP camera."""
def __init__(self, device_info): def __init__(self, hass, device_info):
"""Initialize a generic camera.""" """Initialize a generic camera."""
super().__init__() super().__init__()
self.hass = hass
self._name = device_info.get(CONF_NAME) self._name = device_info.get(CONF_NAME)
self._still_image_url = device_info[CONF_STILL_IMAGE_URL] self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
self._still_image_url.hass = hass
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE] self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
username = device_info.get(CONF_USERNAME) username = device_info.get(CONF_USERNAME)
@ -69,7 +71,7 @@ class GenericCamera(Camera):
def camera_image(self): def camera_image(self):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
try: try:
url = template.render(self.hass, self._still_image_url) url = self._still_image_url.render()
except TemplateError as err: except TemplateError as err:
_LOGGER.error('Error parsing template %s: %s', _LOGGER.error('Error parsing template %s: %s',
self._still_image_url, err) self._still_image_url, err)

View File

@ -8,28 +8,33 @@ import logging
import socket import socket
import requests import requests
import voluptuous as vol
from homeassistant.components.camera import DOMAIN, Camera from homeassistant.const import CONF_PORT
from homeassistant.helpers import validate_config from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['uvcclient==0.9.0'] REQUIREMENTS = ['uvcclient==0.9.0']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_NVR = 'nvr'
CONF_KEY = 'key'
DEFAULT_PORT = 7080
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_NVR): cv.string,
vol.Required(CONF_KEY): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Discover cameras on a Unifi NVR.""" """Discover cameras on a Unifi NVR."""
if not validate_config({DOMAIN: config}, {DOMAIN: ['nvr', 'key']}, addr = config[CONF_NVR]
_LOGGER): key = config[CONF_KEY]
return None port = config[CONF_PORT]
addr = config.get('nvr')
key = config.get('key')
try:
port = int(config.get('port', 7080))
except ValueError:
_LOGGER.error('Invalid port number provided')
return False
from uvcclient import nvr from uvcclient import nvr
nvrconn = nvr.UVCRemote(addr, port, key) nvrconn = nvr.UVCRemote(addr, port, key)

View File

@ -16,7 +16,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
None, None, "Auto", "heat", None, None, None), None, None, "Auto", "heat", None, None, None),
DemoClimate("Hvac", 21, TEMP_CELSIUS, True, 22, "On High", DemoClimate("Hvac", 21, TEMP_CELSIUS, True, 22, "On High",
67, 54, "Off", "cool", False, None, None), 67, 54, "Off", "cool", False, None, None),
DemoClimate("Ecobee", 23, TEMP_CELSIUS, None, 23, "Auto Low", DemoClimate("Ecobee", None, TEMP_CELSIUS, None, 23, "Auto Low",
None, None, "Auto", "auto", None, 24, 21) None, None, "Auto", "auto", None, 24, 21)
]) ])

View File

@ -14,7 +14,7 @@ from homeassistant.components.climate import (
DOMAIN, STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, DOMAIN, STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH) ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) ATTR_ENTITY_ID, STATE_OFF, STATE_ON, TEMP_FAHRENHEIT, TEMP_CELSIUS)
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -86,10 +86,16 @@ class Thermostat(ClimateDevice):
self.hold_temp = hold_temp self.hold_temp = hold_temp
self._operation_list = ['auto', 'auxHeatOnly', 'cool', self._operation_list = ['auto', 'auxHeatOnly', 'cool',
'heat', 'off'] 'heat', 'off']
self.update_without_throttle = False
def update(self): def update(self):
"""Get the latest state from the thermostat.""" """Get the latest state from the thermostat."""
if self.update_without_throttle:
self.data.update(no_throttle=True)
self.update_without_throttle = False
else:
self.data.update() self.data.update()
self.thermostat = self.data.ecobee.get_thermostat( self.thermostat = self.data.ecobee.get_thermostat(
self.thermostat_index) self.thermostat_index)
@ -101,6 +107,9 @@ class Thermostat(ClimateDevice):
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit of measurement.""" """Return the unit of measurement."""
if self.thermostat['settings']['useCelsius']:
return TEMP_CELSIUS
else:
return TEMP_FAHRENHEIT return TEMP_FAHRENHEIT
@property @property
@ -108,15 +117,6 @@ class Thermostat(ClimateDevice):
"""Return the current temperature.""" """Return the current temperature."""
return self.thermostat['runtime']['actualTemperature'] / 10 return self.thermostat['runtime']['actualTemperature'] / 10
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if (self.operation_mode == 'heat' or
self.operation_mode == 'auxHeatOnly'):
return self.target_temperature_low
elif self.operation_mode == 'cool':
return self.target_temperature_high
@property @property
def target_temperature_low(self): def target_temperature_low(self):
"""Return the lower bound temperature we try to reach.""" """Return the lower bound temperature we try to reach."""
@ -211,17 +211,15 @@ class Thermostat(ClimateDevice):
"away", "indefinite") "away", "indefinite")
else: else:
self.data.ecobee.set_climate_hold(self.thermostat_index, "away") self.data.ecobee.set_climate_hold(self.thermostat_index, "away")
self.update_without_throttle = True
def turn_away_mode_off(self): def turn_away_mode_off(self):
"""Turn away off.""" """Turn away off."""
self.data.ecobee.resume_program(self.thermostat_index) self.data.ecobee.resume_program(self.thermostat_index)
self.update_without_throttle = True
def set_temperature(self, **kwargs): def set_temperature(self, **kwargs):
"""Set new target temperature.""" """Set new target temperature."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
temperature = kwargs.get(ATTR_TEMPERATURE)
low_temp = int(temperature)
high_temp = int(temperature)
if kwargs.get(ATTR_TARGET_TEMP_LOW) is not None and \ if kwargs.get(ATTR_TARGET_TEMP_LOW) is not None and \
kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None: kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None:
high_temp = int(kwargs.get(ATTR_TARGET_TEMP_LOW)) high_temp = int(kwargs.get(ATTR_TARGET_TEMP_LOW))
@ -241,15 +239,18 @@ class Thermostat(ClimateDevice):
"high=%s, is=%s", low_temp, isinstance( "high=%s, is=%s", low_temp, isinstance(
low_temp, (int, float)), high_temp, low_temp, (int, float)), high_temp,
isinstance(high_temp, (int, float))) isinstance(high_temp, (int, float)))
self.update_without_throttle = True
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off).""" """Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
self.data.ecobee.set_hvac_mode(self.thermostat_index, operation_mode) self.data.ecobee.set_hvac_mode(self.thermostat_index, operation_mode)
self.update_without_throttle = True
def set_fan_min_on_time(self, fan_min_on_time): def set_fan_min_on_time(self, fan_min_on_time):
"""Set the minimum fan on time.""" """Set the minimum fan on time."""
self.data.ecobee.set_fan_min_on_time(self.thermostat_index, self.data.ecobee.set_fan_min_on_time(self.thermostat_index,
fan_min_on_time) fan_min_on_time)
self.update_without_throttle = True
# Home and Sleep mode aren't used in UI yet: # Home and Sleep mode aren't used in UI yet:

View File

@ -5,16 +5,19 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.generic_thermostat/ https://home-assistant.io/components/climate.generic_thermostat/
""" """
import logging import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components import switch from homeassistant.components import switch
from homeassistant.components.climate import ( from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice) STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import ( from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE) ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE)
from homeassistant.helpers import condition from homeassistant.helpers import condition
from homeassistant.helpers.event import track_state_change from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['switch', 'sensor'] DEPENDENCIES = ['switch', 'sensor']
@ -30,18 +33,16 @@ CONF_TARGET_TEMP = 'target_temp'
CONF_AC_MODE = 'ac_mode' CONF_AC_MODE = 'ac_mode'
CONF_MIN_DUR = 'min_cycle_duration' CONF_MIN_DUR = 'min_cycle_duration'
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = vol.Schema({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required("platform"): "generic_thermostat",
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_HEATER): cv.entity_id, vol.Required(CONF_HEATER): cv.entity_id,
vol.Required(CONF_SENSOR): cv.entity_id, vol.Required(CONF_SENSOR): cv.entity_id,
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float), vol.Optional(CONF_AC_MODE): cv.boolean,
vol.Optional(CONF_MAX_TEMP): vol.Coerce(float), vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
vol.Optional(CONF_AC_MODE): vol.Coerce(bool),
vol.Optional(CONF_MIN_DUR): vol.All(cv.time_period, cv.positive_timedelta), vol.Optional(CONF_MIN_DUR): vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
}) })
@ -56,10 +57,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
ac_mode = config.get(CONF_AC_MODE) ac_mode = config.get(CONF_AC_MODE)
min_cycle_duration = config.get(CONF_MIN_DUR) min_cycle_duration = config.get(CONF_MIN_DUR)
add_devices([GenericThermostat(hass, name, heater_entity_id, add_devices([GenericThermostat(
sensor_entity_id, min_temp, hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
max_temp, target_temp, ac_mode, target_temp, ac_mode, min_cycle_duration)])
min_cycle_duration)])
# pylint: disable=too-many-instance-attributes, abstract-method # pylint: disable=too-many-instance-attributes, abstract-method
@ -110,7 +110,7 @@ class GenericThermostat(ClimateDevice):
return self._cur_temp return self._cur_temp
@property @property
def operation(self): def current_operation(self):
"""Return current operation ie. heat, cool, idle.""" """Return current operation ie. heat, cool, idle."""
if self.ac_mode: if self.ac_mode:
cooling = self._active and self._is_device_active cooling = self._active and self._is_device_active

View File

@ -99,6 +99,9 @@ class HMThermostat(homematic.HMDevice, ClimateDevice):
return None return None
if temperature is None: if temperature is None:
return return
if self.current_operation == STATE_AUTO:
return self._hmdevice.actionNodeData('MANU_MODE', temperature)
self._hmdevice.set_temperature(temperature) self._hmdevice.set_temperature(temperature)
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):

View File

@ -7,44 +7,67 @@ https://home-assistant.io/components/climate.honeywell/
import logging import logging
import socket import socket
from homeassistant.components.climate import ClimateDevice import voluptuous as vol
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import ( from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT, CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE) ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['evohomeclient==0.2.5', REQUIREMENTS = ['evohomeclient==0.2.5',
'somecomfort==0.2.1'] 'somecomfort==0.3.2']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_AWAY_TEMP = "away_temperature" ATTR_FAN = 'fan'
DEFAULT_AWAY_TEMP = 16 ATTR_FANMODE = 'fanmode'
ATTR_SYSTEM_MODE = 'system_mode'
CONF_AWAY_TEMPERATURE = 'away_temperature'
CONF_REGION = 'region'
DEFAULT_AWAY_TEMPERATURE = 16
DEFAULT_REGION = 'eu'
REGIONS = ['eu', 'us']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_AWAY_TEMPERATURE, default=DEFAULT_AWAY_TEMPERATURE):
vol.Coerce(float),
vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(REGIONS),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the HoneywelL thermostat."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
region = config.get(CONF_REGION)
if region == 'us':
return _setup_us(username, password, config, add_devices)
else:
return _setup_round(username, password, config, add_devices)
def _setup_round(username, password, config, add_devices): def _setup_round(username, password, config, add_devices):
"""Setup rounding function.""" """Setup rounding function."""
from evohomeclient import EvohomeClient from evohomeclient import EvohomeClient
try: away_temp = config.get(CONF_AWAY_TEMPERATURE)
away_temp = float(config.get(CONF_AWAY_TEMP, DEFAULT_AWAY_TEMP))
except ValueError:
_LOGGER.error("value entered for item %s should convert to a number",
CONF_AWAY_TEMP)
return False
evo_api = EvohomeClient(username, password) evo_api = EvohomeClient(username, password)
try: try:
zones = evo_api.temperatures(force_refresh=True) zones = evo_api.temperatures(force_refresh=True)
for i, zone in enumerate(zones): for i, zone in enumerate(zones):
add_devices([RoundThermostat(evo_api, add_devices(
zone['id'], [RoundThermostat(evo_api, zone['id'], i == 0, away_temp)]
i == 0, )
away_temp)])
except socket.error: except socket.error:
_LOGGER.error( _LOGGER.error(
"Connection error logging into the honeywell evohome web service" "Connection error logging into the honeywell evohome web service")
)
return False return False
return True return True
@ -74,26 +97,6 @@ def _setup_us(username, password, config, add_devices):
return True return True
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the honeywel thermostat."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
region = config.get('region', 'eu').lower()
if username is None or password is None:
_LOGGER.error("Missing required configuration items %s or %s",
CONF_USERNAME, CONF_PASSWORD)
return False
if region not in ('us', 'eu'):
_LOGGER.error('Region `%s` is invalid (use either us or eu)', region)
return False
if region == 'us':
return _setup_us(username, password, config, add_devices)
else:
return _setup_round(username, password, config, add_devices)
class RoundThermostat(ClimateDevice): class RoundThermostat(ClimateDevice):
"""Representation of a Honeywell Round Connected thermostat.""" """Representation of a Honeywell Round Connected thermostat."""
@ -103,7 +106,7 @@ class RoundThermostat(ClimateDevice):
self.device = device self.device = device
self._current_temperature = None self._current_temperature = None
self._target_temperature = None self._target_temperature = None
self._name = "round connected" self._name = 'round connected'
self._id = zone_id self._id = zone_id
self._master = master self._master = master
self._is_dhw = False self._is_dhw = False
@ -143,7 +146,7 @@ class RoundThermostat(ClimateDevice):
@property @property
def current_operation(self: ClimateDevice) -> str: def current_operation(self: ClimateDevice) -> str:
"""Get the current operation of the system.""" """Get the current operation of the system."""
return getattr(self.device, 'system_mode', None) return getattr(self.device, ATTR_SYSTEM_MODE, None)
@property @property
def is_away_mode_on(self): def is_away_mode_on(self):
@ -152,7 +155,7 @@ class RoundThermostat(ClimateDevice):
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None: def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
"""Set the HVAC mode for the thermostat.""" """Set the HVAC mode for the thermostat."""
if hasattr(self.device, 'system_mode'): if hasattr(self.device, ATTR_SYSTEM_MODE):
self.device.system_mode = operation_mode self.device.system_mode = operation_mode
def turn_away_mode_on(self): def turn_away_mode_on(self):
@ -186,8 +189,8 @@ class RoundThermostat(ClimateDevice):
self._current_temperature = data['temp'] self._current_temperature = data['temp']
self._target_temperature = data['setpoint'] self._target_temperature = data['setpoint']
if data['thermostat'] == "DOMESTIC_HOT_WATER": if data['thermostat'] == 'DOMESTIC_HOT_WATER':
self._name = "Hot Water" self._name = 'Hot Water'
self._is_dhw = True self._is_dhw = True
else: else:
self._name = data['name'] self._name = data['name']
@ -236,7 +239,7 @@ class HoneywellUSThermostat(ClimateDevice):
@property @property
def current_operation(self: ClimateDevice) -> str: def current_operation(self: ClimateDevice) -> str:
"""Return current operation ie. heat, cool, idle.""" """Return current operation ie. heat, cool, idle."""
return getattr(self._device, 'system_mode', None) return getattr(self._device, ATTR_SYSTEM_MODE, None)
def set_temperature(self, **kwargs): def set_temperature(self, **kwargs):
"""Set target temperature.""" """Set target temperature."""
@ -255,9 +258,11 @@ class HoneywellUSThermostat(ClimateDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the device specific state attributes.""" """Return the device specific state attributes."""
return {'fan': (self.is_fan_on and 'running' or 'idle'), return {
'fanmode': self._device.fan_mode, ATTR_FAN: (self.is_fan_on and 'running' or 'idle'),
'system_mode': self._device.system_mode} ATTR_FANMODE: self._device.fan_mode,
ATTR_SYSTEM_MODE: self._device.system_mode,
}
def turn_away_mode_on(self): def turn_away_mode_on(self):
"""Turn away on.""" """Turn away on."""
@ -269,5 +274,5 @@ class HoneywellUSThermostat(ClimateDevice):
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None: def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
"""Set the system mode (Cool, Heat, etc).""" """Set the system mode (Cool, Heat, etc)."""
if hasattr(self._device, 'system_mode'): if hasattr(self._device, ATTR_SYSTEM_MODE):
self._device.system_mode = operation_mode self._device.system_mode = operation_mode

View File

@ -2,26 +2,37 @@
Support for KNX thermostats. Support for KNX thermostats.
For more details about this platform, please refer to the documentation For more details about this platform, please refer to the documentation
https://home-assistant.io/components/knx/ https://home-assistant.io/components/climate.knx/
""" """
import logging import logging
from homeassistant.components.climate import ClimateDevice import voluptuous as vol
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.components.knx import ( from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA)
KNXConfig, KNXMultiAddressDevice) from homeassistant.components.knx import (KNXConfig, KNXMultiAddressDevice)
from homeassistant.const import (CONF_NAME, TEMP_CELSIUS, ATTR_TEMPERATURE)
DEPENDENCIES = ["knx"] import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_ADDRESS = 'address'
CONF_SETPOINT_ADDRESS = 'setpoint_address'
CONF_TEMPERATURE_ADDRESS = 'temperature_address'
def setup_platform(hass, config, add_entities, discovery_info=None): DEFAULT_NAME = 'KNX Thermostat'
DEPENDENCIES = ['knx']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADDRESS): cv.string,
vol.Required(CONF_SETPOINT_ADDRESS): cv.string,
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Create and add an entity based on the configuration.""" """Create and add an entity based on the configuration."""
add_entities([ add_devices([KNXThermostat(hass, KNXConfig(config))])
KNXThermostat(hass, KNXConfig(config))
])
class KNXThermostat(KNXMultiAddressDevice, ClimateDevice): class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
@ -39,9 +50,8 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
def __init__(self, hass, config): def __init__(self, hass, config):
"""Initialize the thermostat based on the given configuration.""" """Initialize the thermostat based on the given configuration."""
KNXMultiAddressDevice.__init__(self, hass, config, KNXMultiAddressDevice.__init__(
["temperature", "setpoint"], self, hass, config, ['temperature', 'setpoint'], ['mode'])
["mode"])
self._unit_of_measurement = TEMP_CELSIUS # KNX always used celsius self._unit_of_measurement = TEMP_CELSIUS # KNX always used celsius
self._away = False # not yet supported self._away = False # not yet supported
@ -62,14 +72,14 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
"""Return the current temperature.""" """Return the current temperature."""
from knxip.conversion import knx2_to_float from knxip.conversion import knx2_to_float
return knx2_to_float(self.value("temperature")) return knx2_to_float(self.value('temperature'))
@property @property
def target_temperature(self): def target_temperature(self):
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
from knxip.conversion import knx2_to_float from knxip.conversion import knx2_to_float
return knx2_to_float(self.value("setpoint")) return knx2_to_float(self.value('setpoint'))
def set_temperature(self, **kwargs): def set_temperature(self, **kwargs):
"""Set new target temperature.""" """Set new target temperature."""
@ -78,7 +88,7 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
return return
from knxip.conversion import float_to_knx2 from knxip.conversion import float_to_knx2
self.set_value("setpoint", float_to_knx2(temperature)) self.set_value('setpoint', float_to_knx2(temperature))
_LOGGER.debug("Set target temperature to %s", temperature) _LOGGER.debug("Set target temperature to %s", temperature)
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):

View File

@ -0,0 +1,192 @@
"""
mysensors platform that offers a Climate(MySensors-HVAC) component.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/climate.mysensors
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.climate import (
STATE_COOL, STATE_HEAT, STATE_OFF, STATE_AUTO, ClimateDevice,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW)
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
_LOGGER = logging.getLogger(__name__)
DICT_HA_TO_MYS = {STATE_COOL: "CoolOn", STATE_HEAT: "HeatOn",
STATE_AUTO: "AutoChangeOver", STATE_OFF: "Off"}
DICT_MYS_TO_HA = {"CoolOn": STATE_COOL, "HeatOn": STATE_HEAT,
"AutoChangeOver": STATE_AUTO, "Off": STATE_OFF}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors climate."""
if discovery_info is None:
return
for gateway in mysensors.GATEWAYS.values():
if float(gateway.protocol_version) < 1.5:
continue
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_HVAC: [set_req.V_HVAC_FLOW_STATE],
}
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsHVAC))
# pylint: disable=too-many-arguments, too-many-public-methods
class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
"""Representation of a MySensorsHVAC hvac."""
@property
def assumed_state(self):
"""Return True if unable to access real state of entity."""
return self.gateway.optimistic
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return (TEMP_CELSIUS
if self.gateway.metric else TEMP_FAHRENHEIT)
@property
def current_temperature(self):
"""Return the current temperature."""
return self._values.get(self.gateway.const.SetReq.V_TEMP)
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SETPOINT_COOL in self._values and \
set_req.V_HVAC_SETPOINT_HEAT in self._values:
return None
temp = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
if temp is None:
temp = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
return temp
@property
def target_temperature_high(self):
"""Return the highbound target temperature we try to reach."""
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SETPOINT_HEAT in self._values:
return self._values.get(set_req.V_HVAC_SETPOINT_COOL)
@property
def target_temperature_low(self):
"""Return the lowbound target temperature we try to reach."""
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SETPOINT_COOL in self._values:
return self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._values.get(self.gateway.const.SetReq.V_HVAC_FLOW_STATE)
@property
def operation_list(self):
"""List of available operation modes."""
return [STATE_OFF, STATE_AUTO, STATE_COOL, STATE_HEAT]
@property
def current_fan_mode(self):
"""Return the fan setting."""
return self._values.get(self.gateway.const.SetReq.V_HVAC_SPEED)
@property
def fan_list(self):
"""List of available fan modes."""
return ["Auto", "Min", "Normal", "Max"]
def set_temperature(self, **kwargs):
"""Set new target temperature."""
set_req = self.gateway.const.SetReq
temp = kwargs.get(ATTR_TEMPERATURE)
low = kwargs.get(ATTR_TARGET_TEMP_LOW)
high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
heat = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
cool = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
updates = ()
if temp is not None:
if heat is not None:
# Set HEAT Target temperature
value_type = set_req.V_HVAC_SETPOINT_HEAT
elif cool is not None:
# Set COOL Target temperature
value_type = set_req.V_HVAC_SETPOINT_COOL
if heat is not None or cool is not None:
updates = [(value_type, temp)]
elif all(val is not None for val in (low, high, heat, cool)):
updates = [
(set_req.V_HVAC_SETPOINT_HEAT, low),
(set_req.V_HVAC_SETPOINT_COOL, high)]
for value_type, value in updates:
self.gateway.set_child_value(
self.node_id, self.child_id, value_type, value)
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[value_type] = value
self.update_ha_state()
def set_fan_mode(self, fan):
"""Set new target temperature."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(self.node_id, self.child_id,
set_req.V_HVAC_SPEED, fan)
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[set_req.V_HVAC_SPEED] = fan
self.update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set new target temperature."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(self.node_id, self.child_id,
set_req.V_HVAC_FLOW_STATE,
DICT_HA_TO_MYS[operation_mode])
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()
def update(self):
"""Update the controller with the latest value from a sensor."""
set_req = self.gateway.const.SetReq
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.debug(
'%s: value_type %s, value = %s', self._name, value_type, value)
if value_type == set_req.V_HVAC_FLOW_STATE:
self._values[value_type] = DICT_MYS_TO_HA[value]
else:
self._values[value_type] = value
def set_humidity(self, humidity):
"""Set new target humidity."""
_LOGGER.error("Service Not Implemented yet")
def set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
_LOGGER.error("Service Not Implemented yet")
def turn_away_mode_on(self):
"""Turn away mode on."""
_LOGGER.error("Service Not Implemented yet")
def turn_away_mode_off(self):
"""Turn away mode off."""
_LOGGER.error("Service Not Implemented yet")
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
_LOGGER.error("Service Not Implemented yet")
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
_LOGGER.error("Service Not Implemented yet")

View File

@ -4,15 +4,18 @@ Support for Nest thermostats.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.nest/ https://home-assistant.io/components/climate.nest/
""" """
import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.components.nest as nest import homeassistant.components.nest as nest
from homeassistant.components.climate import ( from homeassistant.components.climate import (
STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA) STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice,
PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW)
from homeassistant.const import ( from homeassistant.const import (
TEMP_CELSIUS, CONF_SCAN_INTERVAL, ATTR_TEMPERATURE) TEMP_CELSIUS, CONF_SCAN_INTERVAL, STATE_ON, TEMP_FAHRENHEIT)
from homeassistant.util.temperature import convert as convert_temperature
DEPENDENCIES = ['nest'] DEPENDENCIES = ['nest']
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_INTERVAL): vol.Optional(CONF_SCAN_INTERVAL):
@ -22,7 +25,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Nest thermostat.""" """Setup the Nest thermostat."""
add_devices([NestThermostat(structure, device) temp_unit = hass.config.units.temperature_unit
add_devices([NestThermostat(structure, device, temp_unit)
for structure, device in nest.devices()]) for structure, device in nest.devices()])
@ -30,10 +34,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class NestThermostat(ClimateDevice): class NestThermostat(ClimateDevice):
"""Representation of a Nest thermostat.""" """Representation of a Nest thermostat."""
def __init__(self, structure, device): def __init__(self, structure, device, temp_unit):
"""Initialize the thermostat.""" """Initialize the thermostat."""
self._unit = temp_unit
self.structure = structure self.structure = structure
self.device = device self.device = device
self._fan_list = [STATE_ON, STATE_AUTO]
@property @property
def name(self): def name(self):
@ -51,6 +57,9 @@ class NestThermostat(ClimateDevice):
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit of measurement.""" """Return the unit of measurement."""
if self.device.measurment_scale == 'F':
return TEMP_FAHRENHEIT
elif self.device.measurement_scale == 'C':
return TEMP_CELSIUS return TEMP_CELSIUS
@property @property
@ -78,35 +87,6 @@ class NestThermostat(ClimateDevice):
else: else:
return STATE_IDLE return STATE_IDLE
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.device.mode == 'range':
low, high = self.target_temperature_low, \
self.target_temperature_high
if self.operation == STATE_COOL:
temp = high
elif self.operation == STATE_HEAT:
temp = low
else:
# If the outside temp is lower than the current temp, consider
# the 'low' temp to the target, otherwise use the high temp
if (self.device.structure.weather.current.temperature <
self.current_temperature):
temp = low
else:
temp = high
else:
if self.is_away_mode_on:
# away_temperature is a low, high tuple. Only one should be set
# if not in range mode, the other will be None
temp = self.device.away_temperature[0] or \
self.device.away_temperature[1]
else:
temp = self.device.target
return temp
@property @property
def target_temperature_low(self): def target_temperature_low(self):
"""Return the lower bound temperature we try to reach.""" """Return the lower bound temperature we try to reach."""
@ -134,15 +114,16 @@ class NestThermostat(ClimateDevice):
def set_temperature(self, **kwargs): def set_temperature(self, **kwargs):
"""Set new target temperature.""" """Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE) if kwargs.get(ATTR_TARGET_TEMP_LOW) is not None and \
if temperature is None: kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None:
return target_temp_high = convert_temperature(kwargs.get(
if self.device.mode == 'range': ATTR_TARGET_TEMP_HIGH), self._unit, TEMP_CELSIUS)
if self.target_temperature == self.target_temperature_low: target_temp_low = convert_temperature(kwargs.get(
temperature = (temperature, self.target_temperature_high) ATTR_TARGET_TEMP_LOW), self._unit, TEMP_CELSIUS)
elif self.target_temperature == self.target_temperature_high:
temperature = (self.target_temperature_low, temperature) temp = (target_temp_low, target_temp_high)
self.device.target = temperature _LOGGER.debug("Nest set_temperature-output-value=%s", temp)
self.device.target = temp
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
"""Set operation mode.""" """Set operation mode."""
@ -157,17 +138,18 @@ class NestThermostat(ClimateDevice):
self.structure.away = False self.structure.away = False
@property @property
def is_fan_on(self): def current_fan_mode(self):
"""Return whether the fan is on.""" """Return whether the fan is on."""
return self.device.fan return STATE_ON if self.device.fan else STATE_AUTO
def turn_fan_on(self): @property
"""Turn fan on.""" def fan_list(self):
self.device.fan = True """List of available fan modes."""
return self._fan_list
def turn_fan_off(self): def set_fan_mode(self, fan):
"""Turn fan off.""" """Turn fan on/off."""
self.device.fan = False self.device.fan = fan.lower()
@property @property
def min_temp(self): def min_temp(self):

View File

@ -4,13 +4,24 @@ Support for Proliphix NT10e Thermostats.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.proliphix/ https://home-assistant.io/components/climate.proliphix/
""" """
import voluptuous as vol
from homeassistant.components.climate import ( from homeassistant.components.climate import (
STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice) STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['proliphix==0.3.1'] REQUIREMENTS = ['proliphix==0.3.1']
ATTR_FAN = 'fan'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Proliphix thermostats.""" """Setup the Proliphix thermostats."""
@ -22,9 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
pdp = proliphix.PDP(host, username, password) pdp = proliphix.PDP(host, username, password)
add_devices([ add_devices([ProliphixThermostat(pdp)])
ProliphixThermostat(pdp)
])
# pylint: disable=abstract-method # pylint: disable=abstract-method
@ -56,7 +65,7 @@ class ProliphixThermostat(ClimateDevice):
def device_state_attributes(self): def device_state_attributes(self):
"""Return the device specific state attributes.""" """Return the device specific state attributes."""
return { return {
"fan": self._pdp.fan_state ATTR_FAN: self._pdp.fan_state
} }
@property @property

View File

@ -8,15 +8,28 @@ import datetime
import logging import logging
from urllib.error import URLError from urllib.error import URLError
import voluptuous as vol
from homeassistant.components.climate import ( from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, STATE_OFF, STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, STATE_OFF,
ClimateDevice) ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['radiotherm==1.2'] REQUIREMENTS = ['radiotherm==1.2']
HOLD_TEMP = 'hold_temp'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTR_FAN = 'fan'
ATTR_MODE = 'mode'
CONF_HOLD_TEMP = 'hold_temp'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean,
})
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Radio Thermostat.""" """Setup the Radio Thermostat."""
@ -29,10 +42,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hosts.append(radiotherm.discover.discover_address()) hosts.append(radiotherm.discover.discover_address())
if hosts is None: if hosts is None:
_LOGGER.error("No radiotherm thermostats detected.") _LOGGER.error("No Radiotherm Thermostats detected")
return False return False
hold_temp = config.get(HOLD_TEMP, False) hold_temp = config.get(CONF_HOLD_TEMP)
tstats = [] tstats = []
for host in hosts: for host in hosts:
@ -60,6 +73,7 @@ class RadioThermostat(ClimateDevice):
self._name = None self._name = None
self.hold_temp = hold_temp self.hold_temp = hold_temp
self.update() self.update()
self._operation_list = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF]
@property @property
def name(self): def name(self):
@ -75,8 +89,8 @@ class RadioThermostat(ClimateDevice):
def device_state_attributes(self): def device_state_attributes(self):
"""Return the device specific state attributes.""" """Return the device specific state attributes."""
return { return {
"fan": self.device.fmode['human'], ATTR_FAN: self.device.fmode['human'],
"mode": self.device.tmode['human'] ATTR_MODE: self.device.tmode['human']
} }
@property @property
@ -89,6 +103,11 @@ class RadioThermostat(ClimateDevice):
"""Return the current operation. head, cool idle.""" """Return the current operation. head, cool idle."""
return self._current_operation return self._current_operation
@property
def operation_list(self):
"""Return the operation modes list."""
return self._operation_list
@property @property
def target_temperature(self): def target_temperature(self):
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
@ -124,8 +143,11 @@ class RadioThermostat(ClimateDevice):
def set_time(self): def set_time(self):
"""Set device time.""" """Set device time."""
now = datetime.datetime.now() now = datetime.datetime.now()
self.device.time = {'day': now.weekday(), self.device.time = {
'hour': now.hour, 'minute': now.minute} 'day': now.weekday(),
'hour': now.hour,
'minute': now.minute
}
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
"""Set operation mode (auto, cool, heat, off).""" """Set operation mode (auto, cool, heat, off)."""

View File

@ -0,0 +1,137 @@
"""
Support for Vera thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.vera/
"""
import logging
from homeassistant.util import convert
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_FAHRENHEIT, ATTR_TEMPERATURE
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__)
OPERATION_LIST = ["Heat", "Cool", "Auto Changeover", "Off"]
FAN_OPERATION_LIST = ["On", "Auto", "Cycle"]
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Find and return Vera thermostats."""
add_devices_callback(
VeraThermostat(device, VERA_CONTROLLER) for
device in VERA_DEVICES['climate'])
# pylint: disable=abstract-method
class VeraThermostat(VeraDevice, ClimateDevice):
"""Representation of a Vera Thermostat."""
def __init__(self, vera_device, controller):
"""Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller)
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
mode = self.vera_device.get_hvac_mode()
if mode == "HeatOn":
return OPERATION_LIST[0] # heat
elif mode == "CoolOn":
return OPERATION_LIST[1] # cool
elif mode == "AutoChangeOver":
return OPERATION_LIST[2] # auto
elif mode == "Off":
return OPERATION_LIST[3] # off
return "Off"
@property
def operation_list(self):
"""List of available operation modes."""
return OPERATION_LIST
@property
def current_fan_mode(self):
"""Return the fan setting."""
mode = self.vera_device.get_fan_mode()
if mode == "ContinuousOn":
return FAN_OPERATION_LIST[0] # on
elif mode == "Auto":
return FAN_OPERATION_LIST[1] # auto
elif mode == "PeriodicOn":
return FAN_OPERATION_LIST[2] # cycle
return "Auto"
@property
def fan_list(self):
"""List of available fan modes."""
return FAN_OPERATION_LIST
def set_fan_mode(self, mode):
"""Set new target temperature."""
if mode == FAN_OPERATION_LIST[0]:
self.vera_device.fan_on()
elif mode == FAN_OPERATION_LIST[1]:
self.vera_device.fan_auto()
elif mode == FAN_OPERATION_LIST[2]:
return self.vera_device.fan_cycle()
@property
def current_power_mwh(self):
"""Current power usage in mWh."""
power = self.vera_device.power
if power:
return convert(power, float, 0.0) * 1000
def update(self):
"""Called by the vera device callback to update state."""
self._state = self.vera_device.get_hvac_mode()
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT
@property
def current_temperature(self):
"""Return the current temperature."""
return self.vera_device.get_current_temperature()
@property
def operation(self):
"""Return current operation ie. heat, cool, idle."""
return self.vera_device.get_hvac_state()
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.vera_device.get_current_goal_temperature()
def set_temperature(self, **kwargs):
"""Set new target temperatures."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
self.vera_device.set_temperature(kwargs.get(ATTR_TEMPERATURE))
def set_operation_mode(self, operation_mode):
"""Set HVAC mode (auto, cool, heat, off)."""
if operation_mode == OPERATION_LIST[3]: # off
self.vera_device.turn_off()
elif operation_mode == OPERATION_LIST[2]: # auto
self.vera_device.turn_auto_on()
elif operation_mode == OPERATION_LIST[1]: # cool
self.vera_device.turn_cool_on()
elif operation_mode == OPERATION_LIST[0]: # heat
self.vera_device.turn_heat_on()
def turn_fan_on(self):
"""Turn fan on."""
self.vera_device.fan_on()
def turn_fan_off(self):
"""Turn fan off."""
self.vera_device.fan_auto()

View File

@ -261,6 +261,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self._target_temperature = temperature self._target_temperature = temperature
# ZXT-120 responds only to whole int # ZXT-120 responds only to whole int
value.data = round(temperature, 0) value.data = round(temperature, 0)
self.update_ha_state()
break break
else: else:
_LOGGER.debug("Setting new setpoint for %s, " _LOGGER.debug("Setting new setpoint for %s, "

View File

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

View File

@ -25,9 +25,8 @@ from homeassistant.const import (
DOMAIN = 'cover' DOMAIN = 'cover'
SCAN_INTERVAL = 15 SCAN_INTERVAL = 15
GROUP_NAME_ALL_COVERS = 'all_covers' GROUP_NAME_ALL_COVERS = 'all covers'
ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format( ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format('all_covers')
GROUP_NAME_ALL_COVERS)
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'

View File

@ -14,7 +14,6 @@ from homeassistant.const import (
CONF_COMMAND_CLOSE, CONF_COMMAND_OPEN, CONF_COMMAND_STATE, CONF_COMMAND_CLOSE, CONF_COMMAND_OPEN, CONF_COMMAND_STATE,
CONF_COMMAND_STOP, CONF_COVERS, CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME) CONF_COMMAND_STOP, CONF_COVERS, CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -24,7 +23,7 @@ COVER_SCHEMA = vol.Schema({
vol.Optional(CONF_COMMAND_STATE): cv.string, vol.Optional(CONF_COMMAND_STATE): cv.string,
vol.Optional(CONF_COMMAND_STOP, default='true'): cv.string, vol.Optional(CONF_COMMAND_STOP, default='true'): cv.string,
vol.Optional(CONF_FRIENDLY_NAME): cv.string, vol.Optional(CONF_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE, default='{{ value }}'): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}) })
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -38,6 +37,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
covers = [] covers = []
for device_name, device_config in devices.items(): for device_name, device_config in devices.items():
value_template = device_config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass
covers.append( covers.append(
CommandCover( CommandCover(
hass, hass,
@ -46,7 +48,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
device_config.get(CONF_COMMAND_CLOSE), device_config.get(CONF_COMMAND_CLOSE),
device_config.get(CONF_COMMAND_STOP), device_config.get(CONF_COMMAND_STOP),
device_config.get(CONF_COMMAND_STATE), device_config.get(CONF_COMMAND_STATE),
device_config.get(CONF_VALUE_TEMPLATE), value_template,
) )
) )
@ -136,8 +138,8 @@ class CommandCover(CoverDevice):
if self._command_state: if self._command_state:
payload = str(self._query_state()) payload = str(self._query_state())
if self._value_template: if self._value_template:
payload = template.render_with_possible_json_value( payload = self._value_template.render_with_possible_json_value(
self._hass, self._value_template, payload) payload)
self._state = int(payload) self._state = int(payload)
def open_cover(self, **kwargs): def open_cover(self, **kwargs):

View File

@ -0,0 +1,109 @@
"""
Support for ISY994 covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.isy994/
"""
import logging
from typing import Callable # noqa
from homeassistant.components.cover import CoverDevice, DOMAIN
import homeassistant.components.isy994 as isy
from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
VALUE_TO_STATE = {
0: STATE_CLOSED,
101: STATE_UNKNOWN,
}
UOM = ['97']
STATES = [STATE_OPEN, STATE_CLOSED, 'closing', 'opening', 'stopped']
# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None):
"""Setup the ISY994 cover platform."""
if isy.ISY is None or not isy.ISY.connected:
_LOGGER.error('A connection has not been made to the ISY controller.')
return False
devices = []
for node in isy.filter_nodes(isy.NODES, units=UOM,
states=STATES):
devices.append(ISYCoverDevice(node))
for program in isy.PROGRAMS.get(DOMAIN, []):
try:
status = program[isy.KEY_STATUS]
actions = program[isy.KEY_ACTIONS]
assert actions.dtype == 'program', 'Not a program'
except (KeyError, AssertionError):
pass
else:
devices.append(ISYCoverProgram(program.name, status, actions))
add_devices(devices)
class ISYCoverDevice(isy.ISYDevice, CoverDevice):
"""Representation of an ISY994 cover device."""
def __init__(self, node: object):
"""Initialize the ISY994 cover device."""
isy.ISYDevice.__init__(self, node)
@property
def current_cover_position(self) -> int:
"""Get the current cover position."""
return sorted((0, self.value, 100))[1]
@property
def is_closed(self) -> bool:
"""Get whether the ISY994 cover device is closed."""
return self.state == STATE_CLOSED
@property
def state(self) -> str:
"""Get the state of the ISY994 cover device."""
return VALUE_TO_STATE.get(self.value, STATE_OPEN)
def open_cover(self, **kwargs) -> None:
"""Send the open cover command to the ISY994 cover device."""
if not self._node.on(val=100):
_LOGGER.error('Unable to open the cover')
def close_cover(self, **kwargs) -> None:
"""Send the close cover command to the ISY994 cover device."""
if not self._node.off():
_LOGGER.error('Unable to close the cover')
class ISYCoverProgram(ISYCoverDevice):
"""Representation of an ISY994 cover program."""
def __init__(self, name: str, node: object, actions: object) -> None:
"""Initialize the ISY994 cover program."""
ISYCoverDevice.__init__(self, node)
self._name = name
self._actions = actions
@property
def state(self) -> str:
"""Get the state of the ISY994 cover program."""
return STATE_CLOSED if bool(self.value) else STATE_OPEN
def open_cover(self, **kwargs) -> None:
"""Send the open cover command to the ISY994 cover program."""
if not self._actions.runThen():
_LOGGER.error('Unable to open the cover')
def close_cover(self, **kwargs) -> None:
"""Send the close cover command to the ISY994 cover program."""
if not self._actions.runElse():
_LOGGER.error('Unable to close the cover')

View File

@ -15,7 +15,6 @@ from homeassistant.const import (
STATE_CLOSED) STATE_CLOSED)
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -28,10 +27,10 @@ CONF_PAYLOAD_STOP = 'payload_stop'
CONF_STATE_OPEN = 'state_open' CONF_STATE_OPEN = 'state_open'
CONF_STATE_CLOSED = 'state_closed' CONF_STATE_CLOSED = 'state_closed'
DEFAULT_NAME = "MQTT Cover" DEFAULT_NAME = 'MQTT Cover'
DEFAULT_PAYLOAD_OPEN = "OPEN" DEFAULT_PAYLOAD_OPEN = 'OPEN'
DEFAULT_PAYLOAD_CLOSE = "CLOSE" DEFAULT_PAYLOAD_CLOSE = 'CLOSE'
DEFAULT_PAYLOAD_STOP = "STOP" DEFAULT_PAYLOAD_STOP = 'STOP'
DEFAULT_OPTIMISTIC = False DEFAULT_OPTIMISTIC = False
DEFAULT_RETAIN = False DEFAULT_RETAIN = False
@ -43,27 +42,28 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string, vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string,
vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string, vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
}) })
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Add MQTT Cover.""" """Setup the MQTT Cover."""
add_devices_callback([MqttCover( value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices([MqttCover(
hass, hass,
config[CONF_NAME], config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC), config.get(CONF_STATE_TOPIC),
config[CONF_COMMAND_TOPIC], config.get(CONF_COMMAND_TOPIC),
config[CONF_QOS], config.get(CONF_QOS),
config[CONF_RETAIN], config.get(CONF_RETAIN),
config[CONF_STATE_OPEN], config.get(CONF_STATE_OPEN),
config[CONF_STATE_CLOSED], config.get(CONF_STATE_CLOSED),
config[CONF_PAYLOAD_OPEN], config.get(CONF_PAYLOAD_OPEN),
config[CONF_PAYLOAD_CLOSE], config.get(CONF_PAYLOAD_CLOSE),
config[CONF_PAYLOAD_STOP], config.get(CONF_PAYLOAD_STOP),
config[CONF_OPTIMISTIC], config.get(CONF_OPTIMISTIC),
config.get(CONF_VALUE_TEMPLATE) value_template,
)]) )])
@ -93,8 +93,8 @@ class MqttCover(CoverDevice):
def message_received(topic, payload, qos): def message_received(topic, payload, qos):
"""A new MQTT message has been received.""" """A new MQTT message has been received."""
if value_template is not None: if value_template is not None:
payload = template.render_with_possible_json_value( payload = value_template.render_with_possible_json_value(
hass, value_template, payload) payload)
if payload == self._state_open: if payload == self._state_open:
self._state = False self._state = False
_LOGGER.warning("state=%s", int(self._state)) _LOGGER.warning("state=%s", int(self._state))
@ -111,8 +111,8 @@ class MqttCover(CoverDevice):
self.update_ha_state() self.update_ha_state()
else: else:
_LOGGER.warning( _LOGGER.warning(
"Payload is not True or False or" "Payload is not True, False, or integer (0-100): %s",
" integer(0-100) %s", payload) payload)
if self._state_topic is None: if self._state_topic is None:
# Force into optimistic mode. # Force into optimistic mode.
self._optimistic = True self._optimistic = True
@ -149,7 +149,7 @@ class MqttCover(CoverDevice):
self._qos, self._retain) self._qos, self._retain)
if self._optimistic: if self._optimistic:
# Optimistically assume that cover has changed state. # Optimistically assume that cover has changed state.
self._state = 100 self._state = False
self.update_ha_state() self.update_ha_state()
def close_cover(self, **kwargs): def close_cover(self, **kwargs):
@ -158,7 +158,7 @@ class MqttCover(CoverDevice):
self._qos, self._retain) self._qos, self._retain)
if self._optimistic: if self._optimistic:
# Optimistically assume that cover has changed state. # Optimistically assume that cover has changed state.
self._state = 0 self._state = True
self.update_ha_state() self.update_ha_state()
def stop_cover(self, **kwargs): def stop_cover(self, **kwargs):

View File

@ -7,64 +7,69 @@ https://github.com/andrewshilliday/garage-door-controller
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.rpi_gpio/ https://home-assistant.io/components/cover.rpi_gpio/
""" """
import logging import logging
from time import sleep from time import sleep
import voluptuous as vol import voluptuous as vol
from homeassistant.components.cover import CoverDevice from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME
import homeassistant.components.rpi_gpio as rpi_gpio import homeassistant.components.rpi_gpio as rpi_gpio
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
RELAY_TIME = 'relay_time'
STATE_PULL_MODE = 'state_pull_mode'
DEFAULT_PULL_MODE = 'UP'
DEFAULT_RELAY_TIME = .2
DEPENDENCIES = ['rpi_gpio']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_COVERS = 'covers'
CONF_RELAY_PIN = 'relay_pin'
CONF_RELAY_TIME = 'relay_time'
CONF_STATE_PIN = 'state_pin'
CONF_STATE_PULL_MODE = 'state_pull_mode'
DEFAULT_RELAY_TIME = .2
DEFAULT_STATE_PULL_MODE = 'UP'
DEPENDENCIES = ['rpi_gpio']
_COVERS_SCHEMA = vol.All( _COVERS_SCHEMA = vol.All(
cv.ensure_list, cv.ensure_list,
[ [
vol.Schema({ vol.Schema({
'name': str, CONF_NAME: cv.string,
'relay_pin': int, CONF_RELAY_PIN: cv.positive_int,
'state_pin': int, CONF_STATE_PIN: cv.positive_int,
}) })
] ]
) )
PLATFORM_SCHEMA = vol.Schema({
'platform': str, PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required('covers'): _COVERS_SCHEMA, vol.Required(CONF_COVERS): _COVERS_SCHEMA,
vol.Optional(STATE_PULL_MODE, default=DEFAULT_PULL_MODE): cv.string, vol.Optional(CONF_STATE_PULL_MODE, default=DEFAULT_STATE_PULL_MODE):
vol.Optional(RELAY_TIME, default=DEFAULT_RELAY_TIME): vol.Coerce(int), cv.string,
vol.Optional(CONF_RELAY_TIME, default=DEFAULT_RELAY_TIME): cv.positive_int,
}) })
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the cover platform.""" """Setup the RPi cover platform."""
relay_time = config.get(RELAY_TIME) relay_time = config.get(CONF_RELAY_TIME)
state_pull_mode = config.get(STATE_PULL_MODE) state_pull_mode = config.get(CONF_STATE_PULL_MODE)
covers = [] covers = []
covers_conf = config.get('covers') covers_conf = config.get(CONF_COVERS)
for cover in covers_conf: for cover in covers_conf:
covers.append(RPiGPIOCover(cover['name'], cover['relay_pin'], covers.append(RPiGPIOCover(
cover['state_pin'], cover[CONF_NAME], cover[CONF_RELAY_PIN], cover[CONF_STATE_PIN],
state_pull_mode, state_pull_mode, relay_time))
relay_time))
add_devices(covers) add_devices(covers)
# pylint: disable=abstract-method # pylint: disable=abstract-method
class RPiGPIOCover(CoverDevice): class RPiGPIOCover(CoverDevice):
"""Representation of a Raspberry cover.""" """Representation of a Raspberry GPIO cover."""
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, name, relay_pin, state_pin, def __init__(self, name, relay_pin, state_pin, state_pull_mode,
state_pull_mode, relay_time): relay_time):
"""Initialize the cover.""" """Initialize the cover."""
self._name = name self._name = name
self._state = False self._state = False
@ -79,7 +84,7 @@ class RPiGPIOCover(CoverDevice):
@property @property
def unique_id(self): def unique_id(self):
"""Return the ID of this cover.""" """Return the ID of this cover."""
return "{}.{}".format(self.__class__, self._name) return '{}.{}'.format(self.__class__, self._name)
@property @property
def name(self): def name(self):

View File

@ -6,37 +6,43 @@ https://home-assistant.io/components/cover.scsgate/
""" """
import logging import logging
import voluptuous as vol
import homeassistant.components.scsgate as scsgate import homeassistant.components.scsgate as scsgate
from homeassistant.components.cover import CoverDevice from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_NAME from homeassistant.const import (CONF_DEVICES, CONF_NAME)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['scsgate'] DEPENDENCIES = ['scsgate']
SCS_ID = 'scs_id'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA}),
})
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the SCSGate cover.""" """Setup the SCSGate cover."""
devices = config.get('devices') devices = config.get(CONF_DEVICES)
covers = [] covers = []
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if devices: if devices:
for _, entity_info in devices.items(): for _, entity_info in devices.items():
if entity_info[SCS_ID] in scsgate.SCSGATE.devices: if entity_info[scsgate.CONF_SCS_ID] in scsgate.SCSGATE.devices:
continue continue
logger.info("Adding %s scsgate.cover", entity_info[CONF_NAME])
name = entity_info[CONF_NAME] name = entity_info[CONF_NAME]
scs_id = entity_info[SCS_ID] scs_id = entity_info[scsgate.CONF_SCS_ID]
cover = SCSGateCover(
name=name, logger.info("Adding %s scsgate.cover", name)
scs_id=scs_id,
logger=logger) cover = SCSGateCover(name=name, scs_id=scs_id, logger=logger)
scsgate.SCSGATE.add_device(cover) scsgate.SCSGATE.add_device(cover)
covers.append(cover) covers.append(cover)
add_devices_callback(covers) add_devices(covers)
# pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=too-many-arguments, too-many-instance-attributes
@ -91,6 +97,5 @@ class SCSGateCover(CoverDevice):
def process_event(self, message): def process_event(self, message):
"""Handle a SCSGate message related with this cover.""" """Handle a SCSGate message related with this cover."""
self._logger.debug( self._logger.debug("Cover %s, got message %s",
"Rollershutter %s, got message %s",
self._scs_id, message.toggled) self._scs_id, message.toggled)

View File

@ -0,0 +1,70 @@
"""
Support for Vera cover - curtains, rollershutters etc.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.vera/
"""
import logging
from homeassistant.components.cover import CoverDevice
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Find and return Vera covers."""
add_devices_callback(
VeraCover(device, VERA_CONTROLLER) for
device in VERA_DEVICES['cover'])
# pylint: disable=abstract-method
class VeraCover(VeraDevice, CoverDevice):
"""Represents a Vera Cover in Home Assistant."""
def __init__(self, vera_device, controller):
"""Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller)
@property
def current_cover_position(self):
"""
Return current position of cover.
0 is closed, 100 is fully open.
"""
position = self.vera_device.get_level()
if position <= 5:
return 0
if position >= 95:
return 100
return position
def set_cover_position(self, position, **kwargs):
"""Move the cover to a specific position."""
self.vera_device.set_level(position)
@property
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
def open_cover(self, **kwargs):
"""Open the cover."""
self.vera_device.open()
def close_cover(self, **kwargs):
"""Close the cover."""
self.vera_device.close()
def stop_cover(self, **kwargs):
"""Stop the cover."""
self.vera_device.stop()

View File

@ -4,30 +4,17 @@ Support for Wink Covers.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.wink/ https://home-assistant.io/components/cover.wink/
""" """
import logging
from homeassistant.components.cover import CoverDevice from homeassistant.components.cover import CoverDevice
from homeassistant.components.wink import WinkDevice from homeassistant.components.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] DEPENDENCIES = ['wink']
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink cover platform.""" """Setup the Wink cover platform."""
import pywink import pywink
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token. "
"Get one at https://winkbearertoken.appspot.com/")
return
pywink.set_bearer_token(token)
add_devices(WinkCoverDevice(shade) for shade in add_devices(WinkCoverDevice(shade) for shade in
pywink.get_shades()) pywink.get_shades())
add_devices(WinkCoverDevice(door) for door in add_devices(WinkCoverDevice(door) for door in
@ -35,17 +22,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkCoverDevice(WinkDevice, CoverDevice): class WinkCoverDevice(WinkDevice, CoverDevice):
"""Representation of a Wink covers.""" """Representation of a Wink cover device."""
def __init__(self, wink): def __init__(self, wink):
"""Initialize the cover.""" """Initialize the cover."""
WinkDevice.__init__(self, wink) WinkDevice.__init__(self, wink)
@property
def should_poll(self):
"""Wink Shades don't track their position."""
return False
def close_cover(self): def close_cover(self):
"""Close the shade.""" """Close the shade."""
self.wink.set_state(0) self.wink.set_state(0)

View File

@ -139,7 +139,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def set_cover_position(self, position, **kwargs): def set_cover_position(self, position, **kwargs):
"""Move the roller shutter to a specific position.""" """Move the roller shutter to a specific position."""
self._node.set_dimmer(self._value.value_id, 100 - position) self._node.set_dimmer(self._value.value_id, position)
def stop_cover(self, **kwargs): def stop_cover(self, **kwargs):
"""Stop the roller shutter.""" """Stop the roller shutter."""

View File

@ -1,4 +1,4 @@
"""Tracking for bluetooth devices.""" """Tracking for bluetooth low energy devices."""
import logging import logging
from datetime import timedelta from datetime import timedelta

View File

@ -6,9 +6,11 @@ https://home-assistant.io/components/device_tracker.locative/
""" """
import logging import logging
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
# pylint: disable=unused-import
from homeassistant.components.device_tracker import ( # NOQA
DOMAIN, PLATFORM_SCHEMA)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -25,8 +27,8 @@ def setup_scanner(hass, config, see):
class LocativeView(HomeAssistantView): class LocativeView(HomeAssistantView):
"""View to handle locative requests.""" """View to handle locative requests."""
url = "/api/locative" url = '/api/locative'
name = "api:locative" name = 'api:locative'
def __init__(self, hass, see): def __init__(self, hass, see):
"""Initialize Locative url endpoints.""" """Initialize Locative url endpoints."""
@ -43,22 +45,22 @@ class LocativeView(HomeAssistantView):
data = request.values data = request.values
if 'latitude' not in data or 'longitude' not in data: if 'latitude' not in data or 'longitude' not in data:
return ("Latitude and longitude not specified.", return ('Latitude and longitude not specified.',
HTTP_UNPROCESSABLE_ENTITY) HTTP_UNPROCESSABLE_ENTITY)
if 'device' not in data: if 'device' not in data:
_LOGGER.error("Device id not specified.") _LOGGER.error('Device id not specified.')
return ("Device id not specified.", return ('Device id not specified.',
HTTP_UNPROCESSABLE_ENTITY) HTTP_UNPROCESSABLE_ENTITY)
if 'id' not in data: if 'id' not in data:
_LOGGER.error("Location id not specified.") _LOGGER.error('Location id not specified.')
return ("Location id not specified.", return ('Location id not specified.',
HTTP_UNPROCESSABLE_ENTITY) HTTP_UNPROCESSABLE_ENTITY)
if 'trigger' not in data: if 'trigger' not in data:
_LOGGER.error("Trigger is not specified.") _LOGGER.error('Trigger is not specified.')
return ("Trigger is not specified.", return ('Trigger is not specified.',
HTTP_UNPROCESSABLE_ENTITY) HTTP_UNPROCESSABLE_ENTITY)
device = data['device'].replace('-', '') device = data['device'].replace('-', '')
@ -67,15 +69,15 @@ class LocativeView(HomeAssistantView):
if direction == 'enter': if direction == 'enter':
self.see(dev_id=device, location_name=location_name) self.see(dev_id=device, location_name=location_name)
return "Setting location to {}".format(location_name) return 'Setting location to {}'.format(location_name)
elif direction == 'exit': elif direction == 'exit':
current_state = self.hass.states.get( current_state = self.hass.states.get(
"{}.{}".format(DOMAIN, device)) '{}.{}'.format(DOMAIN, device))
if current_state is None or current_state.state == location_name: if current_state is None or current_state.state == location_name:
self.see(dev_id=device, location_name=STATE_NOT_HOME) self.see(dev_id=device, location_name=STATE_NOT_HOME)
return "Setting location to not home" return 'Setting location to not home'
else: else:
# Ignore the message if it is telling us to exit a zone that we # Ignore the message if it is telling us to exit a zone that we
# aren't currently in. This occurs when a zone is entered # aren't currently in. This occurs when a zone is entered
@ -87,10 +89,10 @@ class LocativeView(HomeAssistantView):
elif direction == 'test': elif direction == 'test':
# In the app, a test message can be sent. Just return something to # In the app, a test message can be sent. Just return something to
# the user to let them know that it works. # the user to let them know that it works.
return "Received test message." return 'Received test message.'
else: else:
_LOGGER.error("Received unidentified message from Locative: %s", _LOGGER.error('Received unidentified message from Locative: %s',
direction) direction)
return ("Received unidentified message: {}".format(direction), return ('Received unidentified message: {}'.format(direction),
HTTP_UNPROCESSABLE_ENTITY) HTTP_UNPROCESSABLE_ENTITY)

View File

@ -10,11 +10,13 @@ import subprocess
from collections import namedtuple from collections import namedtuple
from datetime import timedelta from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import DOMAIN from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOSTS from homeassistant.const import CONF_HOSTS
from homeassistant.helpers import validate_config from homeassistant.util import Throttle
from homeassistant.util import Throttle, convert
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
@ -27,18 +29,21 @@ CONF_EXCLUDE = 'exclude'
REQUIREMENTS = ['python-nmap==0.6.1'] REQUIREMENTS = ['python-nmap==0.6.1']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOSTS): cv.string,
vol.Required(CONF_HOME_INTERVAL, default=0): cv.positive_int,
vol.Optional(CONF_EXCLUDE, default=[]):
vol.All(cv.ensure_list, vol.Length(min=1))
})
def get_scanner(hass, config): def get_scanner(hass, config):
"""Validate the configuration and return a Nmap scanner.""" """Validate the configuration and return a Nmap scanner."""
if not validate_config(config, {DOMAIN: [CONF_HOSTS]},
_LOGGER):
return None
scanner = NmapDeviceScanner(config[DOMAIN]) scanner = NmapDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None return scanner if scanner.success_init else None
Device = namedtuple("Device", ["mac", "name", "ip", "last_update"]) Device = namedtuple('Device', ['mac', 'name', 'ip', 'last_update'])
def _arp(ip_address): def _arp(ip_address):
@ -49,24 +54,26 @@ def _arp(ip_address):
match = re.search(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})', str(out)) match = re.search(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})', str(out))
if match: if match:
return match.group(0) return match.group(0)
_LOGGER.info("No MAC address found for %s", ip_address) _LOGGER.info('No MAC address found for %s', ip_address)
return None return None
class NmapDeviceScanner(object): class NmapDeviceScanner(object):
"""This class scans for devices using nmap.""" """This class scans for devices using nmap."""
exclude = []
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner.""" """Initialize the scanner."""
self.last_results = [] self.last_results = []
self.hosts = config[CONF_HOSTS] self.hosts = config[CONF_HOSTS]
self.exclude = config.get(CONF_EXCLUDE, []) self.exclude = config.get(CONF_EXCLUDE, [])
minutes = convert(config.get(CONF_HOME_INTERVAL), int, 0) minutes = config[CONF_HOME_INTERVAL]
self.home_interval = timedelta(minutes=minutes) self.home_interval = timedelta(minutes=minutes)
self.success_init = self._update_info() self.success_init = self._update_info()
_LOGGER.info("nmap scanner initialized") _LOGGER.info('nmap scanner initialized')
def scan_devices(self): def scan_devices(self):
"""Scan for new devices and return a list with found device IDs.""" """Scan for new devices and return a list with found device IDs."""
@ -90,21 +97,18 @@ class NmapDeviceScanner(object):
Returns boolean if scanning successful. Returns boolean if scanning successful.
""" """
_LOGGER.info("Scanning") _LOGGER.info('Scanning')
from nmap import PortScanner, PortScannerError from nmap import PortScanner, PortScannerError
scanner = PortScanner() scanner = PortScanner()
options = "-F --host-timeout 5s " options = '-F --host-timeout 5s '
exclude = "--exclude "
if self.home_interval: if self.home_interval:
boundary = dt_util.now() - self.home_interval boundary = dt_util.now() - self.home_interval
last_results = [device for device in self.last_results last_results = [device for device in self.last_results
if device.last_update > boundary] if device.last_update > boundary]
if last_results: if last_results:
# Pylint is confused here.
# pylint: disable=no-member
exclude_hosts = self.exclude + [device.ip for device exclude_hosts = self.exclude + [device.ip for device
in last_results] in last_results]
else: else:
@ -113,8 +117,7 @@ class NmapDeviceScanner(object):
last_results = [] last_results = []
exclude_hosts = self.exclude exclude_hosts = self.exclude
if exclude_hosts: if exclude_hosts:
exclude = " --exclude {}".format(",".join(exclude_hosts)) options += ' --exclude {}'.format(','.join(exclude_hosts))
options += exclude
try: try:
result = scanner.scan(hosts=self.hosts, arguments=options) result = scanner.scan(hosts=self.hosts, arguments=options)
@ -134,5 +137,5 @@ class NmapDeviceScanner(object):
self.last_results = last_results self.last_results = last_results
_LOGGER.info("nmap scan successful") _LOGGER.info('nmap scan successful')
return True return True

View File

@ -218,9 +218,18 @@ def setup_scanner(hass, config, see):
lat = wayp[WAYPOINT_LAT_KEY] lat = wayp[WAYPOINT_LAT_KEY]
lon = wayp[WAYPOINT_LON_KEY] lon = wayp[WAYPOINT_LON_KEY]
rad = wayp['rad'] rad = wayp['rad']
# check zone exists
entity_id = zone_comp.ENTITY_ID_FORMAT.format(slugify(pretty_name))
# Check if state already exists
if hass.states.get(entity_id) is not None:
continue
zone = zone_comp.Zone(hass, pretty_name, lat, lon, rad, zone = zone_comp.Zone(hass, pretty_name, lat, lon, rad,
zone_comp.ICON_IMPORT, False, True) zone_comp.ICON_IMPORT, False)
zone_comp.add_zone(hass, pretty_name, zone) zone.entity_id = entity_id
zone.update_ha_state()
def see_beacons(dev_id, kwargs_param): def see_beacons(dev_id, kwargs_param):
"""Set active beacons to the current location.""" """Set active beacons to the current location."""

View File

@ -10,9 +10,11 @@ import telnetlib
import threading import threading
from datetime import timedelta from datetime import timedelta
from homeassistant.components.device_tracker import DOMAIN import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago. # Return cached results if last scan was less then this time ago.
@ -21,23 +23,24 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_DEVICES_REGEX = re.compile( _DEVICES_REGEX = re.compile(
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' + r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s'
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' + r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+'
r'(?P<status>([^\s]+))\s+' + r'(?P<status>([^\s]+))\s+'
r'(?P<type>([^\s]+))\s+' + r'(?P<type>([^\s]+))\s+'
r'(?P<intf>([^\s]+))\s+' + r'(?P<intf>([^\s]+))\s+'
r'(?P<hwintf>([^\s]+))\s+' + r'(?P<hwintf>([^\s]+))\s+'
r'(?P<host>([^\s]+))') r'(?P<host>([^\s]+))')
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string
})
# pylint: disable=unused-argument # pylint: disable=unused-argument
def get_scanner(hass, config): def get_scanner(hass, config):
"""Validate the configuration and return a THOMSON scanner.""" """Validate the configuration and return a THOMSON scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = ThomsonDeviceScanner(config[DOMAIN]) scanner = ThomsonDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None return scanner if scanner.success_init else None
@ -84,7 +87,7 @@ class ThomsonDeviceScanner(object):
return False return False
with self.lock: with self.lock:
_LOGGER.info("Checking ARP") _LOGGER.info('Checking ARP')
data = self.get_thomson_data() data = self.get_thomson_data()
if not data: if not data:
return False return False
@ -108,11 +111,11 @@ class ThomsonDeviceScanner(object):
devices_result = telnet.read_until(b'=>').split(b'\r\n') devices_result = telnet.read_until(b'=>').split(b'\r\n')
telnet.write('exit\r\n'.encode('ascii')) telnet.write('exit\r\n'.encode('ascii'))
except EOFError: except EOFError:
_LOGGER.exception("Unexpected response from router") _LOGGER.exception('Unexpected response from router')
return return
except ConnectionRefusedError: except ConnectionRefusedError:
_LOGGER.exception("Connection refused by router," + _LOGGER.exception('Connection refused by router,'
" is telnet enabled?") ' is telnet enabled?')
return return
devices = {} devices = {}

View File

@ -12,10 +12,11 @@ import threading
from datetime import timedelta from datetime import timedelta
import requests import requests
import voluptuous as vol
from homeassistant.components.device_tracker import DOMAIN import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago
@ -23,27 +24,23 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string
})
def get_scanner(hass, config): def get_scanner(hass, config):
"""Validate the configuration and return a TP-Link scanner.""" """Validate the configuration and return a TP-Link scanner."""
if not validate_config(config, for cls in [Tplink4DeviceScanner, Tplink3DeviceScanner,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, Tplink2DeviceScanner, TplinkDeviceScanner]:
_LOGGER): scanner = cls(config[DOMAIN])
if scanner.success_init:
return scanner
return None return None
scanner = Tplink4DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = Tplink3DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = Tplink2DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = TplinkDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class TplinkDeviceScanner(object): class TplinkDeviceScanner(object):
"""This class queries a wireless router running TP-Link firmware.""" """This class queries a wireless router running TP-Link firmware."""

13
homeassistant/components/emulated_hue.py Executable file → Normal file
View File

@ -17,7 +17,7 @@ from homeassistant import util, core
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, SERVICE_TURN_OFF, SERVICE_TURN_ON, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, SERVICE_TURN_OFF, SERVICE_TURN_ON,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
STATE_ON STATE_ON, HTTP_BAD_REQUEST
) )
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_SUPPORTED_FEATURES, SUPPORT_BRIGHTNESS ATTR_BRIGHTNESS, ATTR_SUPPORTED_FEATURES, SUPPORT_BRIGHTNESS
@ -74,7 +74,8 @@ def setup(hass, yaml_config):
api_password=None, api_password=None,
ssl_certificate=None, ssl_certificate=None,
ssl_key=None, ssl_key=None,
cors_origins=[] cors_origins=[],
approved_ips=[]
) )
server.register_view(DescriptionXmlView(hass, config)) server.register_view(DescriptionXmlView(hass, config))
@ -190,6 +191,7 @@ class HueUsernameView(HomeAssistantView):
url = '/api' url = '/api'
name = 'hue:api' name = 'hue:api'
extra_urls = ['/api/']
requires_auth = False requires_auth = False
def __init__(self, hass): def __init__(self, hass):
@ -201,11 +203,10 @@ class HueUsernameView(HomeAssistantView):
data = request.json data = request.json
if 'devicetype' not in data: if 'devicetype' not in data:
return self.Response("devicetype not specified", status=400) return self.json_message('devicetype not specified',
HTTP_BAD_REQUEST)
json_response = [{'success': {'username': '12345678901234567890'}}] return self.json([{'success': {'username': '12345678901234567890'}}])
return self.json(json_response)
class HueLightsView(HomeAssistantView): class HueLightsView(HomeAssistantView):

View File

@ -12,7 +12,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.components.discovery import load_platform from homeassistant.components.discovery import load_platform
REQUIREMENTS = ['pyenvisalink==1.0', 'pydispatcher==2.0.5'] REQUIREMENTS = ['pyenvisalink==1.7', 'pydispatcher==2.0.5']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = 'envisalink' DOMAIN = 'envisalink'
@ -34,12 +34,14 @@ CONF_PARTITIONS = 'partitions'
CONF_ZONENAME = 'name' CONF_ZONENAME = 'name'
CONF_ZONETYPE = 'type' CONF_ZONETYPE = 'type'
CONF_PARTITIONNAME = 'name' CONF_PARTITIONNAME = 'name'
CONF_PANIC = 'panic_type'
DEFAULT_PORT = 4025 DEFAULT_PORT = 4025
DEFAULT_EVL_VERSION = 3 DEFAULT_EVL_VERSION = 3
DEFAULT_KEEPALIVE = 60 DEFAULT_KEEPALIVE = 60
DEFAULT_ZONEDUMP_INTERVAL = 30 DEFAULT_ZONEDUMP_INTERVAL = 30
DEFAULT_ZONETYPE = 'opening' DEFAULT_ZONETYPE = 'opening'
DEFAULT_PANIC = 'Police'
SIGNAL_ZONE_UPDATE = 'zones_updated' SIGNAL_ZONE_UPDATE = 'zones_updated'
SIGNAL_PARTITION_UPDATE = 'partition_updated' SIGNAL_PARTITION_UPDATE = 'partition_updated'
@ -60,6 +62,7 @@ CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASS): cv.string, vol.Required(CONF_PASS): cv.string,
vol.Required(CONF_CODE): cv.string, vol.Required(CONF_CODE): cv.string,
vol.Optional(CONF_PANIC, default=DEFAULT_PANIC): cv.string,
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA}, vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA}, vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA},
vol.Optional(CONF_EVL_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_EVL_PORT, default=DEFAULT_PORT): cv.port,
@ -89,6 +92,7 @@ def setup(hass, base_config):
_port = config.get(CONF_EVL_PORT) _port = config.get(CONF_EVL_PORT)
_code = config.get(CONF_CODE) _code = config.get(CONF_CODE)
_panel_type = config.get(CONF_PANEL_TYPE) _panel_type = config.get(CONF_PANEL_TYPE)
_panic_type = config.get(CONF_PANIC)
_version = config.get(CONF_EVL_VERSION) _version = config.get(CONF_EVL_VERSION)
_user = config.get(CONF_USERNAME) _user = config.get(CONF_USERNAME)
_pass = config.get(CONF_PASS) _pass = config.get(CONF_PASS)
@ -104,7 +108,8 @@ def setup(hass, base_config):
_user, _user,
_pass, _pass,
_zone_dump, _zone_dump,
_keep_alive) _keep_alive,
hass.loop)
def login_fail_callback(data): def login_fail_callback(data):
"""Callback for when the evl rejects our login.""" """Callback for when the evl rejects our login."""
@ -149,7 +154,7 @@ def setup(hass, base_config):
def start_envisalink(event): def start_envisalink(event):
"""Startup process for the Envisalink.""" """Startup process for the Envisalink."""
EVL_CONTROLLER.start() hass.loop.call_soon_threadsafe(EVL_CONTROLLER.start)
for _ in range(10): for _ in range(10):
if 'success' in _connect_status: if 'success' in _connect_status:
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink)
@ -177,14 +182,15 @@ def setup(hass, base_config):
# Load sub-components for Envisalink # Load sub-components for Envisalink
if _partitions: if _partitions:
load_platform(hass, 'alarm_control_panel', 'envisalink', load_platform(hass, 'alarm_control_panel', 'envisalink',
{'partitions': _partitions, {CONF_PARTITIONS: _partitions,
'code': _code}, config) CONF_CODE: _code,
CONF_PANIC: _panic_type}, config)
load_platform(hass, 'sensor', 'envisalink', load_platform(hass, 'sensor', 'envisalink',
{'partitions': _partitions, {CONF_PARTITIONS: _partitions,
'code': _code}, config) CONF_CODE: _code}, config)
if _zones: if _zones:
load_platform(hass, 'binary_sensor', 'envisalink', load_platform(hass, 'binary_sensor', 'envisalink',
{'zones': _zones}, config) {CONF_ZONES: _zones}, config)
return True return True

View File

@ -0,0 +1,120 @@
"""
Support for ISY994 fans.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/fan.isy994/
"""
import logging
from typing import Callable
from homeassistant.components.fan import (FanEntity, DOMAIN, SPEED_OFF,
SPEED_LOW, SPEED_MED,
SPEED_HIGH)
import homeassistant.components.isy994 as isy
from homeassistant.const import STATE_UNKNOWN, STATE_ON, STATE_OFF
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
VALUE_TO_STATE = {
0: SPEED_OFF,
63: SPEED_LOW,
64: SPEED_LOW,
190: SPEED_MED,
191: SPEED_MED,
255: SPEED_HIGH,
}
STATE_TO_VALUE = {}
for key in VALUE_TO_STATE:
STATE_TO_VALUE[VALUE_TO_STATE[key]] = key
STATES = [SPEED_OFF, SPEED_LOW, SPEED_MED, SPEED_HIGH]
# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None):
"""Setup the ISY994 fan platform."""
if isy.ISY is None or not isy.ISY.connected:
_LOGGER.error('A connection has not been made to the ISY controller.')
return False
devices = []
for node in isy.filter_nodes(isy.NODES, states=STATES):
devices.append(ISYFanDevice(node))
for program in isy.PROGRAMS.get(DOMAIN, []):
try:
status = program[isy.KEY_STATUS]
actions = program[isy.KEY_ACTIONS]
assert actions.dtype == 'program', 'Not a program'
except (KeyError, AssertionError):
pass
else:
devices.append(ISYFanProgram(program.name, status, actions))
add_devices(devices)
class ISYFanDevice(isy.ISYDevice, FanEntity):
"""Representation of an ISY994 fan device."""
def __init__(self, node) -> None:
"""Initialize the ISY994 fan device."""
isy.ISYDevice.__init__(self, node)
self.speed = self.state
@property
def state(self) -> str:
"""Get the state of the ISY994 fan device."""
return VALUE_TO_STATE.get(self.value, STATE_UNKNOWN)
def set_speed(self, speed: str) -> None:
"""Send the set speed command to the ISY994 fan device."""
if not self._node.on(val=STATE_TO_VALUE.get(speed, 0)):
_LOGGER.debug('Unable to set fan speed')
else:
self.speed = self.state
def turn_on(self, speed: str=None, **kwargs) -> None:
"""Send the turn on command to the ISY994 fan device."""
self.set_speed(speed)
def turn_off(self, **kwargs) -> None:
"""Send the turn off command to the ISY994 fan device."""
if not self._node.off():
_LOGGER.debug('Unable to set fan speed')
else:
self.speed = self.state
class ISYFanProgram(ISYFanDevice):
"""Representation of an ISY994 fan program."""
def __init__(self, name: str, node, actions) -> None:
"""Initialize the ISY994 fan program."""
ISYFanDevice.__init__(self, node)
self._name = name
self._actions = actions
self.speed = STATE_ON if self.is_on else STATE_OFF
@property
def state(self) -> str:
"""Get the state of the ISY994 fan program."""
return STATE_ON if bool(self.value) else STATE_OFF
def turn_off(self, **kwargs) -> None:
"""Send the turn on command to ISY994 fan program."""
if not self._actions.runThen():
_LOGGER.error('Unable to open the cover')
else:
self.speed = STATE_ON if self.is_on else STATE_OFF
def turn_on(self, **kwargs) -> None:
"""Send the turn off command to ISY994 fan program."""
if not self._actions.runElse():
_LOGGER.error('Unable to close the cover')
else:
self.speed = STATE_ON if self.is_on else STATE_OFF

View File

@ -5,17 +5,16 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/fan.mqtt/ https://home-assistant.io/components/fan.mqtt/
""" """
import logging import logging
from functools import partial
import voluptuous as vol import voluptuous as vol
import homeassistant.components.mqtt as mqtt import homeassistant.components.mqtt as mqtt
from homeassistant.const import (CONF_NAME, CONF_OPTIMISTIC, CONF_STATE, from homeassistant.const import (
STATE_ON, STATE_OFF) CONF_NAME, CONF_OPTIMISTIC, CONF_STATE, STATE_ON, STATE_OFF,
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON)
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.template import render_with_possible_json_value
from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_MEDIUM, from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_MEDIUM,
SPEED_HIGH, FanEntity, SPEED_HIGH, FanEntity,
SUPPORT_SET_SPEED, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, SUPPORT_OSCILLATE,
@ -23,33 +22,31 @@ from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_MEDIUM,
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ["mqtt"] DEPENDENCIES = ['mqtt']
CONF_STATE_VALUE_TEMPLATE = "state_value_template" CONF_STATE_VALUE_TEMPLATE = 'state_value_template'
CONF_SPEED_STATE_TOPIC = "speed_state_topic" CONF_SPEED_STATE_TOPIC = 'speed_state_topic'
CONF_SPEED_COMMAND_TOPIC = "speed_command_topic" CONF_SPEED_COMMAND_TOPIC = 'speed_command_topic'
CONF_SPEED_VALUE_TEMPLATE = "speed_value_template" CONF_SPEED_VALUE_TEMPLATE = 'speed_value_template'
CONF_OSCILLATION_STATE_TOPIC = "oscillation_state_topic" CONF_OSCILLATION_STATE_TOPIC = 'oscillation_state_topic'
CONF_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic" CONF_OSCILLATION_COMMAND_TOPIC = 'oscillation_command_topic'
CONF_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template" CONF_OSCILLATION_VALUE_TEMPLATE = 'oscillation_value_template'
CONF_PAYLOAD_ON = "payload_on" CONF_PAYLOAD_OSCILLATION_ON = 'payload_oscillation_on'
CONF_PAYLOAD_OFF = "payload_off" CONF_PAYLOAD_OSCILLATION_OFF = 'payload_oscillation_off'
CONF_PAYLOAD_OSCILLATION_ON = "payload_oscillation_on" CONF_PAYLOAD_LOW_SPEED = 'payload_low_speed'
CONF_PAYLOAD_OSCILLATION_OFF = "payload_oscillation_off" CONF_PAYLOAD_MEDIUM_SPEED = 'payload_medium_speed'
CONF_PAYLOAD_LOW_SPEED = "payload_low_speed" CONF_PAYLOAD_HIGH_SPEED = 'payload_high_speed'
CONF_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed" CONF_SPEED_LIST = 'speeds'
CONF_PAYLOAD_HIGH_SPEED = "payload_high_speed"
CONF_SPEED_LIST = "speeds"
DEFAULT_NAME = "MQTT Fan" DEFAULT_NAME = 'MQTT Fan'
DEFAULT_PAYLOAD_ON = "ON" DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = "OFF" DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_OPTIMISTIC = False DEFAULT_OPTIMISTIC = False
OSCILLATE_ON_PAYLOAD = "oscillate_on" OSCILLATE_ON_PAYLOAD = 'oscillate_on'
OSCILLATE_OFF_PAYLOAD = "oscillate_off" OSCILLATE_OFF_PAYLOAD = 'oscillate_off'
OSCILLATION = "oscillation" OSCILLATION = 'oscillation'
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@ -77,11 +74,11 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup MQTT fan platform.""" """Setup MQTT fan platform."""
add_devices_callback([MqttFan( add_devices([MqttFan(
hass, hass,
config[CONF_NAME], config.get(CONF_NAME),
{ {
key: config.get(key) for key in ( key: config.get(key) for key in (
CONF_STATE_TOPIC, CONF_STATE_TOPIC,
@ -97,19 +94,19 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
ATTR_SPEED: config.get(CONF_SPEED_VALUE_TEMPLATE), ATTR_SPEED: config.get(CONF_SPEED_VALUE_TEMPLATE),
OSCILLATION: config.get(CONF_OSCILLATION_VALUE_TEMPLATE) OSCILLATION: config.get(CONF_OSCILLATION_VALUE_TEMPLATE)
}, },
config[CONF_QOS], config.get(CONF_QOS),
config[CONF_RETAIN], config.get(CONF_RETAIN),
{ {
STATE_ON: config[CONF_PAYLOAD_ON], STATE_ON: config.get(CONF_PAYLOAD_ON),
STATE_OFF: config[CONF_PAYLOAD_OFF], STATE_OFF: config.get(CONF_PAYLOAD_OFF),
OSCILLATE_ON_PAYLOAD: config[CONF_PAYLOAD_OSCILLATION_ON], OSCILLATE_ON_PAYLOAD: config.get(CONF_PAYLOAD_OSCILLATION_ON),
OSCILLATE_OFF_PAYLOAD: config[CONF_PAYLOAD_OSCILLATION_OFF], OSCILLATE_OFF_PAYLOAD: config.get(CONF_PAYLOAD_OSCILLATION_OFF),
SPEED_LOW: config[CONF_PAYLOAD_LOW_SPEED], SPEED_LOW: config.get(CONF_PAYLOAD_LOW_SPEED),
SPEED_MEDIUM: config[CONF_PAYLOAD_MEDIUM_SPEED], SPEED_MEDIUM: config.get(CONF_PAYLOAD_MEDIUM_SPEED),
SPEED_HIGH: config[CONF_PAYLOAD_HIGH_SPEED], SPEED_HIGH: config.get(CONF_PAYLOAD_HIGH_SPEED),
}, },
config[CONF_SPEED_LIST], config.get(CONF_SPEED_LIST),
config[CONF_OPTIMISTIC], config.get(CONF_OPTIMISTIC),
)]) )])
@ -120,7 +117,7 @@ class MqttFan(FanEntity):
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, hass, name, topic, templates, qos, retain, payload, def __init__(self, hass, name, topic, templates, qos, retain, payload,
speed_list, optimistic): speed_list, optimistic):
"""Initialize MQTT fan.""" """Initialize the MQTT fan."""
self._hass = hass self._hass = hass
self._name = name self._name = name
self._topic = topic self._topic = topic
@ -129,11 +126,10 @@ class MqttFan(FanEntity):
self._payload = payload self._payload = payload
self._speed_list = speed_list self._speed_list = speed_list
self._optimistic = optimistic or topic[CONF_STATE_TOPIC] is None self._optimistic = optimistic or topic[CONF_STATE_TOPIC] is None
self._optimistic_oscillation = (optimistic or self._optimistic_oscillation = (
topic[CONF_OSCILLATION_STATE_TOPIC] optimistic or topic[CONF_OSCILLATION_STATE_TOPIC] is None)
is None) self._optimistic_speed = (
self._optimistic_speed = (optimistic or optimistic or topic[CONF_SPEED_STATE_TOPIC] is None)
topic[CONF_SPEED_STATE_TOPIC] is None)
self._state = False self._state = False
self._supported_features = 0 self._supported_features = 0
self._supported_features |= (topic[CONF_OSCILLATION_STATE_TOPIC] self._supported_features |= (topic[CONF_OSCILLATION_STATE_TOPIC]
@ -141,9 +137,12 @@ class MqttFan(FanEntity):
self._supported_features |= (topic[CONF_SPEED_STATE_TOPIC] self._supported_features |= (topic[CONF_SPEED_STATE_TOPIC]
is not None and SUPPORT_SET_SPEED) is not None and SUPPORT_SET_SPEED)
templates = {key: ((lambda value: value) if tpl is None else for key, tpl in list(templates.items()):
partial(render_with_possible_json_value, hass, tpl)) if tpl is None:
for key, tpl in templates.items()} templates[key] = lambda value: value
else:
tpl.hass = hass
templates[key] = tpl.render_with_possible_json_value
def state_received(topic, payload, qos): def state_received(topic, payload, qos):
"""A new MQTT message has been received.""" """A new MQTT message has been received."""

View File

@ -0,0 +1,70 @@
"""
Component that will help set the ffmpeg component.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/ffmpeg/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
DOMAIN = 'ffmpeg'
REQUIREMENTS = ["ha-ffmpeg==0.13"]
_LOGGER = logging.getLogger(__name__)
CONF_INPUT = 'input'
CONF_FFMPEG_BIN = 'ffmpeg_bin'
CONF_EXTRA_ARGUMENTS = 'extra_arguments'
CONF_OUTPUT = 'output'
CONF_RUN_TEST = 'run_test'
DEFAULT_BINARY = 'ffmpeg'
DEFAULT_RUN_TEST = True
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_FFMPEG_BIN, default=DEFAULT_BINARY): cv.string,
vol.Optional(CONF_RUN_TEST, default=DEFAULT_RUN_TEST): cv.boolean,
}),
}, extra=vol.ALLOW_EXTRA)
FFMPEG_CONFIG = {
CONF_FFMPEG_BIN: DEFAULT_BINARY,
CONF_RUN_TEST: DEFAULT_RUN_TEST,
}
FFMPEG_TEST_CACHE = {}
def setup(hass, config):
"""Setup the FFmpeg component."""
if DOMAIN in config:
FFMPEG_CONFIG.update(config.get(DOMAIN))
return True
def get_binary():
"""Return ffmpeg binary from config."""
return FFMPEG_CONFIG.get(CONF_FFMPEG_BIN)
def run_test(input_source):
"""Run test on this input. TRUE is deactivate or run correct."""
from haffmpeg import Test
if FFMPEG_CONFIG.get(CONF_RUN_TEST):
# if in cache
if input_source in FFMPEG_TEST_CACHE:
return FFMPEG_TEST_CACHE[input_source]
# run test
test = Test(get_binary())
if not test.run_test(input_source):
_LOGGER.error("FFmpeg '%s' test fails!", input_source)
FFMPEG_TEST_CACHE[input_source] = False
return False
FFMPEG_TEST_CACHE[input_source] = True
return True

View File

@ -1,14 +1,14 @@
"""DO NOT MODIFY. Auto-generated by script/fingerprint_frontend.""" """DO NOT MODIFY. Auto-generated by script/fingerprint_frontend."""
FINGERPRINTS = { FINGERPRINTS = {
"core.js": "1fd10c1fcdf56a61f60cf861d5a0368c", "core.js": "78862c0984279b6876f594ffde45177c",
"frontend.html": "20defe06c11b2fa2f076dc92b6c3b0dd", "frontend.html": "c1753e1ce530f978036742477c96d2fd",
"mdi.html": "710b84acc99b32514f52291aba9cd8e8", "mdi.html": "6bd013a8252e19b3c1f1de52994cfbe4",
"panels/ha-panel-dev-event.html": "3cc881ae8026c0fba5aa67d334a3ab2b", "panels/ha-panel-dev-event.html": "c4a5f70eece9f92616a65e8d26be803e",
"panels/ha-panel-dev-info.html": "34e2df1af32e60fffcafe7e008a92169", "panels/ha-panel-dev-info.html": "34e2df1af32e60fffcafe7e008a92169",
"panels/ha-panel-dev-service.html": "bb5c587ada694e0fd42ceaaedd6fe6aa", "panels/ha-panel-dev-service.html": "07e83c6b7f79d78a59258f6dba477b54",
"panels/ha-panel-dev-state.html": "4608326978256644c42b13940c028e0a", "panels/ha-panel-dev-state.html": "fd8eb946856b1346a87a51d0c86854ff",
"panels/ha-panel-dev-template.html": "0a099d4589636ed3038a3e9f020468a7", "panels/ha-panel-dev-template.html": "7cff8a2ef3f44fdaf357a0d41696bf6d",
"panels/ha-panel-history.html": "efe1bcdd7733b09e55f4f965d171c295", "panels/ha-panel-history.html": "efe1bcdd7733b09e55f4f965d171c295",
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab", "panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
"panels/ha-panel-logbook.html": "66108d82763359a218c9695f0553de40", "panels/ha-panel-logbook.html": "66108d82763359a218c9695f0553de40",

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 ba588fc779d34a2fbf7cc9a23103c38e3e3e0356 Subproject commit 0a4454c68f3c29c77cd60f4315d410d8b3737543

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

@ -16,7 +16,6 @@ from homeassistant.const import (
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -45,6 +44,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Add MQTT Garage Door.""" """Add MQTT Garage Door."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices_callback([MqttGarageDoor( add_devices_callback([MqttGarageDoor(
hass, hass,
config[CONF_NAME], config[CONF_NAME],
@ -57,7 +59,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
config[CONF_SERVICE_OPEN], config[CONF_SERVICE_OPEN],
config[CONF_SERVICE_CLOSE], config[CONF_SERVICE_CLOSE],
config[CONF_OPTIMISTIC], config[CONF_OPTIMISTIC],
config.get(CONF_VALUE_TEMPLATE))]) value_template)])
# pylint: disable=too-many-arguments, too-many-instance-attributes # pylint: disable=too-many-arguments, too-many-instance-attributes
@ -84,8 +86,8 @@ class MqttGarageDoor(GarageDoorDevice):
def message_received(topic, payload, qos): def message_received(topic, payload, qos):
"""A new MQTT message has been received.""" """A new MQTT message has been received."""
if value_template is not None: if value_template is not None:
payload = template.render_with_possible_json_value( payload = value_template.render_with_possible_json_value(
hass, value_template, payload) payload)
if payload == self._state_open: if payload == self._state_open:
self._state = True self._state = True
self.update_ha_state() self.update_ha_state()

View File

@ -4,30 +4,17 @@ Support for Wink garage doors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/garage_door.wink/ https://home-assistant.io/components/garage_door.wink/
""" """
import logging
from homeassistant.components.garage_door import GarageDoorDevice from homeassistant.components.garage_door import GarageDoorDevice
from homeassistant.components.wink import WinkDevice from homeassistant.components.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] DEPENDENCIES = ['wink']
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink garage door platform.""" """Setup the Wink garage door platform."""
import pywink import pywink
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token. "
"Get one at https://winkbearertoken.appspot.com/")
return
pywink.set_bearer_token(token)
add_devices(WinkGarageDoorDevice(door) for door in add_devices(WinkGarageDoorDevice(door) for door in
pywink.get_garage_doors()) pywink.get_garage_doors())

View File

@ -23,7 +23,7 @@ from homeassistant.config import load_yaml_config_file
from homeassistant.util import Throttle from homeassistant.util import Throttle
DOMAIN = 'homematic' DOMAIN = 'homematic'
REQUIREMENTS = ["pyhomematic==0.1.13"] REQUIREMENTS = ["pyhomematic==0.1.14"]
HOMEMATIC = None HOMEMATIC = None
HOMEMATIC_LINK_DELAY = 0.5 HOMEMATIC_LINK_DELAY = 0.5

View File

@ -25,9 +25,10 @@ import homeassistant.util.dt as dt_util
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
DOMAIN = 'http' DOMAIN = 'http'
REQUIREMENTS = ('cherrypy==7.1.0', 'static3==0.7.0', 'Werkzeug==0.11.11') REQUIREMENTS = ('cherrypy==8.1.0', 'static3==0.7.0', 'Werkzeug==0.11.11')
CONF_API_PASSWORD = 'api_password' CONF_API_PASSWORD = 'api_password'
CONF_APPROVED_IPS = 'approved_ips'
CONF_SERVER_HOST = 'server_host' CONF_SERVER_HOST = 'server_host'
CONF_SERVER_PORT = 'server_port' CONF_SERVER_PORT = 'server_port'
CONF_DEVELOPMENT = 'development' CONF_DEVELOPMENT = 'development'
@ -71,7 +72,8 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_DEVELOPMENT): cv.string, vol.Optional(CONF_DEVELOPMENT): cv.string,
vol.Optional(CONF_SSL_CERTIFICATE): cv.isfile, vol.Optional(CONF_SSL_CERTIFICATE): cv.isfile,
vol.Optional(CONF_SSL_KEY): cv.isfile, vol.Optional(CONF_SSL_KEY): cv.isfile,
vol.Optional(CONF_CORS_ORIGINS): cv.ensure_list vol.Optional(CONF_CORS_ORIGINS): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_APPROVED_IPS): vol.All(cv.ensure_list, [cv.string])
}), }),
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@ -108,6 +110,7 @@ def setup(hass, config):
ssl_certificate = conf.get(CONF_SSL_CERTIFICATE) ssl_certificate = conf.get(CONF_SSL_CERTIFICATE)
ssl_key = conf.get(CONF_SSL_KEY) ssl_key = conf.get(CONF_SSL_KEY)
cors_origins = conf.get(CONF_CORS_ORIGINS, []) cors_origins = conf.get(CONF_CORS_ORIGINS, [])
approved_ips = conf.get(CONF_APPROVED_IPS, [])
server = HomeAssistantWSGI( server = HomeAssistantWSGI(
hass, hass,
@ -117,7 +120,8 @@ def setup(hass, config):
api_password=api_password, api_password=api_password,
ssl_certificate=ssl_certificate, ssl_certificate=ssl_certificate,
ssl_key=ssl_key, ssl_key=ssl_key,
cors_origins=cors_origins cors_origins=cors_origins,
approved_ips=approved_ips
) )
def start_wsgi_server(event): def start_wsgi_server(event):
@ -249,7 +253,8 @@ class HomeAssistantWSGI(object):
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, hass, development, api_password, ssl_certificate, def __init__(self, hass, development, api_password, ssl_certificate,
ssl_key, server_host, server_port, cors_origins): ssl_key, server_host, server_port, cors_origins,
approved_ips):
"""Initilalize the WSGI Home Assistant server.""" """Initilalize the WSGI Home Assistant server."""
from werkzeug.wrappers import Response from werkzeug.wrappers import Response
@ -268,6 +273,7 @@ class HomeAssistantWSGI(object):
self.server_host = server_host self.server_host = server_host
self.server_port = server_port self.server_port = server_port
self.cors_origins = cors_origins self.cors_origins = cors_origins
self.approved_ips = approved_ips
self.event_forwarder = None self.event_forwarder = None
self.server = None self.server = None
@ -468,6 +474,9 @@ class HomeAssistantView(object):
if self.hass.wsgi.api_password is None: if self.hass.wsgi.api_password is None:
authenticated = True authenticated = True
elif request.remote_addr in self.hass.wsgi.approved_ips:
authenticated = True
elif hmac.compare_digest(request.headers.get(HTTP_HEADER_HA_AUTH, ''), elif hmac.compare_digest(request.headers.get(HTTP_HEADER_HA_AUTH, ''),
self.hass.wsgi.api_password): self.hass.wsgi.api_password):
# A valid auth header has been set # A valid auth header has been set

View File

@ -6,65 +6,71 @@ https://home-assistant.io/components/influxdb/
""" """
import logging import logging
import homeassistant.util as util import voluptuous as vol
from homeassistant.const import (EVENT_STATE_CHANGED, STATE_UNAVAILABLE,
STATE_UNKNOWN) from homeassistant.const import (
EVENT_STATE_CHANGED, STATE_UNAVAILABLE, STATE_UNKNOWN, CONF_HOST,
CONF_PORT, CONF_SSL, CONF_VERIFY_SSL, CONF_USERNAME, CONF_BLACKLIST,
CONF_PASSWORD, CONF_WHITELIST)
from homeassistant.helpers import state as state_helper from homeassistant.helpers import state as state_helper
from homeassistant.helpers import validate_config import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DOMAIN = "influxdb"
DEPENDENCIES = []
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 8086
DEFAULT_DATABASE = 'home_assistant'
DEFAULT_SSL = False
DEFAULT_VERIFY_SSL = False
REQUIREMENTS = ['influxdb==3.0.0'] REQUIREMENTS = ['influxdb==3.0.0']
CONF_HOST = 'host' _LOGGER = logging.getLogger(__name__)
CONF_PORT = 'port'
CONF_DB_NAME = 'database' CONF_DB_NAME = 'database'
CONF_USERNAME = 'username'
CONF_PASSWORD = 'password'
CONF_SSL = 'ssl'
CONF_VERIFY_SSL = 'verify_ssl'
CONF_BLACKLIST = 'blacklist'
CONF_WHITELIST = 'whitelist'
CONF_TAGS = 'tags' CONF_TAGS = 'tags'
DEFAULT_DATABASE = 'home_assistant'
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 8086
DEFAULT_SSL = False
DEFAULT_VERIFY_SSL = False
DOMAIN = 'influxdb'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_BLACKLIST, default=[]):
vol.All(cv.ensure_list, [cv.entity_id]),
vol.Optional(CONF_DB_NAME, default=DEFAULT_DATABASE): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_PORT, default=False): cv.boolean,
vol.Optional(CONF_SSL, default=False): cv.boolean,
vol.Optional(CONF_TAGS, default={}):
vol.Schema({cv.string: cv.string}),
vol.Optional(CONF_WHITELIST, default=[]):
vol.All(cv.ensure_list, [cv.entity_id]),
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
}),
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
def setup(hass, config): def setup(hass, config):
"""Setup the InfluxDB component.""" """Setup the InfluxDB component."""
from influxdb import InfluxDBClient, exceptions from influxdb import InfluxDBClient, exceptions
if not validate_config(config, {DOMAIN: ['host',
CONF_USERNAME,
CONF_PASSWORD]}, _LOGGER):
return False
conf = config[DOMAIN] conf = config[DOMAIN]
host = conf[CONF_HOST] host = conf.get(CONF_HOST)
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT) port = conf.get(CONF_PORT)
database = util.convert(conf.get(CONF_DB_NAME), str, DEFAULT_DATABASE) database = conf.get(CONF_DB_NAME)
username = util.convert(conf.get(CONF_USERNAME), str) username = conf.get(CONF_USERNAME)
password = util.convert(conf.get(CONF_PASSWORD), str) password = conf.get(CONF_PASSWORD)
ssl = util.convert(conf.get(CONF_SSL), bool, DEFAULT_SSL) ssl = conf.get(CONF_SSL)
verify_ssl = util.convert(conf.get(CONF_VERIFY_SSL), bool, verify_ssl = conf.get(CONF_VERIFY_SSL)
DEFAULT_VERIFY_SSL) blacklist = conf.get(CONF_BLACKLIST)
blacklist = conf.get(CONF_BLACKLIST, []) whitelist = conf.get(CONF_WHITELIST)
whitelist = conf.get(CONF_WHITELIST, []) tags = conf.get(CONF_TAGS)
tags = conf.get(CONF_TAGS, {})
try: try:
influx = InfluxDBClient(host=host, port=port, username=username, influx = InfluxDBClient(
password=password, database=database, host=host, port=port, username=username, password=password,
ssl=ssl, verify_ssl=verify_ssl) database=database, ssl=ssl, verify_ssl=verify_ssl)
influx.query("select * from /.*/ LIMIT 1;") influx.query("select * from /.*/ LIMIT 1;")
except exceptions.InfluxDBClientError as exc: except exceptions.InfluxDBClientError as exc:
_LOGGER.error("Database host is not accessible due to '%s', please " _LOGGER.error("Database host is not accessible due to '%s', please "
@ -106,8 +112,11 @@ def setup(hass, config):
} }
] ]
for tag in tags: for key, value in state.attributes.items():
json_body[0]['tags'][tag] = tags[tag] if key != 'unit_of_measurement':
json_body[0]['fields'][key] = value
json_body[0]['tags'].update(tags)
try: try:
influx.write_points(json_body) influx.write_points(json_body)

View File

@ -9,11 +9,11 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON) ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_TOGGLE, STATE_ON)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify
DOMAIN = 'input_boolean' DOMAIN = 'input_boolean'
@ -21,14 +21,19 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_NAME = "name" CONF_INITIAL = 'initial'
CONF_INITIAL = "initial"
CONF_ICON = "icon"
TOGGLE_SERVICE_SCHEMA = vol.Schema({ SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
}) })
CONFIG_SCHEMA = vol.Schema({DOMAIN: {
cv.slug: vol.Any({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_INITIAL, default=False): cv.boolean,
vol.Optional(CONF_ICON): cv.icon,
}, None)}}, extra=vol.ALLOW_EXTRA)
def is_on(hass, entity_id): def is_on(hass, entity_id):
"""Test if input_boolean is True.""" """Test if input_boolean is True."""
@ -45,21 +50,18 @@ def turn_off(hass, entity_id):
hass.services.call(DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}) hass.services.call(DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id})
def toggle(hass, entity_id):
"""Set input_boolean to False."""
hass.services.call(DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: entity_id})
def setup(hass, config): def setup(hass, config):
"""Set up input boolean.""" """Set up input boolean."""
if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False
component = EntityComponent(_LOGGER, DOMAIN, hass) component = EntityComponent(_LOGGER, DOMAIN, hass)
entities = [] entities = []
for object_id, cfg in config[DOMAIN].items(): for object_id, cfg in config[DOMAIN].items():
if object_id != slugify(object_id):
_LOGGER.warning("Found invalid key for boolean input: %s. "
"Use %s instead", object_id, slugify(object_id))
continue
if not cfg: if not cfg:
cfg = {} cfg = {}
@ -72,20 +74,24 @@ def setup(hass, config):
if not entities: if not entities:
return False return False
def toggle_service(service): def handler_service(service):
"""Handle a calls to the input boolean services.""" """Handle a calls to the input boolean services."""
target_inputs = component.extract_from_service(service) target_inputs = component.extract_from_service(service)
for input_b in target_inputs: for input_b in target_inputs:
if service.service == SERVICE_TURN_ON: if service.service == SERVICE_TURN_ON:
input_b.turn_on() input_b.turn_on()
else: elif service.service == SERVICE_TURN_OFF:
input_b.turn_off() input_b.turn_off()
else:
input_b.toggle()
hass.services.register(DOMAIN, SERVICE_TURN_OFF, toggle_service, hass.services.register(DOMAIN, SERVICE_TURN_OFF, handler_service,
schema=TOGGLE_SERVICE_SCHEMA) schema=SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_TURN_ON, toggle_service, hass.services.register(DOMAIN, SERVICE_TURN_ON, handler_service,
schema=TOGGLE_SERVICE_SCHEMA) schema=SERVICE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_TOGGLE, handler_service,
schema=SERVICE_SCHEMA)
component.add_entities(entities) component.add_entities(entities)

View File

@ -8,19 +8,17 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID from homeassistant.const import ATTR_ENTITY_ID, CONF_ICON, CONF_NAME
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify
DOMAIN = 'input_select' DOMAIN = 'input_select'
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_NAME = 'name'
CONF_INITIAL = 'initial' CONF_INITIAL = 'initial'
CONF_ICON = 'icon'
CONF_OPTIONS = 'options' CONF_OPTIONS = 'options'
ATTR_OPTION = 'option' ATTR_OPTION = 'option'
@ -34,6 +32,26 @@ SERVICE_SELECT_OPTION_SCHEMA = vol.Schema({
}) })
def _cv_input_select(cfg):
"""Config validation helper for input select (Voluptuous)."""
options = cfg[CONF_OPTIONS]
state = cfg.get(CONF_INITIAL, options[0])
if state not in options:
raise vol.Invalid('initial state "{}" is not part of the options: {}'
.format(state, ','.join(options)))
return cfg
CONFIG_SCHEMA = vol.Schema({DOMAIN: {
cv.slug: vol.All({
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_OPTIONS): vol.All(cv.ensure_list, vol.Length(min=1),
[cv.string]),
vol.Optional(CONF_INITIAL): cv.string,
vol.Optional(CONF_ICON): cv.icon,
}, _cv_input_select)}}, required=True, extra=vol.ALLOW_EXTRA)
def select_option(hass, entity_id, option): def select_option(hass, entity_id, option):
"""Set input_select to False.""" """Set input_select to False."""
hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, { hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, {
@ -44,39 +62,15 @@ def select_option(hass, entity_id, option):
def setup(hass, config): def setup(hass, config):
"""Setup input select.""" """Setup input select."""
if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False
component = EntityComponent(_LOGGER, DOMAIN, hass) component = EntityComponent(_LOGGER, DOMAIN, hass)
entities = [] entities = []
for object_id, cfg in config[DOMAIN].items(): for object_id, cfg in config[DOMAIN].items():
if object_id != slugify(object_id):
_LOGGER.warning("Found invalid key for boolean input: %s. "
"Use %s instead", object_id, slugify(object_id))
continue
if not cfg:
_LOGGER.warning("No configuration specified for %s", object_id)
continue
name = cfg.get(CONF_NAME) name = cfg.get(CONF_NAME)
options = cfg.get(CONF_OPTIONS) options = cfg.get(CONF_OPTIONS)
state = cfg.get(CONF_INITIAL, options[0])
if not isinstance(options, list) or len(options) == 0:
_LOGGER.warning('Key %s should be a list of options', CONF_OPTIONS)
continue
options = [str(val) for val in options]
state = cfg.get(CONF_INITIAL)
if state not in options:
state = options[0]
icon = cfg.get(CONF_ICON) icon = cfg.get(CONF_ICON)
entities.append(InputSelect(object_id, name, state, options, icon)) entities.append(InputSelect(object_id, name, state, options, icon))
if not entities: if not entities:

View File

@ -8,21 +8,19 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, CONF_ICON, CONF_NAME)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import slugify
DOMAIN = 'input_slider' DOMAIN = 'input_slider'
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_NAME = 'name'
CONF_INITIAL = 'initial' CONF_INITIAL = 'initial'
CONF_MIN = 'min' CONF_MIN = 'min'
CONF_MAX = 'max' CONF_MAX = 'max'
CONF_ICON = 'icon'
CONF_STEP = 'step' CONF_STEP = 'step'
ATTR_VALUE = 'value' ATTR_VALUE = 'value'
@ -38,6 +36,33 @@ SERVICE_SELECT_VALUE_SCHEMA = vol.Schema({
}) })
def _cv_input_slider(cfg):
"""Config validation helper for input slider (Voluptuous)."""
minimum = cfg.get(CONF_MIN)
maximum = cfg.get(CONF_MAX)
if minimum >= maximum:
raise vol.Invalid('Maximum ({}) is not greater than minimum ({})'
.format(minimum, maximum))
state = cfg.get(CONF_INITIAL, minimum)
if state < minimum or state > maximum:
raise vol.Invalid('Initial value {} not in range {}-{}'
.format(state, minimum, maximum))
cfg[CONF_INITIAL] = state
return cfg
CONFIG_SCHEMA = vol.Schema({DOMAIN: {
cv.slug: vol.All({
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_MIN): vol.Coerce(float),
vol.Required(CONF_MAX): vol.Coerce(float),
vol.Optional(CONF_INITIAL): vol.Coerce(float),
vol.Optional(CONF_STEP, default=1): vol.All(vol.Coerce(float),
vol.Range(min=1e-3)),
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string
}, _cv_input_slider)}}, required=True, extra=vol.ALLOW_EXTRA)
def select_value(hass, entity_id, value): def select_value(hass, entity_id, value):
"""Set input_slider to value.""" """Set input_slider to value."""
hass.services.call(DOMAIN, SERVICE_SELECT_VALUE, { hass.services.call(DOMAIN, SERVICE_SELECT_VALUE, {
@ -48,36 +73,19 @@ def select_value(hass, entity_id, value):
def setup(hass, config): def setup(hass, config):
"""Set up input slider.""" """Set up input slider."""
if not isinstance(config.get(DOMAIN), dict):
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
return False
component = EntityComponent(_LOGGER, DOMAIN, hass) component = EntityComponent(_LOGGER, DOMAIN, hass)
entities = [] entities = []
for object_id, cfg in config[DOMAIN].items(): for object_id, cfg in config[DOMAIN].items():
if object_id != slugify(object_id):
_LOGGER.warning("Found invalid key for boolean input: %s. "
"Use %s instead", object_id, slugify(object_id))
continue
if not cfg:
_LOGGER.warning("No configuration specified for %s", object_id)
continue
name = cfg.get(CONF_NAME) name = cfg.get(CONF_NAME)
minimum = cfg.get(CONF_MIN) minimum = cfg.get(CONF_MIN)
maximum = cfg.get(CONF_MAX) maximum = cfg.get(CONF_MAX)
state = cfg.get(CONF_INITIAL, minimum) state = cfg.get(CONF_INITIAL, minimum)
step = cfg.get(CONF_STEP, 1) step = cfg.get(CONF_STEP)
icon = cfg.get(CONF_ICON) icon = cfg.get(CONF_ICON)
unit = cfg.get(ATTR_UNIT_OF_MEASUREMENT) unit = cfg.get(ATTR_UNIT_OF_MEASUREMENT)
if state < minimum:
state = minimum
if state > maximum:
state = maximum
entities.append(InputSlider(object_id, name, state, minimum, maximum, entities.append(InputSlider(object_id, name, state, minimum, maximum,
step, icon, unit)) step, icon, unit))

View File

@ -6,26 +6,33 @@ https://home-assistant.io/components/insteon_hub/
""" """
import logging import logging
from homeassistant.const import CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME import voluptuous as vol
from homeassistant.helpers import validate_config, discovery
from homeassistant.const import (CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME)
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
DOMAIN = "insteon_hub"
REQUIREMENTS = ['insteon_hub==0.4.5'] REQUIREMENTS = ['insteon_hub==0.4.5']
INSTEON = None
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = 'insteon_hub'
INSTEON = None
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
})
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config): def setup(hass, config):
"""Setup Insteon Hub component. """Setup Insteon Hub component.
This will automatically import associated lights. This will automatically import associated lights.
""" """
if not validate_config(
config,
{DOMAIN: [CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY]},
_LOGGER):
return False
import insteon import insteon
username = config[DOMAIN][CONF_USERNAME] username = config[DOMAIN][CONF_USERNAME]
@ -36,8 +43,8 @@ def setup(hass, config):
INSTEON = insteon.Insteon(username, password, api_key) INSTEON = insteon.Insteon(username, password, api_key)
if INSTEON is None: if INSTEON is None:
_LOGGER.error("Could not connect to Insteon service.") _LOGGER.error("Could not connect to Insteon service")
return return False
discovery.load_platform(hass, 'light', DOMAIN, {}, config) discovery.load_platform(hass, 'light', DOMAIN, {}, config)

View File

@ -6,43 +6,150 @@ https://home-assistant.io/components/isy994/
""" """
import logging import logging
from urllib.parse import urlparse from urllib.parse import urlparse
import voluptuous as vol
from homeassistant.core import HomeAssistant # noqa
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_HOST, CONF_PASSWORD, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP) EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import validate_config, discovery from homeassistant.helpers import discovery, config_validation as cv
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType, Dict # noqa
DOMAIN = "isy994" DOMAIN = "isy994"
REQUIREMENTS = ['PyISY==1.0.6'] REQUIREMENTS = ['PyISY==1.0.7']
ISY = None ISY = None
SENSOR_STRING = 'Sensor' DEFAULT_SENSOR_STRING = 'sensor'
HIDDEN_STRING = '{HIDE ME}' DEFAULT_HIDDEN_STRING = '{HIDE ME}'
CONF_TLS_VER = 'tls' CONF_TLS_VER = 'tls'
CONF_HIDDEN_STRING = 'hidden_string'
CONF_SENSOR_STRING = 'sensor_string'
KEY_MY_PROGRAMS = 'My Programs'
KEY_FOLDER = 'folder'
KEY_ACTIONS = 'actions'
KEY_STATUS = 'status'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.url,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_TLS_VER): vol.Coerce(float),
vol.Optional(CONF_HIDDEN_STRING,
default=DEFAULT_HIDDEN_STRING): cv.string,
vol.Optional(CONF_SENSOR_STRING,
default=DEFAULT_SENSOR_STRING): cv.string
})
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config): SENSOR_NODES = []
"""Setup ISY994 component. NODES = []
GROUPS = []
PROGRAMS = {}
This will automatically import associated lights, switches, and sensors. PYISY = None
"""
import PyISY
# pylint: disable=global-statement HIDDEN_STRING = DEFAULT_HIDDEN_STRING
# check for required values in configuration file
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return False
# Pull and parse standard configuration. SUPPORTED_DOMAINS = ['binary_sensor', 'cover', 'fan', 'light', 'lock',
user = config[DOMAIN][CONF_USERNAME] 'sensor', 'switch']
password = config[DOMAIN][CONF_PASSWORD]
host = urlparse(config[DOMAIN][CONF_HOST])
def filter_nodes(nodes: list, units: list=None, states: list=None) -> list:
"""Filter a list of ISY nodes based on the units and states provided."""
filtered_nodes = []
units = units if units else []
states = states if states else []
for node in nodes:
match_unit = False
match_state = True
for uom in node.uom:
if uom in units:
match_unit = True
continue
elif uom not in states:
match_state = False
if match_unit:
continue
if match_unit or match_state:
filtered_nodes.append(node)
return filtered_nodes
def _categorize_nodes(hidden_identifier: str, sensor_identifier: str) -> None:
"""Categorize the ISY994 nodes."""
global SENSOR_NODES
global NODES
global GROUPS
SENSOR_NODES = []
NODES = []
GROUPS = []
for (path, node) in ISY.nodes:
hidden = hidden_identifier in path or hidden_identifier in node.name
if hidden:
node.name += hidden_identifier
if sensor_identifier in path or sensor_identifier in node.name:
SENSOR_NODES.append(node)
elif isinstance(node, PYISY.Nodes.Node): # pylint: disable=no-member
NODES.append(node)
elif isinstance(node, PYISY.Nodes.Group): # pylint: disable=no-member
GROUPS.append(node)
def _categorize_programs() -> None:
"""Categorize the ISY994 programs."""
global PROGRAMS
PROGRAMS = {}
for component in SUPPORTED_DOMAINS:
try:
folder = ISY.programs[KEY_MY_PROGRAMS]['HA.' + component]
except KeyError:
pass
else:
for dtype, _, node_id in folder.children:
if dtype is KEY_FOLDER:
program = folder[node_id]
try:
node = program[KEY_STATUS].leaf
assert node.dtype == 'program', 'Not a program'
except (KeyError, AssertionError):
pass
else:
if component not in PROGRAMS:
PROGRAMS[component] = []
PROGRAMS[component].append(program)
# pylint: disable=too-many-locals
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the ISY 994 platform."""
isy_config = config.get(DOMAIN)
user = isy_config.get(CONF_USERNAME)
password = isy_config.get(CONF_PASSWORD)
tls_version = isy_config.get(CONF_TLS_VER)
host = urlparse(isy_config.get(CONF_HOST))
port = host.port
addr = host.geturl() addr = host.geturl()
hidden_identifier = isy_config.get(CONF_HIDDEN_STRING,
DEFAULT_HIDDEN_STRING)
sensor_identifier = isy_config.get(CONF_SENSOR_STRING,
DEFAULT_SENSOR_STRING)
global HIDDEN_STRING
HIDDEN_STRING = hidden_identifier
if host.scheme == 'http': if host.scheme == 'http':
addr = addr.replace('http://', '') addr = addr.replace('http://', '')
https = False https = False
@ -50,169 +157,125 @@ def setup(hass, config):
addr = addr.replace('https://', '') addr = addr.replace('https://', '')
https = True https = True
else: else:
_LOGGER.error('isy994 host value in configuration file is invalid.') _LOGGER.error('isy994 host value in configuration is invalid.')
return False return False
port = host.port
addr = addr.replace(':{}'.format(port), '') addr = addr.replace(':{}'.format(port), '')
# Pull and parse optional configuration. import PyISY
global SENSOR_STRING
global HIDDEN_STRING global PYISY
SENSOR_STRING = str(config[DOMAIN].get('sensor_string', SENSOR_STRING)) PYISY = PyISY
HIDDEN_STRING = str(config[DOMAIN].get('hidden_string', HIDDEN_STRING))
tls_version = config[DOMAIN].get(CONF_TLS_VER, None)
# Connect to ISY controller. # Connect to ISY controller.
global ISY global ISY
ISY = PyISY.ISY(addr, port, user, password, use_https=https, ISY = PyISY.ISY(addr, port, username=user, password=password,
tls_ver=tls_version, log=_LOGGER) use_https=https, tls_ver=tls_version, log=_LOGGER)
if not ISY.connected: if not ISY.connected:
return False return False
_categorize_nodes(hidden_identifier, sensor_identifier)
_categorize_programs()
# Listen for HA stop to disconnect. # Listen for HA stop to disconnect.
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop)
# Load platforms for the devices in the ISY controller that we support. # Load platforms for the devices in the ISY controller that we support.
for component in ('sensor', 'light', 'switch'): for component in SUPPORTED_DOMAINS:
discovery.load_platform(hass, component, DOMAIN, {}, config) discovery.load_platform(hass, component, DOMAIN, {}, config)
ISY.auto_update = True ISY.auto_update = True
return True return True
def stop(event): # pylint: disable=unused-argument
"""Cleanup the ISY subscription.""" def stop(event: object) -> None:
"""Stop ISY auto updates."""
ISY.auto_update = False ISY.auto_update = False
class ISYDeviceABC(ToggleEntity): class ISYDevice(Entity):
"""An abstract Class for an ISY device.""" """Representation of an ISY994 device."""
_attrs = {} _attrs = {}
_onattrs = [] _domain = None # type: str
_states = [] _name = None # type: str
_dtype = None
_domain = None
_name = None
def __init__(self, node): def __init__(self, node) -> None:
"""Initialize the device.""" """Initialize the insteon device."""
# setup properties self._node = node
self.node = node
# track changes self._change_handler = self._node.status.subscribe('changed',
self._change_handler = self.node.status. \ self.on_update)
subscribe('changed', self.on_update)
def __del__(self): def __del__(self) -> None:
"""Cleanup subscriptions because it is the right thing to do.""" """Cleanup the subscriptions."""
self._change_handler.unsubscribe() self._change_handler.unsubscribe()
# pylint: disable=unused-argument
def on_update(self, event: object) -> None:
"""Handle the update event from the ISY994 Node."""
self.update_ha_state()
@property @property
def domain(self): def domain(self) -> str:
"""Return the domain of the entity.""" """Get the domain of the device."""
return self._domain return self._domain
@property @property
def dtype(self): def unique_id(self) -> str:
"""Return the data type of the entity (binary or analog).""" """Get the unique identifier of the device."""
if self._dtype in ['analog', 'binary']:
return self._dtype
return 'binary' if self.unit_of_measurement is None else 'analog'
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def value(self):
"""Return the unclean value from the controller."""
# pylint: disable=protected-access # pylint: disable=protected-access
return self.node.status._val return self._node._id
@property @property
def state_attributes(self): def raw_name(self) -> str:
"""Return the state attributes for the node.""" """Get the raw name of the device."""
attr = {}
for name, prop in self._attrs.items():
attr[name] = getattr(self, prop)
attr = self._attr_filter(attr)
return attr
def _attr_filter(self, attr):
"""A Placeholder for attribute filters."""
# pylint: disable=no-self-use
return attr
@property
def unique_id(self):
"""Return the ID of this ISY sensor."""
# pylint: disable=protected-access
return self.node._id
@property
def raw_name(self):
"""Return the unclean node name."""
return str(self._name) \ return str(self._name) \
if self._name is not None else str(self.node.name) if self._name is not None else str(self._node.name)
@property @property
def name(self): def name(self) -> str:
"""Return the cleaned name of the node.""" """Get the name of the device."""
return self.raw_name.replace(HIDDEN_STRING, '').strip() \ return self.raw_name.replace(HIDDEN_STRING, '').strip() \
.replace('_', ' ') .replace('_', ' ')
@property @property
def hidden(self): def should_poll(self) -> bool:
"""Suggestion if the entity should be hidden from UIs.""" """No polling required since we're using the subscription."""
return False
@property
def value(self) -> object:
"""Get the current value of the device."""
# pylint: disable=protected-access
return self._node.status._val
@property
def state_attributes(self) -> Dict:
"""Get the state attributes for the device."""
attr = {}
if hasattr(self._node, 'aux_properties'):
for name, val in self._node.aux_properties.items():
attr[name] = '{} {}'.format(val.get('value'), val.get('uom'))
return attr
@property
def hidden(self) -> bool:
"""Get whether the device should be hidden from the UI."""
return HIDDEN_STRING in self.raw_name return HIDDEN_STRING in self.raw_name
def update(self):
"""Update state of the sensor."""
# ISY objects are automatically updated by the ISY's event stream
pass
def on_update(self, event):
"""Handle the update received event."""
self.update_ha_state()
@property @property
def is_on(self): def unit_of_measurement(self) -> str:
"""Return a boolean response if the node is on.""" """Get the device unit of measure."""
return bool(self.value)
@property
def is_open(self):
"""Return boolean response if the node is open. On = Open."""
return self.is_on
@property
def state(self):
"""Return the state of the node."""
if len(self._states) > 0:
return self._states[0] if self.is_on else self._states[1]
return self.value
def turn_on(self, **kwargs):
"""Turn the device on."""
if self.domain is not 'sensor':
attrs = [kwargs.get(name) for name in self._onattrs]
self.node.on(*attrs)
else:
_LOGGER.error('ISY cannot turn on sensors.')
def turn_off(self, **kwargs):
"""Turn the device off."""
if self.domain is not 'sensor':
self.node.off()
else:
_LOGGER.error('ISY cannot turn off sensors.')
@property
def unit_of_measurement(self):
"""Return the defined units of measurement or None."""
try:
return self.node.units
except AttributeError:
return None return None
def _attr_filter(self, attr: str) -> str:
"""Filter the attribute."""
# pylint: disable=no-self-use
return attr
def update(self) -> None:
"""Perform an update for the device."""
pass

View File

@ -50,6 +50,7 @@ def media_prev_track(hass):
def setup(hass, config): def setup(hass, config):
"""Listen for keyboard events.""" """Listen for keyboard events."""
# pylint: disable=import-error
import pykeyboard import pykeyboard
keyboard = pykeyboard.PyKeyboard() keyboard = pykeyboard.PyKeyboard()

View File

@ -0,0 +1,135 @@
"""
Recieve signals from a keyboard and use it as a remote control.
This component allows to use a keyboard as remote control. It will
fire ´keyboard_remote_command_received´ events witch can then be used
in automation rules.
The `evdev` package is used to interface with the keyboard and thus this
is Linux only. It also means you can't use your normal keyboard for this,
because `evdev` will block it.
Example:
keyboard_remote:
device_descriptor: '/dev/input/by-id/foo'
key_value: 'key_up' # optional alternaive 'key_down' and 'key_hold'
# be carefull, 'key_hold' fires a lot of events
and an automation rule to bring breath live into it.
automation:
alias: Keyboard All light on
trigger:
platform: event
event_type: keyboard_remote_command_received
event_data:
key_code: 107 # inspect log to obtain desired keycode
action:
service: light.turn_on
entity_id: light.all
"""
# pylint: disable=import-error
import threading
import logging
import os
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP
)
DOMAIN = "keyboard_remote"
REQUIREMENTS = ['evdev==0.6.1']
_LOGGER = logging.getLogger(__name__)
ICON = 'mdi:remote'
KEYBOARD_REMOTE_COMMAND_RECEIVED = 'keyboard_remote_command_received'
KEY_CODE = 'key_code'
KEY_VALUE = {'key_up': 0, 'key_down': 1, 'key_hold': 2}
TYPE = 'type'
DEVICE_DESCRIPTOR = 'device_descriptor'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(DEVICE_DESCRIPTOR): cv.string,
vol.Optional(TYPE, default='key_up'):
vol.All(cv.string, vol.Any('key_up', 'key_down', 'key_hold')),
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Setup keyboard_remote."""
config = config.get(DOMAIN)
device_descriptor = config.get(DEVICE_DESCRIPTOR)
if not device_descriptor or not os.path.isfile(device_descriptor):
id_folder = '/dev/input/by-id/'
_LOGGER.error(
'A device_descriptor must be defined. '
'Possible descriptors are %s:\n%s',
id_folder, os.listdir(id_folder)
)
return
key_value = KEY_VALUE.get(config.get(TYPE, 'key_up'))
keyboard_remote = KeyboardRemote(
hass,
device_descriptor,
key_value
)
def _start_keyboard_remote(_event):
keyboard_remote.run()
def _stop_keyboard_remote(_event):
keyboard_remote.stopped.set()
hass.bus.listen_once(
EVENT_HOMEASSISTANT_START,
_start_keyboard_remote
)
hass.bus.listen_once(
EVENT_HOMEASSISTANT_STOP,
_stop_keyboard_remote
)
return True
class KeyboardRemote(threading.Thread):
"""This interfaces with the inputdevice using evdev."""
def __init__(self, hass, device_descriptor, key_value):
"""Construct a KeyboardRemote interface object."""
from evdev import InputDevice
self.dev = InputDevice(device_descriptor)
threading.Thread.__init__(self)
self.stopped = threading.Event()
self.hass = hass
self.key_value = key_value
def run(self):
"""Main loop of the KeyboardRemote."""
from evdev import categorize, ecodes
_LOGGER.debug('KeyboardRemote interface started for %s', self.dev)
self.dev.grab()
while not self.stopped.isSet():
event = self.dev.read_one()
if not event:
continue
# pylint: disable=no-member
if event.type is ecodes.EV_KEY and event.value is self.key_value:
_LOGGER.debug(categorize(event))
self.hass.bus.fire(
KEYBOARD_REMOTE_COMMAND_RECEIVED,
{KEY_CODE: event.code}
)

View File

@ -6,22 +6,31 @@ https://home-assistant.io/components/knx/
""" """
import logging import logging
from homeassistant.const import EVENT_HOMEASSISTANT_STOP import voluptuous as vol
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT)
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
DOMAIN = "knx"
REQUIREMENTS = ['knxip==0.3.3'] REQUIREMENTS = ['knxip==0.3.3']
EVENT_KNX_FRAME_RECEIVED = "knx_frame_received" _LOGGER = logging.getLogger(__name__)
CONF_HOST = "host" DEFAULT_HOST = '0.0.0.0'
CONF_PORT = "port" DEFAULT_PORT = '3671'
DOMAIN = 'knx'
DEFAULT_PORT = "3671" EVENT_KNX_FRAME_RECEIVED = 'knx_frame_received'
KNXTUNNEL = None KNXTUNNEL = None
_LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config): def setup(hass, config):
@ -31,17 +40,11 @@ def setup(hass, config):
from knxip.ip import KNXIPTunnel from knxip.ip import KNXIPTunnel
from knxip.core import KNXException from knxip.core import KNXException
host = config[DOMAIN].get(CONF_HOST, None) host = config[DOMAIN].get(CONF_HOST)
port = config[DOMAIN].get(CONF_PORT)
if host is None: if host is '0.0.0.0':
_LOGGER.debug("Will try to auto-detect KNX/IP gateway") _LOGGER.debug("Will try to auto-detect KNX/IP gateway")
host = "0.0.0.0"
try:
port = int(config[DOMAIN].get(CONF_PORT, DEFAULT_PORT))
except ValueError:
_LOGGER.exception("Can't parse KNX IP interface port")
return False
KNXTUNNEL = KNXIPTunnel(host, port) KNXTUNNEL = KNXIPTunnel(host, port)
try: try:
@ -78,21 +81,21 @@ class KNXConfig(object):
from knxip.core import parse_group_address from knxip.core import parse_group_address
self.config = config self.config = config
self.should_poll = config.get("poll", True) self.should_poll = config.get('poll', True)
if config.get("address"): if config.get('address'):
self._address = parse_group_address(config.get("address")) self._address = parse_group_address(config.get('address'))
else: else:
self._address = None self._address = None
if self.config.get("state_address"): if self.config.get('state_address'):
self._state_address = parse_group_address( self._state_address = parse_group_address(
self.config.get("state_address")) self.config.get('state_address'))
else: else:
self._state_address = None self._state_address = None
@property @property
def name(self): def name(self):
"""The name given to the entity.""" """The name given to the entity."""
return self.config["name"] return self.config['name']
@property @property
def address(self): def address(self):
@ -175,7 +178,7 @@ class KNXGroupAddress(Entity):
@property @property
def cache(self): def cache(self):
"""The name given to the entity.""" """The name given to the entity."""
return self._config.config.get("cache", True) return self._config.config.get('cache', True)
def group_write(self, value): def group_write(self, value):
"""Write to the group address.""" """Write to the group address."""
@ -187,22 +190,21 @@ class KNXGroupAddress(Entity):
try: try:
if self.state_address: if self.state_address:
res = KNXTUNNEL.group_read(self.state_address, res = KNXTUNNEL.group_read(
use_cache=self.cache) self.state_address, use_cache=self.cache)
else: else:
res = KNXTUNNEL.group_read(self.address, res = KNXTUNNEL.group_read(self.address, use_cache=self.cache)
use_cache=self.cache)
if res: if res:
self._state = res[0] self._state = res[0]
self._data = res self._data = res
else: else:
_LOGGER.debug("Unable to read from KNX address: %s (None)", _LOGGER.debug(
self.address) "Unable to read from KNX address: %s (None)", self.address)
except KNXException: except KNXException:
_LOGGER.exception("Unable to read from KNX address: %s", _LOGGER.exception(
self.address) "Unable to read from KNX address: %s", self.address)
return False return False
@ -234,19 +236,19 @@ class KNXMultiAddressDevice(Entity):
# parse required addresses # parse required addresses
for name in required: for name in required:
_LOGGER.info(name) _LOGGER.info(name)
paramname = name + "_address" paramname = '{}{}'.format(name, '_address')
addr = self._config.config.get(paramname) addr = self._config.config.get(paramname)
if addr is None: if addr is None:
_LOGGER.exception("Required KNX group address %s missing", _LOGGER.exception(
paramname) "Required KNX group address %s missing", paramname)
raise KNXException("Group address for %s missing " raise KNXException(
"in configuration", paramname) "Group address for %s missing in configuration", paramname)
addr = parse_group_address(addr) addr = parse_group_address(addr)
self.names[addr] = name self.names[addr] = name
# parse optional addresses # parse optional addresses
for name in optional: for name in optional:
paramname = name + "_address" paramname = '{}{}'.format(name, '_address')
addr = self._config.config.get(paramname) addr = self._config.config.get(paramname)
if addr: if addr:
try: try:
@ -273,7 +275,7 @@ class KNXMultiAddressDevice(Entity):
@property @property
def cache(self): def cache(self):
"""The name given to the entity.""" """The name given to the entity."""
return self._config.config.get("cache", True) return self._config.config.get('cache', True)
def has_attribute(self, name): def has_attribute(self, name):
"""Check if the attribute with the given name is defined. """Check if the attribute with the given name is defined.
@ -301,8 +303,7 @@ class KNXMultiAddressDevice(Entity):
try: try:
res = KNXTUNNEL.group_read(addr, use_cache=self.cache) res = KNXTUNNEL.group_read(addr, use_cache=self.cache)
except KNXException: except KNXException:
_LOGGER.exception("Unable to read from KNX address: %s", _LOGGER.exception("Unable to read from KNX address: %s", addr)
addr)
return False return False
return res return res
@ -323,8 +324,7 @@ class KNXMultiAddressDevice(Entity):
try: try:
KNXTUNNEL.group_write(addr, value) KNXTUNNEL.group_write(addr, value)
except KNXException: except KNXException:
_LOGGER.exception("Unable to write to KNX address: %s", _LOGGER.exception("Unable to write to KNX address: %s", addr)
addr)
return False return False
return True return True

View File

@ -39,6 +39,7 @@ SUPPORT_FLASH = 8
SUPPORT_RGB_COLOR = 16 SUPPORT_RGB_COLOR = 16
SUPPORT_TRANSITION = 32 SUPPORT_TRANSITION = 32
SUPPORT_XY_COLOR = 64 SUPPORT_XY_COLOR = 64
SUPPORT_WHITE_VALUE = 128
# Integer that represents transition time in seconds to make change. # Integer that represents transition time in seconds to make change.
ATTR_TRANSITION = "transition" ATTR_TRANSITION = "transition"
@ -48,6 +49,7 @@ ATTR_RGB_COLOR = "rgb_color"
ATTR_XY_COLOR = "xy_color" ATTR_XY_COLOR = "xy_color"
ATTR_COLOR_TEMP = "color_temp" ATTR_COLOR_TEMP = "color_temp"
ATTR_COLOR_NAME = "color_name" ATTR_COLOR_NAME = "color_name"
ATTR_WHITE_VALUE = "white_value"
# int with value 0 .. 255 representing brightness of the light. # int with value 0 .. 255 representing brightness of the light.
ATTR_BRIGHTNESS = "brightness" ATTR_BRIGHTNESS = "brightness"
@ -73,6 +75,7 @@ PROP_TO_ATTR = {
'color_temp': ATTR_COLOR_TEMP, 'color_temp': ATTR_COLOR_TEMP,
'rgb_color': ATTR_RGB_COLOR, 'rgb_color': ATTR_RGB_COLOR,
'xy_color': ATTR_XY_COLOR, 'xy_color': ATTR_XY_COLOR,
'white_value': ATTR_WHITE_VALUE,
'supported_features': ATTR_SUPPORTED_FEATURES, 'supported_features': ATTR_SUPPORTED_FEATURES,
} }
@ -91,6 +94,7 @@ LIGHT_TURN_ON_SCHEMA = vol.Schema({
ATTR_XY_COLOR: vol.All(vol.ExactSequence((cv.small_float, cv.small_float)), ATTR_XY_COLOR: vol.All(vol.ExactSequence((cv.small_float, cv.small_float)),
vol.Coerce(tuple)), vol.Coerce(tuple)),
ATTR_COLOR_TEMP: vol.All(int, vol.Range(min=154, max=500)), ATTR_COLOR_TEMP: vol.All(int, vol.Range(min=154, max=500)),
ATTR_WHITE_VALUE: vol.All(int, vol.Range(min=0, max=255)),
ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]), ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]),
ATTR_EFFECT: vol.In([EFFECT_COLORLOOP, EFFECT_RANDOM, EFFECT_WHITE]), ATTR_EFFECT: vol.In([EFFECT_COLORLOOP, EFFECT_RANDOM, EFFECT_WHITE]),
}) })
@ -121,8 +125,8 @@ def is_on(hass, entity_id=None):
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def turn_on(hass, entity_id=None, transition=None, brightness=None, def turn_on(hass, entity_id=None, transition=None, brightness=None,
rgb_color=None, xy_color=None, color_temp=None, profile=None, rgb_color=None, xy_color=None, color_temp=None, white_value=None,
flash=None, effect=None, color_name=None): profile=None, flash=None, effect=None, color_name=None):
"""Turn all or specified light on.""" """Turn all or specified light on."""
data = { data = {
key: value for key, value in [ key: value for key, value in [
@ -133,6 +137,7 @@ def turn_on(hass, entity_id=None, transition=None, brightness=None,
(ATTR_RGB_COLOR, rgb_color), (ATTR_RGB_COLOR, rgb_color),
(ATTR_XY_COLOR, xy_color), (ATTR_XY_COLOR, xy_color),
(ATTR_COLOR_TEMP, color_temp), (ATTR_COLOR_TEMP, color_temp),
(ATTR_WHITE_VALUE, white_value),
(ATTR_FLASH, flash), (ATTR_FLASH, flash),
(ATTR_EFFECT, effect), (ATTR_EFFECT, effect),
(ATTR_COLOR_NAME, color_name), (ATTR_COLOR_NAME, color_name),
@ -283,6 +288,11 @@ class Light(ToggleEntity):
"""Return the CT color value in mireds.""" """Return the CT color value in mireds."""
return None return None
@property
def white_value(self):
"""Return the white value of this light between 0..255."""
return None
@property @property
def state_attributes(self): def state_attributes(self):
"""Return optional state attributes.""" """Return optional state attributes."""

View File

@ -8,14 +8,19 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.light import (ATTR_RGB_COLOR, SUPPORT_RGB_COLOR, from homeassistant.components.light import (
Light, PLATFORM_SCHEMA) ATTR_RGB_COLOR, SUPPORT_RGB_COLOR, Light, PLATFORM_SCHEMA)
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['blinkstick==1.1.8']
_LOGGER = logging.getLogger(__name__)
CONF_SERIAL = 'serial' CONF_SERIAL = 'serial'
DEFAULT_NAME = 'Blinkstick' DEFAULT_NAME = 'Blinkstick'
REQUIREMENTS = ["blinkstick==1.1.8"]
SUPPORT_BLINKSTICK = SUPPORT_RGB_COLOR SUPPORT_BLINKSTICK = SUPPORT_RGB_COLOR
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -23,8 +28,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}) })
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):

View File

@ -7,8 +7,9 @@ https://home-assistant.io/components/demo/
import random import random
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_WHITE_VALUE,
SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, Light) ATTR_XY_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR,
SUPPORT_WHITE_VALUE, Light)
LIGHT_COLORS = [ LIGHT_COLORS = [
[237, 224, 33], [237, 224, 33],
@ -17,7 +18,8 @@ LIGHT_COLORS = [
LIGHT_TEMPS = [240, 380] LIGHT_TEMPS = [240, 380]
SUPPORT_DEMO = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_RGB_COLOR SUPPORT_DEMO = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_RGB_COLOR |
SUPPORT_WHITE_VALUE)
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
@ -33,13 +35,17 @@ class DemoLight(Light):
"""Represenation of a demo light.""" """Represenation of a demo light."""
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, name, state, rgb=None, ct=None, brightness=180): def __init__(
self, name, state, rgb=None, ct=None, brightness=180,
xy_color=(.5, .5), white=200):
"""Initialize the light.""" """Initialize the light."""
self._name = name self._name = name
self._state = state self._state = state
self._rgb = rgb or random.choice(LIGHT_COLORS) self._rgb = rgb
self._ct = ct or random.choice(LIGHT_TEMPS) self._ct = ct or random.choice(LIGHT_TEMPS)
self._brightness = brightness self._brightness = brightness
self._xy_color = xy_color
self._white = white
@property @property
def should_poll(self): def should_poll(self):
@ -56,6 +62,11 @@ class DemoLight(Light):
"""Return the brightness of this light between 0..255.""" """Return the brightness of this light between 0..255."""
return self._brightness return self._brightness
@property
def xy_color(self):
"""Return the XY color value [float, float]."""
return self._xy_color
@property @property
def rgb_color(self): def rgb_color(self):
"""Return the RBG color value.""" """Return the RBG color value."""
@ -66,6 +77,11 @@ class DemoLight(Light):
"""Return the CT color temperature.""" """Return the CT color temperature."""
return self._ct return self._ct
@property
def white_value(self):
"""Return the white value of this light between 0..255."""
return self._white
@property @property
def is_on(self): def is_on(self):
"""Return true if light is on.""" """Return true if light is on."""
@ -89,6 +105,12 @@ class DemoLight(Light):
if ATTR_BRIGHTNESS in kwargs: if ATTR_BRIGHTNESS in kwargs:
self._brightness = kwargs[ATTR_BRIGHTNESS] self._brightness = kwargs[ATTR_BRIGHTNESS]
if ATTR_XY_COLOR in kwargs:
self._xy_color = kwargs[ATTR_XY_COLOR]
if ATTR_WHITE_VALUE in kwargs:
self._white = kwargs[ATTR_WHITE_VALUE]
self.update_ha_state() self.update_ha_state()
def turn_off(self, **kwargs): def turn_off(self, **kwargs):

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