mirror of
https://github.com/home-assistant/core.git
synced 2025-08-03 18:48:22 +00:00
Merge pull request #3486 from home-assistant/dev
0.29 - 🎈 anniversary edition
This commit is contained in:
commit
c4d817146f
19
.coveragerc
19
.coveragerc
@ -93,19 +93,18 @@ omit =
|
||||
homeassistant/components/*/pilight.py
|
||||
|
||||
homeassistant/components/knx.py
|
||||
homeassistant/components/switch/knx.py
|
||||
homeassistant/components/binary_sensor/knx.py
|
||||
homeassistant/components/thermostat/knx.py
|
||||
homeassistant/components/*/knx.py
|
||||
|
||||
homeassistant/components/ffmpeg.py
|
||||
homeassistant/components/*/ffmpeg.py
|
||||
|
||||
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
||||
homeassistant/components/alarm_control_panel/nx584.py
|
||||
homeassistant/components/alarm_control_panel/simplisafe.py
|
||||
homeassistant/components/binary_sensor/arest.py
|
||||
homeassistant/components/binary_sensor/ffmpeg.py
|
||||
homeassistant/components/binary_sensor/rest.py
|
||||
homeassistant/components/browser.py
|
||||
homeassistant/components/camera/bloomsky.py
|
||||
homeassistant/components/camera/ffmpeg.py
|
||||
homeassistant/components/camera/foscam.py
|
||||
homeassistant/components/camera/mjpeg.py
|
||||
homeassistant/components/camera/rpi_camera.py
|
||||
@ -147,6 +146,7 @@ omit =
|
||||
homeassistant/components/ifttt.py
|
||||
homeassistant/components/joaoapps_join.py
|
||||
homeassistant/components/keyboard.py
|
||||
homeassistant/components/keyboard_remote.py
|
||||
homeassistant/components/light/blinksticklight.py
|
||||
homeassistant/components/light/flux_led.py
|
||||
homeassistant/components/light/hue.py
|
||||
@ -188,6 +188,7 @@ omit =
|
||||
homeassistant/components/notify/group.py
|
||||
homeassistant/components/notify/instapush.py
|
||||
homeassistant/components/notify/joaoapps_join.py
|
||||
homeassistant/components/notify/kodi.py
|
||||
homeassistant/components/notify/llamalab_automate.py
|
||||
homeassistant/components/notify/message_bird.py
|
||||
homeassistant/components/notify/nma.py
|
||||
@ -196,6 +197,7 @@ omit =
|
||||
homeassistant/components/notify/pushover.py
|
||||
homeassistant/components/notify/rest.py
|
||||
homeassistant/components/notify/sendgrid.py
|
||||
homeassistant/components/notify/simplepush.py
|
||||
homeassistant/components/notify/slack.py
|
||||
homeassistant/components/notify/smtp.py
|
||||
homeassistant/components/notify/syslog.py
|
||||
@ -203,9 +205,12 @@ omit =
|
||||
homeassistant/components/notify/twilio_sms.py
|
||||
homeassistant/components/notify/twitter.py
|
||||
homeassistant/components/notify/xmpp.py
|
||||
homeassistant/components/nuimo_controller.py
|
||||
homeassistant/components/openalpr.py
|
||||
homeassistant/components/scene/hunterdouglas_powerview.py
|
||||
homeassistant/components/sensor/arest.py
|
||||
homeassistant/components/sensor/bitcoin.py
|
||||
homeassistant/components/sensor/bom.py
|
||||
homeassistant/components/sensor/coinmarketcap.py
|
||||
homeassistant/components/sensor/cpuspeed.py
|
||||
homeassistant/components/sensor/deutsche_bahn.py
|
||||
@ -213,6 +218,7 @@ omit =
|
||||
homeassistant/components/sensor/dte_energy_bridge.py
|
||||
homeassistant/components/sensor/efergy.py
|
||||
homeassistant/components/sensor/eliqonline.py
|
||||
homeassistant/components/sensor/emoncms.py
|
||||
homeassistant/components/sensor/fastdotcom.py
|
||||
homeassistant/components/sensor/fitbit.py
|
||||
homeassistant/components/sensor/fixer.py
|
||||
@ -224,10 +230,12 @@ omit =
|
||||
homeassistant/components/sensor/gtfs.py
|
||||
homeassistant/components/sensor/hp_ilo.py
|
||||
homeassistant/components/sensor/imap.py
|
||||
homeassistant/components/sensor/imap_email_content.py
|
||||
homeassistant/components/sensor/lastfm.py
|
||||
homeassistant/components/sensor/linux_battery.py
|
||||
homeassistant/components/sensor/loopenergy.py
|
||||
homeassistant/components/sensor/mhz19.py
|
||||
homeassistant/components/sensor/miflora.py
|
||||
homeassistant/components/sensor/mqtt_room.py
|
||||
homeassistant/components/sensor/neurio_energy.py
|
||||
homeassistant/components/sensor/nzbget.py
|
||||
@ -255,6 +263,7 @@ omit =
|
||||
homeassistant/components/sensor/uber.py
|
||||
homeassistant/components/sensor/worldclock.py
|
||||
homeassistant/components/sensor/xbox_live.py
|
||||
homeassistant/components/sensor/yahoo_finance.py
|
||||
homeassistant/components/sensor/yweather.py
|
||||
homeassistant/components/switch/acer_projector.py
|
||||
homeassistant/components/switch/arest.py
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -99,4 +99,7 @@ virtualization/vagrant/config
|
||||
.vscode
|
||||
|
||||
# Built docs
|
||||
docs/build
|
||||
docs/build
|
||||
|
||||
# Windows Explorer
|
||||
desktop.ini
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM python:3.4
|
||||
FROM python:3.5
|
||||
MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
|
||||
|
||||
VOLUME /config
|
||||
@ -10,7 +10,7 @@ RUN pip3 install --no-cache-dir colorlog cython
|
||||
|
||||
# For the nmap tracker, bluetooth tracker, Z-Wave
|
||||
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/*
|
||||
|
||||
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
|
||||
# certifi breaks Debian based installs
|
||||
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 . .
|
||||
|
@ -16,16 +16,14 @@ from homeassistant.const import (
|
||||
REQUIRED_PYTHON_VER,
|
||||
RESTART_EXIT_CODE,
|
||||
)
|
||||
from homeassistant.util.async import run_callback_threadsafe
|
||||
|
||||
|
||||
def validate_python() -> None:
|
||||
"""Validate we're running the right Python version."""
|
||||
major, minor = sys.version_info[:2]
|
||||
req_major, req_minor = 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))
|
||||
if sys.version_info[:3] < REQUIRED_PYTHON_VER:
|
||||
print("Home Assistant requires at least Python {}.{}.{}".format(
|
||||
*REQUIRED_PYTHON_VER))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@ -256,12 +254,14 @@ def setup_and_run_hass(config_dir: str,
|
||||
import webbrowser
|
||||
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()
|
||||
exit_code = int(hass.block_till_stopped())
|
||||
|
||||
return exit_code
|
||||
return hass.exit_code
|
||||
|
||||
|
||||
def try_to_restart() -> None:
|
||||
|
@ -118,11 +118,13 @@ def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool:
|
||||
|
||||
# Assumption: if a component does not depend on groups
|
||||
# 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.bus.fire(
|
||||
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN})
|
||||
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@ -144,7 +146,7 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
|
||||
if hasattr(component, 'CONFIG_SCHEMA'):
|
||||
try:
|
||||
config = component.CONFIG_SCHEMA(config)
|
||||
except vol.MultipleInvalid as ex:
|
||||
except vol.Invalid as ex:
|
||||
log_exception(ex, domain, config)
|
||||
return None
|
||||
|
||||
@ -154,8 +156,8 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
|
||||
# Validate component specific platform schema
|
||||
try:
|
||||
p_validated = component.PLATFORM_SCHEMA(p_config)
|
||||
except vol.MultipleInvalid as ex:
|
||||
log_exception(ex, domain, p_config)
|
||||
except vol.Invalid as ex:
|
||||
log_exception(ex, domain, config)
|
||||
return None
|
||||
|
||||
# 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'):
|
||||
try:
|
||||
p_validated = platform.PLATFORM_SCHEMA(p_validated)
|
||||
except vol.MultipleInvalid as ex:
|
||||
except vol.Invalid as ex:
|
||||
log_exception(ex, '{}.{}'.format(domain, p_name),
|
||||
p_validated)
|
||||
return None
|
||||
@ -278,23 +280,29 @@ def from_config_dict(config: Dict[str, Any],
|
||||
components = set(key.split(' ')[0] for key in config.keys()
|
||||
if key != core.DOMAIN)
|
||||
|
||||
if not core_components.setup(hass, config):
|
||||
_LOGGER.error('Home Assistant core failed to initialize. '
|
||||
'Further initialization aborted.')
|
||||
return hass
|
||||
# Setup in a thread to avoid blocking
|
||||
def component_setup():
|
||||
"""Set up a component."""
|
||||
if not core_components.setup(hass, config):
|
||||
_LOGGER.error('Home Assistant core failed to initialize. '
|
||||
'Further initialization aborted.')
|
||||
return hass
|
||||
|
||||
persistent_notification.setup(hass, config)
|
||||
persistent_notification.setup(hass, config)
|
||||
|
||||
_LOGGER.info('Home Assistant core initialized')
|
||||
_LOGGER.info('Home Assistant core initialized')
|
||||
|
||||
# Give event decorators access to HASS
|
||||
event_decorators.HASS = hass
|
||||
service.HASS = hass
|
||||
# Give event decorators access to HASS
|
||||
event_decorators.HASS = hass
|
||||
service.HASS = hass
|
||||
|
||||
# Setup the components
|
||||
for domain in loader.load_order_components(components):
|
||||
_setup_component(hass, domain, config)
|
||||
# Setup the components
|
||||
for domain in loader.load_order_components(components):
|
||||
_setup_component(hass, domain, config)
|
||||
|
||||
hass.loop.run_until_complete(
|
||||
hass.loop.run_in_executor(None, component_setup)
|
||||
)
|
||||
return hass
|
||||
|
||||
|
||||
@ -390,16 +398,21 @@ def _ensure_loader_prepared(hass: core.HomeAssistant) -> None:
|
||||
def log_exception(ex, domain, config):
|
||||
"""Generate log exception for config validation."""
|
||||
message = 'Invalid config for [{}]: '.format(domain)
|
||||
|
||||
if 'extra keys not allowed' in ex.error_message:
|
||||
message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\
|
||||
.format(ex.path[-1], domain, domain,
|
||||
'->'.join('%s' % m for m in ex.path))
|
||||
else:
|
||||
message += humanize_error(config, ex)
|
||||
message += '{}.'.format(humanize_error(config, ex))
|
||||
|
||||
if hasattr(config, '__line__'):
|
||||
message += " (See {}:{})".format(config.__config_file__,
|
||||
config.__line__ or '?')
|
||||
message += " (See {}:{})".format(
|
||||
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)
|
||||
|
||||
|
@ -10,6 +10,7 @@ from homeassistant.components.envisalink import (EVL_CONTROLLER,
|
||||
EnvisalinkDevice,
|
||||
PARTITION_SCHEMA,
|
||||
CONF_CODE,
|
||||
CONF_PANIC,
|
||||
CONF_PARTITIONNAME,
|
||||
SIGNAL_PARTITION_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."""
|
||||
_configured_partitions = discovery_info['partitions']
|
||||
_code = discovery_info[CONF_CODE]
|
||||
_panic_type = discovery_info[CONF_PANIC]
|
||||
for part_num in _configured_partitions:
|
||||
_device_config_data = PARTITION_SCHEMA(
|
||||
_configured_partitions[part_num])
|
||||
@ -33,6 +35,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
part_num,
|
||||
_device_config_data[CONF_PARTITIONNAME],
|
||||
_code,
|
||||
_panic_type,
|
||||
EVL_CONTROLLER.alarm_state['partition'][part_num],
|
||||
EVL_CONTROLLER)
|
||||
add_devices_callback([_device])
|
||||
@ -44,11 +47,13 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
"""Represents the Envisalink-based alarm panel."""
|
||||
|
||||
# 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."""
|
||||
from pydispatch import dispatcher
|
||||
self._partition_number = partition_number
|
||||
self._code = code
|
||||
self._panic_type = panic_type
|
||||
_LOGGER.debug('Setting up alarm: ' + alarm_name)
|
||||
EnvisalinkDevice.__init__(self, alarm_name, info, controller)
|
||||
dispatcher.connect(self._update_callback,
|
||||
@ -61,7 +66,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
def _update_callback(self, partition):
|
||||
"""Update HA state, if needed."""
|
||||
if partition is None or int(partition) == self._partition_number:
|
||||
self.update_ha_state()
|
||||
self.hass.async_add_job(self.update_ha_state)
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
@ -101,5 +106,6 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
self._partition_number)
|
||||
|
||||
def alarm_trigger(self, code=None):
|
||||
"""Alarm trigger command. Not possible for us."""
|
||||
raise NotImplementedError()
|
||||
"""Alarm trigger command. Will be used to trigger a panic alarm."""
|
||||
if self._code:
|
||||
EVL_CONTROLLER.panic_alarm(self._panic_type)
|
||||
|
@ -28,7 +28,7 @@ PLATFORM_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string,
|
||||
vol.Optional(CONF_CODE): cv.string,
|
||||
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.All(vol.Coerce(int), vol.Range(min=1)),
|
||||
vol.Optional(CONF_DISARM_AFTER_TRIGGER,
|
||||
|
@ -4,11 +4,14 @@ Support for Alexa skill service end point.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/alexa/
|
||||
"""
|
||||
import copy
|
||||
import enum
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
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
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -20,10 +23,49 @@ CONF_CARD = 'card'
|
||||
CONF_INTENTS = 'intents'
|
||||
CONF_SPEECH = 'speech'
|
||||
|
||||
CONF_TYPE = 'type'
|
||||
CONF_TITLE = 'title'
|
||||
CONF_CONTENT = 'content'
|
||||
CONF_TEXT = 'text'
|
||||
|
||||
DOMAIN = 'alexa'
|
||||
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):
|
||||
"""Activate Alexa component."""
|
||||
hass.wsgi.register_view(AlexaView(hass,
|
||||
@ -42,6 +84,9 @@ class AlexaView(HomeAssistantView):
|
||||
"""Initialize Alexa view."""
|
||||
super().__init__(hass)
|
||||
|
||||
intents = copy.deepcopy(intents)
|
||||
template.attach(hass, intents)
|
||||
|
||||
for name, intent in intents.items():
|
||||
if CONF_ACTION in intent:
|
||||
intent[CONF_ACTION] = script.Script(
|
||||
@ -101,29 +146,15 @@ class AlexaView(HomeAssistantView):
|
||||
|
||||
# pylint: disable=unsubscriptable-object
|
||||
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:
|
||||
response.add_card(CardType[card['type']], card['title'],
|
||||
card['content'])
|
||||
response.add_card(card[CONF_TYPE], card[CONF_TITLE],
|
||||
card[CONF_CONTENT])
|
||||
|
||||
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):
|
||||
"""Help generating the response for Alexa."""
|
||||
|
||||
@ -153,8 +184,8 @@ class AlexaResponse(object):
|
||||
self.card = card
|
||||
return
|
||||
|
||||
card["title"] = self._render(title),
|
||||
card["content"] = self._render(content)
|
||||
card["title"] = title.render(self.variables)
|
||||
card["content"] = content.render(self.variables)
|
||||
self.card = card
|
||||
|
||||
def add_speech(self, speech_type, text):
|
||||
@ -163,9 +194,12 @@ class AlexaResponse(object):
|
||||
|
||||
key = 'ssml' if speech_type == SpeechType.ssml else 'text'
|
||||
|
||||
if isinstance(text, template.Template):
|
||||
text = text.render(self.variables)
|
||||
|
||||
self.speech = {
|
||||
'type': speech_type.value,
|
||||
key: self._render(text)
|
||||
key: text
|
||||
}
|
||||
|
||||
def add_reprompt(self, speech_type, text):
|
||||
@ -176,7 +210,7 @@ class AlexaResponse(object):
|
||||
|
||||
self.reprompt = {
|
||||
'type': speech_type.value,
|
||||
key: self._render(text)
|
||||
key: text.render(self.variables)
|
||||
}
|
||||
|
||||
def as_dict(self):
|
||||
@ -201,7 +235,3 @@ class AlexaResponse(object):
|
||||
'sessionAttributes': self.session_attributes,
|
||||
'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)
|
||||
|
@ -4,6 +4,7 @@ Rest API for Home Assistant.
|
||||
For more details about the RESTful API, please refer to the documentation at
|
||||
https://home-assistant.io/developers/api/
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import queue
|
||||
@ -79,6 +80,7 @@ class APIEventStream(HomeAssistantView):
|
||||
if restrict:
|
||||
restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP]
|
||||
|
||||
@asyncio.coroutine
|
||||
def forward_events(event):
|
||||
"""Forward events to the open request."""
|
||||
if event.event_type == EVENT_TIME_CHANGED:
|
||||
@ -376,8 +378,8 @@ class APITemplateView(HomeAssistantView):
|
||||
def post(self, request):
|
||||
"""Render a template."""
|
||||
try:
|
||||
return template.render(self.hass, request.json['template'],
|
||||
request.json.get('variables'))
|
||||
tpl = template.Template(request.json['template'], self.hass)
|
||||
return tpl.render(request.json.get('variables'))
|
||||
except TemplateError as ex:
|
||||
return self.json_message('Error rendering template: {}'.format(ex),
|
||||
HTTP_BAD_REQUEST)
|
||||
|
@ -13,7 +13,7 @@ from homeassistant.const import (
|
||||
from homeassistant.const import CONF_PORT
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['PyMata==2.12']
|
||||
REQUIREMENTS = ['PyMata==2.13']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -30,6 +30,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
DEPENDENCIES = ['group']
|
||||
|
||||
CONF_ALIAS = 'alias'
|
||||
CONF_HIDE_ENTITY = 'hide_entity'
|
||||
|
||||
CONF_CONDITION = 'condition'
|
||||
CONF_ACTION = 'action'
|
||||
@ -41,6 +42,7 @@ CONDITION_TYPE_AND = 'and'
|
||||
CONDITION_TYPE_OR = 'or'
|
||||
|
||||
DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
|
||||
DEFAULT_HIDE_ENTITY = False
|
||||
|
||||
METHOD_TRIGGER = 'trigger'
|
||||
METHOD_IF_ACTION = 'if_action'
|
||||
@ -99,6 +101,7 @@ _CONDITION_SCHEMA = vol.Any(
|
||||
|
||||
PLATFORM_SCHEMA = vol.Schema({
|
||||
CONF_ALIAS: cv.string,
|
||||
vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
|
||||
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
|
||||
vol.Required(CONF_CONDITION_TYPE, default=DEFAULT_CONDITION_TYPE):
|
||||
vol.All(vol.Lower, vol.Any(CONDITION_TYPE_AND, CONDITION_TYPE_OR)),
|
||||
@ -206,7 +209,8 @@ def setup(hass, config):
|
||||
class AutomationEntity(ToggleEntity):
|
||||
"""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."""
|
||||
self._name = name
|
||||
self._attach_triggers = attach_triggers
|
||||
@ -215,6 +219,7 @@ class AutomationEntity(ToggleEntity):
|
||||
self._action = action
|
||||
self._enabled = True
|
||||
self._last_triggered = None
|
||||
self._hidden = hidden
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -233,6 +238,11 @@ class AutomationEntity(ToggleEntity):
|
||||
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
|
||||
def is_on(self) -> bool:
|
||||
"""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,
|
||||
list_no)
|
||||
|
||||
hidden = config_block[CONF_HIDE_ENTITY]
|
||||
|
||||
action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)
|
||||
|
||||
if CONF_CONDITION in config_block:
|
||||
@ -295,7 +307,8 @@ def _process_config(hass, config, component):
|
||||
|
||||
attach_triggers = partial(_process_trigger, hass, config,
|
||||
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,))
|
||||
success = True
|
||||
|
||||
|
@ -4,6 +4,7 @@ Offer event listening automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#event-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -28,11 +29,12 @@ def trigger(hass, config, action):
|
||||
event_type = config.get(CONF_EVENT_TYPE)
|
||||
event_data = config.get(CONF_EVENT_DATA)
|
||||
|
||||
@asyncio.coroutine
|
||||
def handle_event(event):
|
||||
"""Listen for events and calls the action when data matches."""
|
||||
if not event_data or all(val == event.data.get(key) for key, val
|
||||
in event_data.items()):
|
||||
action({
|
||||
hass.async_add_job(action, {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event': event,
|
||||
|
@ -31,6 +31,8 @@ def trigger(hass, config, action):
|
||||
below = config.get(CONF_BELOW)
|
||||
above = config.get(CONF_ABOVE)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def state_automation_listener(entity, from_s, to_s):
|
||||
|
@ -4,12 +4,12 @@ Offer template automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#template-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_VALUE_TEMPLATE, CONF_PLATFORM, MATCH_ALL)
|
||||
from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM
|
||||
from homeassistant.helpers import condition
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@ -26,19 +26,21 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
|
||||
def trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
value_template.hass = hass
|
||||
|
||||
# Local variable to keep track of if the action has already been triggered
|
||||
already_triggered = False
|
||||
|
||||
@asyncio.coroutine
|
||||
def state_changed_listener(entity_id, from_s, to_s):
|
||||
"""Listen for state changes and calls action."""
|
||||
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
|
||||
if template_result and not already_triggered:
|
||||
already_triggered = True
|
||||
action({
|
||||
hass.async_add_job(action, {
|
||||
'trigger': {
|
||||
'platform': 'template',
|
||||
'entity_id': entity_id,
|
||||
@ -49,4 +51,5 @@ def trigger(hass, config, action):
|
||||
elif not template_result:
|
||||
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)
|
||||
|
@ -9,18 +9,15 @@ from datetime import timedelta
|
||||
|
||||
import requests
|
||||
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice,
|
||||
SENSOR_CLASSES)
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, SENSOR_CLASSES)
|
||||
from homeassistant.const import CONF_RESOURCE, CONF_PIN
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Return cached results if last scan was less then this time ago
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
|
||||
|
||||
CONF_RESOURCE = 'resource'
|
||||
CONF_PIN = 'pin'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the aREST binary sensor."""
|
||||
|
@ -15,7 +15,6 @@ from homeassistant.components.sensor.command_line import CommandSensorData
|
||||
from homeassistant.const import (
|
||||
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE,
|
||||
CONF_SENSOR_CLASS, CONF_COMMAND)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_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)
|
||||
sensor_class = config.get(CONF_SENSOR_CLASS)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
data = CommandSensorData(command)
|
||||
|
||||
add_devices([CommandBinarySensor(
|
||||
@ -91,8 +91,8 @@ class CommandBinarySensor(BinarySensorDevice):
|
||||
value = self.data.value
|
||||
|
||||
if self._value_template is not None:
|
||||
value = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, value, False)
|
||||
value = self._value_template.render_with_possible_json_value(
|
||||
value, False)
|
||||
if value == self._payload_on:
|
||||
self._state = True
|
||||
elif value == self._payload_off:
|
||||
|
@ -68,4 +68,4 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
|
||||
def _update_callback(self, zone):
|
||||
"""Update the zone's state, if needed."""
|
||||
if zone is None or int(zone) == self._zone_number:
|
||||
self.update_ha_state()
|
||||
self.hass.async_add_job(self.update_ha_state)
|
||||
|
@ -10,13 +10,17 @@ from os import path
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice,
|
||||
PLATFORM_SCHEMA, DOMAIN)
|
||||
from homeassistant.components.binary_sensor import (
|
||||
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.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME,
|
||||
ATTR_ENTITY_ID)
|
||||
|
||||
REQUIREMENTS = ["ha-ffmpeg==0.10"]
|
||||
DEPENDENCIES = ['ffmpeg']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SERVICE_RESTART = 'ffmpeg_restart'
|
||||
|
||||
@ -29,10 +33,6 @@ MAP_FFMPEG_BIN = [
|
||||
]
|
||||
|
||||
CONF_TOOL = 'tool'
|
||||
CONF_INPUT = 'input'
|
||||
CONF_FFMPEG_BIN = 'ffmpeg_bin'
|
||||
CONF_EXTRA_ARGUMENTS = 'extra_arguments'
|
||||
CONF_OUTPUT = 'output'
|
||||
CONF_PEAK = 'peak'
|
||||
CONF_DURATION = 'duration'
|
||||
CONF_RESET = 'reset'
|
||||
@ -40,11 +40,12 @@ CONF_CHANGES = 'changes'
|
||||
CONF_REPEAT = 'repeat'
|
||||
CONF_REPEAT_TIME = 'repeat_time'
|
||||
|
||||
DEFAULT_NAME = 'FFmpeg'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_TOOL): vol.In(MAP_FFMPEG_BIN),
|
||||
vol.Required(CONF_INPUT): cv.string,
|
||||
vol.Optional(CONF_FFMPEG_BIN, default="ffmpeg"): cv.string,
|
||||
vol.Optional(CONF_NAME, default="FFmpeg"): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
|
||||
vol.Optional(CONF_OUTPUT): cv.string,
|
||||
vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int),
|
||||
@ -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
|
||||
DEVICES = []
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Create the binary sensor."""
|
||||
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:
|
||||
entity = FFmpegNoise(SensorNoise, config)
|
||||
else:
|
||||
@ -88,7 +98,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
||||
# exists service?
|
||||
if hass.services.has_service(DOMAIN, SERVICE_RESTART):
|
||||
return True
|
||||
return
|
||||
|
||||
descriptions = load_yaml_config_file(
|
||||
path.join(path.dirname(__file__), 'services.yaml'))
|
||||
@ -105,13 +115,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
_devices = DEVICES
|
||||
|
||||
for device in _devices:
|
||||
device.reset_ffmpeg()
|
||||
device.restart_ffmpeg()
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_RESTART,
|
||||
_service_handle_restart,
|
||||
descriptions.get(SERVICE_RESTART),
|
||||
schema=SERVICE_RESTART_SCHEMA)
|
||||
return True
|
||||
|
||||
|
||||
class FFmpegBinarySensor(BinarySensorDevice):
|
||||
@ -122,7 +131,7 @@ class FFmpegBinarySensor(BinarySensorDevice):
|
||||
self._state = False
|
||||
self._config = config
|
||||
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)
|
||||
|
||||
@ -139,7 +148,7 @@ class FFmpegBinarySensor(BinarySensorDevice):
|
||||
"""For STOP event to shutdown ffmpeg."""
|
||||
self._ffmpeg.close()
|
||||
|
||||
def reset_ffmpeg(self):
|
||||
def restart_ffmpeg(self):
|
||||
"""Restart ffmpeg with new config."""
|
||||
self._ffmpeg.close()
|
||||
self._start_ffmpeg(self._config)
|
||||
|
71
homeassistant/components/binary_sensor/isy994.py
Normal file
71
homeassistant/components/binary_sensor/isy994.py
Normal 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
|
@ -5,17 +5,14 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.knx/
|
||||
"""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.knx import (
|
||||
KNXConfig, KNXGroupAddress)
|
||||
from homeassistant.components.knx import (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."""
|
||||
add_entities([
|
||||
KNXSwitch(hass, KNXConfig(config))
|
||||
])
|
||||
add_devices([KNXSwitch(hass, KNXConfig(config))])
|
||||
|
||||
|
||||
class KNXSwitch(KNXGroupAddress, BinarySensorDevice):
|
||||
|
61
homeassistant/components/binary_sensor/modbus.py
Normal file
61
homeassistant/components/binary_sensor/modbus.py
Normal 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]
|
@ -15,7 +15,6 @@ from homeassistant.const import (
|
||||
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
|
||||
CONF_SENSOR_CLASS)
|
||||
from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -37,6 +36,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the MQTT binary sensor."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
add_devices([MqttBinarySensor(
|
||||
hass,
|
||||
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_PAYLOAD_ON),
|
||||
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):
|
||||
"""A new MQTT message has been received."""
|
||||
if value_template is not None:
|
||||
payload = template.render_with_possible_json_value(
|
||||
hass, value_template, payload)
|
||||
payload = value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
if payload == self._payload_on:
|
||||
self._state = True
|
||||
self.update_ha_state()
|
||||
|
@ -14,7 +14,6 @@ from homeassistant.components.sensor.rest import RestData
|
||||
from homeassistant.const import (
|
||||
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
|
||||
CONF_SENSOR_CLASS, CONF_VERIFY_SSL)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_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)
|
||||
sensor_class = config.get(CONF_SENSOR_CLASS)
|
||||
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.update()
|
||||
|
||||
@ -88,8 +88,8 @@ class RestBinarySensor(BinarySensorDevice):
|
||||
return False
|
||||
|
||||
if self._value_template is not None:
|
||||
response = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, self.rest.data, False)
|
||||
response = self._value_template.render_with_possible_json_value(
|
||||
self.rest.data, False)
|
||||
|
||||
try:
|
||||
return bool(int(response))
|
||||
|
@ -6,16 +6,37 @@ https://home-assistant.io/components/binary_sensor.rpi_gpio/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import homeassistant.components.rpi_gpio as rpi_gpio
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import DEVICE_DEFAULT_NAME
|
||||
import voluptuous as vol
|
||||
|
||||
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_INVERT_LOGIC = False
|
||||
DEFAULT_PULL_MODE = 'UP'
|
||||
|
||||
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
|
||||
|
53
homeassistant/components/binary_sensor/sleepiq.py
Normal file
53
homeassistant/components/binary_sensor/sleepiq.py
Normal 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
|
@ -12,10 +12,9 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
|
||||
SENSOR_CLASSES_SCHEMA)
|
||||
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)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@ -25,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
SENSOR_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
|
||||
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
|
||||
})
|
||||
|
||||
@ -40,10 +39,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
for device, device_config in config[CONF_SENSORS].items():
|
||||
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)
|
||||
sensor_class = device_config.get(CONF_SENSOR_CLASS)
|
||||
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
|
||||
sensors.append(
|
||||
BinarySensorTemplate(
|
||||
hass,
|
||||
@ -107,8 +110,7 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
def update(self):
|
||||
"""Get the latest data and update the state."""
|
||||
try:
|
||||
self._state = template.render(
|
||||
self.hass, self._template).lower() == 'true'
|
||||
self._state = self._template.render().lower() == 'true'
|
||||
except TemplateError as ex:
|
||||
if ex.args and ex.args[0].startswith(
|
||||
"UndefinedError: 'None' has no attribute"):
|
||||
|
@ -2,7 +2,7 @@
|
||||
A sensor that monitors trands in other components.
|
||||
|
||||
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 voluptuous as vol
|
||||
@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the template sensors."""
|
||||
"""Setup the trend sensors."""
|
||||
sensors = []
|
||||
|
||||
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):
|
||||
"""Representation of a Template Sensor."""
|
||||
"""Representation of a trend Sensor."""
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||
def __init__(self, hass, device_id, friendly_name,
|
||||
@ -90,14 +90,14 @@ class SensorTrend(BinarySensorDevice):
|
||||
|
||||
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."""
|
||||
self.from_state = old_state
|
||||
self.to_state = new_state
|
||||
self.update_ha_state(True)
|
||||
|
||||
track_state_change(hass, target_entity,
|
||||
template_sensor_state_listener)
|
||||
trend_sensor_state_listener)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -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
|
||||
at https://home-assistant.io/components/sensor.wink/
|
||||
at https://home-assistant.io/components/binary_sensor.wink/
|
||||
"""
|
||||
import logging
|
||||
import json
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.sensor.wink import WinkDevice
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
from homeassistant.helpers.entity import Entity
|
||||
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
|
||||
SENSOR_TYPES = {
|
||||
@ -21,7 +19,8 @@ SENSOR_TYPES = {
|
||||
"brightness": "light",
|
||||
"vibration": "vibration",
|
||||
"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."""
|
||||
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():
|
||||
if sensor.capability() in SENSOR_TYPES:
|
||||
add_devices([WinkBinarySensorDevice(sensor)])
|
||||
@ -77,6 +65,8 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
|
||||
return self.wink.brightness_boolean()
|
||||
elif self.capability == "liquid_detected":
|
||||
return self.wink.liquid_boolean()
|
||||
elif self.capability == "motion":
|
||||
return self.wink.motion_boolean()
|
||||
else:
|
||||
return self.wink.state()
|
||||
|
||||
|
@ -65,7 +65,7 @@ class BloomSky(object):
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
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")
|
||||
response = requests.get(self.API_URL,
|
||||
headers={"Authorization": self._api_key},
|
||||
|
@ -9,30 +9,28 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
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
|
||||
from homeassistant.const import CONF_NAME
|
||||
|
||||
REQUIREMENTS = ['ha-ffmpeg==0.10']
|
||||
DEPENDENCIES = ['ffmpeg']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_INPUT = 'input'
|
||||
CONF_FFMPEG_BIN = 'ffmpeg_bin'
|
||||
CONF_EXTRA_ARGUMENTS = 'extra_arguments'
|
||||
|
||||
DEFAULT_BINARY = 'ffmpeg'
|
||||
DEFAULT_NAME = 'FFmpeg'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_INPUT): 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,
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup a FFmpeg Camera."""
|
||||
if not run_test(config.get(CONF_INPUT)):
|
||||
return
|
||||
add_devices([FFmpegCamera(config)])
|
||||
|
||||
|
||||
@ -45,12 +43,11 @@ class FFmpegCamera(Camera):
|
||||
self._name = config.get(CONF_NAME)
|
||||
self._input = config.get(CONF_INPUT)
|
||||
self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS)
|
||||
self._ffmpeg_bin = config.get(CONF_FFMPEG_BIN)
|
||||
|
||||
def camera_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
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,
|
||||
extra_cmd=self._extra_arguments)
|
||||
@ -59,7 +56,7 @@ class FFmpegCamera(Camera):
|
||||
"""Generate an HTTP MJPEG stream from the camera."""
|
||||
from haffmpeg import CameraMjpeg
|
||||
|
||||
stream = CameraMjpeg(self._ffmpeg_bin)
|
||||
stream = CameraMjpeg(get_binary())
|
||||
stream.open_camera(self._input, extra_cmd=self._extra_arguments)
|
||||
return response(
|
||||
stream,
|
||||
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
||||
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
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__)
|
||||
|
||||
@ -25,7 +25,7 @@ CONF_STILL_IMAGE_URL = 'still_image_url'
|
||||
DEFAULT_NAME = 'Generic Camera'
|
||||
|
||||
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.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
|
||||
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
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup a generic IP Camera."""
|
||||
add_devices([GenericCamera(config)])
|
||||
add_devices([GenericCamera(hass, config)])
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class GenericCamera(Camera):
|
||||
"""A generic implementation of an IP camera."""
|
||||
|
||||
def __init__(self, device_info):
|
||||
def __init__(self, hass, device_info):
|
||||
"""Initialize a generic camera."""
|
||||
super().__init__()
|
||||
self.hass = hass
|
||||
self._name = device_info.get(CONF_NAME)
|
||||
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]
|
||||
|
||||
username = device_info.get(CONF_USERNAME)
|
||||
@ -69,7 +71,7 @@ class GenericCamera(Camera):
|
||||
def camera_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
try:
|
||||
url = template.render(self.hass, self._still_image_url)
|
||||
url = self._still_image_url.render()
|
||||
except TemplateError as err:
|
||||
_LOGGER.error('Error parsing template %s: %s',
|
||||
self._still_image_url, err)
|
||||
|
@ -8,28 +8,33 @@ import logging
|
||||
import socket
|
||||
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.camera import DOMAIN, Camera
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.const import CONF_PORT
|
||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['uvcclient==0.9.0']
|
||||
|
||||
_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):
|
||||
"""Discover cameras on a Unifi NVR."""
|
||||
if not validate_config({DOMAIN: config}, {DOMAIN: ['nvr', 'key']},
|
||||
_LOGGER):
|
||||
return None
|
||||
|
||||
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
|
||||
addr = config[CONF_NVR]
|
||||
key = config[CONF_KEY]
|
||||
port = config[CONF_PORT]
|
||||
|
||||
from uvcclient import nvr
|
||||
nvrconn = nvr.UVCRemote(addr, port, key)
|
||||
|
@ -16,7 +16,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
None, None, "Auto", "heat", None, None, None),
|
||||
DemoClimate("Hvac", 21, TEMP_CELSIUS, True, 22, "On High",
|
||||
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)
|
||||
])
|
||||
|
||||
|
@ -14,7 +14,7 @@ from homeassistant.components.climate import (
|
||||
DOMAIN, STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice,
|
||||
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH)
|
||||
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
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
@ -86,10 +86,16 @@ class Thermostat(ClimateDevice):
|
||||
self.hold_temp = hold_temp
|
||||
self._operation_list = ['auto', 'auxHeatOnly', 'cool',
|
||||
'heat', 'off']
|
||||
self.update_without_throttle = False
|
||||
|
||||
def update(self):
|
||||
"""Get the latest state from the thermostat."""
|
||||
self.data.update()
|
||||
if self.update_without_throttle:
|
||||
self.data.update(no_throttle=True)
|
||||
self.update_without_throttle = False
|
||||
else:
|
||||
self.data.update()
|
||||
|
||||
self.thermostat = self.data.ecobee.get_thermostat(
|
||||
self.thermostat_index)
|
||||
|
||||
@ -101,22 +107,16 @@ class Thermostat(ClimateDevice):
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_FAHRENHEIT
|
||||
if self.thermostat['settings']['useCelsius']:
|
||||
return TEMP_CELSIUS
|
||||
else:
|
||||
return TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
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
|
||||
def target_temperature_low(self):
|
||||
"""Return the lower bound temperature we try to reach."""
|
||||
@ -211,17 +211,15 @@ class Thermostat(ClimateDevice):
|
||||
"away", "indefinite")
|
||||
else:
|
||||
self.data.ecobee.set_climate_hold(self.thermostat_index, "away")
|
||||
self.update_without_throttle = True
|
||||
|
||||
def turn_away_mode_off(self):
|
||||
"""Turn away off."""
|
||||
self.data.ecobee.resume_program(self.thermostat_index)
|
||||
self.update_without_throttle = True
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""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 \
|
||||
kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None:
|
||||
high_temp = int(kwargs.get(ATTR_TARGET_TEMP_LOW))
|
||||
@ -241,15 +239,18 @@ class Thermostat(ClimateDevice):
|
||||
"high=%s, is=%s", low_temp, isinstance(
|
||||
low_temp, (int, float)), high_temp,
|
||||
isinstance(high_temp, (int, float)))
|
||||
self.update_without_throttle = True
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
|
||||
self.data.ecobee.set_hvac_mode(self.thermostat_index, operation_mode)
|
||||
self.update_without_throttle = True
|
||||
|
||||
def set_fan_min_on_time(self, fan_min_on_time):
|
||||
"""Set the minimum fan on time."""
|
||||
self.data.ecobee.set_fan_min_on_time(self.thermostat_index,
|
||||
fan_min_on_time)
|
||||
self.update_without_throttle = True
|
||||
|
||||
# Home and Sleep mode aren't used in UI yet:
|
||||
|
||||
|
@ -5,16 +5,19 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.generic_thermostat/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components import switch
|
||||
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 (
|
||||
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE)
|
||||
from homeassistant.helpers import condition
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['switch', 'sensor']
|
||||
|
||||
@ -30,18 +33,16 @@ CONF_TARGET_TEMP = 'target_temp'
|
||||
CONF_AC_MODE = 'ac_mode'
|
||||
CONF_MIN_DUR = 'min_cycle_duration'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = vol.Schema({
|
||||
vol.Required("platform"): "generic_thermostat",
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HEATER): 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_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_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)
|
||||
min_cycle_duration = config.get(CONF_MIN_DUR)
|
||||
|
||||
add_devices([GenericThermostat(hass, name, heater_entity_id,
|
||||
sensor_entity_id, min_temp,
|
||||
max_temp, target_temp, ac_mode,
|
||||
min_cycle_duration)])
|
||||
add_devices([GenericThermostat(
|
||||
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
|
||||
target_temp, ac_mode, min_cycle_duration)])
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes, abstract-method
|
||||
@ -110,7 +110,7 @@ class GenericThermostat(ClimateDevice):
|
||||
return self._cur_temp
|
||||
|
||||
@property
|
||||
def operation(self):
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
if self.ac_mode:
|
||||
cooling = self._active and self._is_device_active
|
||||
|
@ -99,6 +99,9 @@ class HMThermostat(homematic.HMDevice, ClimateDevice):
|
||||
return None
|
||||
if temperature is None:
|
||||
return
|
||||
|
||||
if self.current_operation == STATE_AUTO:
|
||||
return self._hmdevice.actionNodeData('MANU_MODE', temperature)
|
||||
self._hmdevice.set_temperature(temperature)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
|
@ -7,44 +7,67 @@ https://home-assistant.io/components/climate.honeywell/
|
||||
import logging
|
||||
import socket
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
||||
ATTR_TEMPERATURE)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['evohomeclient==0.2.5',
|
||||
'somecomfort==0.2.1']
|
||||
'somecomfort==0.3.2']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_AWAY_TEMP = "away_temperature"
|
||||
DEFAULT_AWAY_TEMP = 16
|
||||
ATTR_FAN = 'fan'
|
||||
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):
|
||||
"""Setup rounding function."""
|
||||
from evohomeclient import EvohomeClient
|
||||
|
||||
try:
|
||||
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
|
||||
|
||||
away_temp = config.get(CONF_AWAY_TEMPERATURE)
|
||||
evo_api = EvohomeClient(username, password)
|
||||
|
||||
try:
|
||||
zones = evo_api.temperatures(force_refresh=True)
|
||||
for i, zone in enumerate(zones):
|
||||
add_devices([RoundThermostat(evo_api,
|
||||
zone['id'],
|
||||
i == 0,
|
||||
away_temp)])
|
||||
add_devices(
|
||||
[RoundThermostat(evo_api, zone['id'], i == 0, away_temp)]
|
||||
)
|
||||
except socket.error:
|
||||
_LOGGER.error(
|
||||
"Connection error logging into the honeywell evohome web service"
|
||||
)
|
||||
"Connection error logging into the honeywell evohome web service")
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -74,26 +97,6 @@ def _setup_us(username, password, config, add_devices):
|
||||
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):
|
||||
"""Representation of a Honeywell Round Connected thermostat."""
|
||||
|
||||
@ -103,7 +106,7 @@ class RoundThermostat(ClimateDevice):
|
||||
self.device = device
|
||||
self._current_temperature = None
|
||||
self._target_temperature = None
|
||||
self._name = "round connected"
|
||||
self._name = 'round connected'
|
||||
self._id = zone_id
|
||||
self._master = master
|
||||
self._is_dhw = False
|
||||
@ -143,7 +146,7 @@ class RoundThermostat(ClimateDevice):
|
||||
@property
|
||||
def current_operation(self: ClimateDevice) -> str:
|
||||
"""Get the current operation of the system."""
|
||||
return getattr(self.device, 'system_mode', None)
|
||||
return getattr(self.device, ATTR_SYSTEM_MODE, None)
|
||||
|
||||
@property
|
||||
def is_away_mode_on(self):
|
||||
@ -152,7 +155,7 @@ class RoundThermostat(ClimateDevice):
|
||||
|
||||
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
|
||||
"""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
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
@ -186,8 +189,8 @@ class RoundThermostat(ClimateDevice):
|
||||
|
||||
self._current_temperature = data['temp']
|
||||
self._target_temperature = data['setpoint']
|
||||
if data['thermostat'] == "DOMESTIC_HOT_WATER":
|
||||
self._name = "Hot Water"
|
||||
if data['thermostat'] == 'DOMESTIC_HOT_WATER':
|
||||
self._name = 'Hot Water'
|
||||
self._is_dhw = True
|
||||
else:
|
||||
self._name = data['name']
|
||||
@ -236,7 +239,7 @@ class HoneywellUSThermostat(ClimateDevice):
|
||||
@property
|
||||
def current_operation(self: ClimateDevice) -> str:
|
||||
"""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):
|
||||
"""Set target temperature."""
|
||||
@ -255,9 +258,11 @@ class HoneywellUSThermostat(ClimateDevice):
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
return {'fan': (self.is_fan_on and 'running' or 'idle'),
|
||||
'fanmode': self._device.fan_mode,
|
||||
'system_mode': self._device.system_mode}
|
||||
return {
|
||||
ATTR_FAN: (self.is_fan_on and 'running' or 'idle'),
|
||||
ATTR_FANMODE: self._device.fan_mode,
|
||||
ATTR_SYSTEM_MODE: self._device.system_mode,
|
||||
}
|
||||
|
||||
def turn_away_mode_on(self):
|
||||
"""Turn away on."""
|
||||
@ -269,5 +274,5 @@ class HoneywellUSThermostat(ClimateDevice):
|
||||
|
||||
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
|
||||
"""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
|
||||
|
@ -2,26 +2,37 @@
|
||||
Support for KNX thermostats.
|
||||
|
||||
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
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.knx import (
|
||||
KNXConfig, KNXMultiAddressDevice)
|
||||
|
||||
DEPENDENCIES = ["knx"]
|
||||
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.components.knx import (KNXConfig, KNXMultiAddressDevice)
|
||||
from homeassistant.const import (CONF_NAME, TEMP_CELSIUS, ATTR_TEMPERATURE)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_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."""
|
||||
add_entities([
|
||||
KNXThermostat(hass, KNXConfig(config))
|
||||
])
|
||||
add_devices([KNXThermostat(hass, KNXConfig(config))])
|
||||
|
||||
|
||||
class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
|
||||
@ -39,9 +50,8 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
|
||||
|
||||
def __init__(self, hass, config):
|
||||
"""Initialize the thermostat based on the given configuration."""
|
||||
KNXMultiAddressDevice.__init__(self, hass, config,
|
||||
["temperature", "setpoint"],
|
||||
["mode"])
|
||||
KNXMultiAddressDevice.__init__(
|
||||
self, hass, config, ['temperature', 'setpoint'], ['mode'])
|
||||
|
||||
self._unit_of_measurement = TEMP_CELSIUS # KNX always used celsius
|
||||
self._away = False # not yet supported
|
||||
@ -62,14 +72,14 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
|
||||
"""Return the current temperature."""
|
||||
from knxip.conversion import knx2_to_float
|
||||
|
||||
return knx2_to_float(self.value("temperature"))
|
||||
return knx2_to_float(self.value('temperature'))
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
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):
|
||||
"""Set new target temperature."""
|
||||
@ -78,7 +88,7 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
|
||||
return
|
||||
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)
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
|
192
homeassistant/components/climate/mysensors.py
Executable file
192
homeassistant/components/climate/mysensors.py
Executable 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")
|
@ -4,15 +4,18 @@ Support for Nest thermostats.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.nest/
|
||||
"""
|
||||
import logging
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.nest as nest
|
||||
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 (
|
||||
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']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_SCAN_INTERVAL):
|
||||
@ -22,7 +25,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""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()])
|
||||
|
||||
|
||||
@ -30,10 +34,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
class NestThermostat(ClimateDevice):
|
||||
"""Representation of a Nest thermostat."""
|
||||
|
||||
def __init__(self, structure, device):
|
||||
def __init__(self, structure, device, temp_unit):
|
||||
"""Initialize the thermostat."""
|
||||
self._unit = temp_unit
|
||||
self.structure = structure
|
||||
self.device = device
|
||||
self._fan_list = [STATE_ON, STATE_AUTO]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -51,7 +57,10 @@ class NestThermostat(ClimateDevice):
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
if self.device.measurment_scale == 'F':
|
||||
return TEMP_FAHRENHEIT
|
||||
elif self.device.measurement_scale == 'C':
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
@ -78,35 +87,6 @@ class NestThermostat(ClimateDevice):
|
||||
else:
|
||||
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
|
||||
def target_temperature_low(self):
|
||||
"""Return the lower bound temperature we try to reach."""
|
||||
@ -134,15 +114,16 @@ class NestThermostat(ClimateDevice):
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if temperature is None:
|
||||
return
|
||||
if self.device.mode == 'range':
|
||||
if self.target_temperature == self.target_temperature_low:
|
||||
temperature = (temperature, self.target_temperature_high)
|
||||
elif self.target_temperature == self.target_temperature_high:
|
||||
temperature = (self.target_temperature_low, temperature)
|
||||
self.device.target = temperature
|
||||
if kwargs.get(ATTR_TARGET_TEMP_LOW) is not None and \
|
||||
kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None:
|
||||
target_temp_high = convert_temperature(kwargs.get(
|
||||
ATTR_TARGET_TEMP_HIGH), self._unit, TEMP_CELSIUS)
|
||||
target_temp_low = convert_temperature(kwargs.get(
|
||||
ATTR_TARGET_TEMP_LOW), self._unit, TEMP_CELSIUS)
|
||||
|
||||
temp = (target_temp_low, target_temp_high)
|
||||
_LOGGER.debug("Nest set_temperature-output-value=%s", temp)
|
||||
self.device.target = temp
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set operation mode."""
|
||||
@ -157,17 +138,18 @@ class NestThermostat(ClimateDevice):
|
||||
self.structure.away = False
|
||||
|
||||
@property
|
||||
def is_fan_on(self):
|
||||
def current_fan_mode(self):
|
||||
"""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):
|
||||
"""Turn fan on."""
|
||||
self.device.fan = True
|
||||
@property
|
||||
def fan_list(self):
|
||||
"""List of available fan modes."""
|
||||
return self._fan_list
|
||||
|
||||
def turn_fan_off(self):
|
||||
"""Turn fan off."""
|
||||
self.device.fan = False
|
||||
def set_fan_mode(self, fan):
|
||||
"""Turn fan on/off."""
|
||||
self.device.fan = fan.lower()
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
|
@ -4,13 +4,24 @@ Support for Proliphix NT10e Thermostats.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/climate.proliphix/
|
||||
"""
|
||||
import voluptuous as vol
|
||||
|
||||
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 (
|
||||
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
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):
|
||||
"""Setup the Proliphix thermostats."""
|
||||
@ -22,9 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
pdp = proliphix.PDP(host, username, password)
|
||||
|
||||
add_devices([
|
||||
ProliphixThermostat(pdp)
|
||||
])
|
||||
add_devices([ProliphixThermostat(pdp)])
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
@ -56,7 +65,7 @@ class ProliphixThermostat(ClimateDevice):
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
return {
|
||||
"fan": self._pdp.fan_state
|
||||
ATTR_FAN: self._pdp.fan_state
|
||||
}
|
||||
|
||||
@property
|
||||
|
@ -8,15 +8,28 @@ import datetime
|
||||
import logging
|
||||
from urllib.error import URLError
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, STATE_OFF,
|
||||
ClimateDevice)
|
||||
ClimateDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['radiotherm==1.2']
|
||||
HOLD_TEMP = 'hold_temp'
|
||||
|
||||
_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):
|
||||
"""Setup the Radio Thermostat."""
|
||||
@ -29,10 +42,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
hosts.append(radiotherm.discover.discover_address())
|
||||
|
||||
if hosts is None:
|
||||
_LOGGER.error("No radiotherm thermostats detected.")
|
||||
_LOGGER.error("No Radiotherm Thermostats detected")
|
||||
return False
|
||||
|
||||
hold_temp = config.get(HOLD_TEMP, False)
|
||||
hold_temp = config.get(CONF_HOLD_TEMP)
|
||||
tstats = []
|
||||
|
||||
for host in hosts:
|
||||
@ -60,6 +73,7 @@ class RadioThermostat(ClimateDevice):
|
||||
self._name = None
|
||||
self.hold_temp = hold_temp
|
||||
self.update()
|
||||
self._operation_list = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -75,8 +89,8 @@ class RadioThermostat(ClimateDevice):
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
return {
|
||||
"fan": self.device.fmode['human'],
|
||||
"mode": self.device.tmode['human']
|
||||
ATTR_FAN: self.device.fmode['human'],
|
||||
ATTR_MODE: self.device.tmode['human']
|
||||
}
|
||||
|
||||
@property
|
||||
@ -89,6 +103,11 @@ class RadioThermostat(ClimateDevice):
|
||||
"""Return the current operation. head, cool idle."""
|
||||
return self._current_operation
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
"""Return the operation modes list."""
|
||||
return self._operation_list
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
@ -124,8 +143,11 @@ class RadioThermostat(ClimateDevice):
|
||||
def set_time(self):
|
||||
"""Set device time."""
|
||||
now = datetime.datetime.now()
|
||||
self.device.time = {'day': now.weekday(),
|
||||
'hour': now.hour, 'minute': now.minute}
|
||||
self.device.time = {
|
||||
'day': now.weekday(),
|
||||
'hour': now.hour,
|
||||
'minute': now.minute
|
||||
}
|
||||
|
||||
def set_operation_mode(self, operation_mode):
|
||||
"""Set operation mode (auto, cool, heat, off)."""
|
||||
|
137
homeassistant/components/climate/vera.py
Normal file
137
homeassistant/components/climate/vera.py
Normal 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()
|
@ -261,6 +261,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
||||
self._target_temperature = temperature
|
||||
# ZXT-120 responds only to whole int
|
||||
value.data = round(temperature, 0)
|
||||
self.update_ha_state()
|
||||
break
|
||||
else:
|
||||
_LOGGER.debug("Setting new setpoint for %s, "
|
||||
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['fuzzywuzzy==0.11.1']
|
||||
REQUIREMENTS = ['fuzzywuzzy==0.12.0']
|
||||
|
||||
ATTR_TEXT = 'text'
|
||||
|
||||
|
@ -25,9 +25,8 @@ from homeassistant.const import (
|
||||
DOMAIN = 'cover'
|
||||
SCAN_INTERVAL = 15
|
||||
|
||||
GROUP_NAME_ALL_COVERS = 'all_covers'
|
||||
ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format(
|
||||
GROUP_NAME_ALL_COVERS)
|
||||
GROUP_NAME_ALL_COVERS = 'all covers'
|
||||
ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format('all_covers')
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
|
@ -14,7 +14,6 @@ from homeassistant.const import (
|
||||
CONF_COMMAND_CLOSE, CONF_COMMAND_OPEN, CONF_COMMAND_STATE,
|
||||
CONF_COMMAND_STOP, CONF_COVERS, CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import template
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -24,7 +23,7 @@ COVER_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_COMMAND_STATE): cv.string,
|
||||
vol.Optional(CONF_COMMAND_STOP, default='true'): 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({
|
||||
@ -38,6 +37,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
covers = []
|
||||
|
||||
for device_name, device_config in devices.items():
|
||||
value_template = device_config.get(CONF_VALUE_TEMPLATE)
|
||||
value_template.hass = hass
|
||||
|
||||
covers.append(
|
||||
CommandCover(
|
||||
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_STOP),
|
||||
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:
|
||||
payload = str(self._query_state())
|
||||
if self._value_template:
|
||||
payload = template.render_with_possible_json_value(
|
||||
self._hass, self._value_template, payload)
|
||||
payload = self._value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
self._state = int(payload)
|
||||
|
||||
def open_cover(self, **kwargs):
|
||||
|
109
homeassistant/components/cover/isy994.py
Normal file
109
homeassistant/components/cover/isy994.py
Normal 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')
|
@ -15,7 +15,6 @@ from homeassistant.const import (
|
||||
STATE_CLOSED)
|
||||
from homeassistant.components.mqtt import (
|
||||
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -28,10 +27,10 @@ CONF_PAYLOAD_STOP = 'payload_stop'
|
||||
CONF_STATE_OPEN = 'state_open'
|
||||
CONF_STATE_CLOSED = 'state_closed'
|
||||
|
||||
DEFAULT_NAME = "MQTT Cover"
|
||||
DEFAULT_PAYLOAD_OPEN = "OPEN"
|
||||
DEFAULT_PAYLOAD_CLOSE = "CLOSE"
|
||||
DEFAULT_PAYLOAD_STOP = "STOP"
|
||||
DEFAULT_NAME = 'MQTT Cover'
|
||||
DEFAULT_PAYLOAD_OPEN = 'OPEN'
|
||||
DEFAULT_PAYLOAD_CLOSE = 'CLOSE'
|
||||
DEFAULT_PAYLOAD_STOP = 'STOP'
|
||||
DEFAULT_OPTIMISTIC = 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_CLOSED, default=STATE_CLOSED): cv.string,
|
||||
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):
|
||||
"""Add MQTT Cover."""
|
||||
add_devices_callback([MqttCover(
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the MQTT Cover."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
add_devices([MqttCover(
|
||||
hass,
|
||||
config[CONF_NAME],
|
||||
config.get(CONF_NAME),
|
||||
config.get(CONF_STATE_TOPIC),
|
||||
config[CONF_COMMAND_TOPIC],
|
||||
config[CONF_QOS],
|
||||
config[CONF_RETAIN],
|
||||
config[CONF_STATE_OPEN],
|
||||
config[CONF_STATE_CLOSED],
|
||||
config[CONF_PAYLOAD_OPEN],
|
||||
config[CONF_PAYLOAD_CLOSE],
|
||||
config[CONF_PAYLOAD_STOP],
|
||||
config[CONF_OPTIMISTIC],
|
||||
config.get(CONF_VALUE_TEMPLATE)
|
||||
config.get(CONF_COMMAND_TOPIC),
|
||||
config.get(CONF_QOS),
|
||||
config.get(CONF_RETAIN),
|
||||
config.get(CONF_STATE_OPEN),
|
||||
config.get(CONF_STATE_CLOSED),
|
||||
config.get(CONF_PAYLOAD_OPEN),
|
||||
config.get(CONF_PAYLOAD_CLOSE),
|
||||
config.get(CONF_PAYLOAD_STOP),
|
||||
config.get(CONF_OPTIMISTIC),
|
||||
value_template,
|
||||
)])
|
||||
|
||||
|
||||
@ -93,8 +93,8 @@ class MqttCover(CoverDevice):
|
||||
def message_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
if value_template is not None:
|
||||
payload = template.render_with_possible_json_value(
|
||||
hass, value_template, payload)
|
||||
payload = value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
if payload == self._state_open:
|
||||
self._state = False
|
||||
_LOGGER.warning("state=%s", int(self._state))
|
||||
@ -111,8 +111,8 @@ class MqttCover(CoverDevice):
|
||||
self.update_ha_state()
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Payload is not True or False or"
|
||||
" integer(0-100) %s", payload)
|
||||
"Payload is not True, False, or integer (0-100): %s",
|
||||
payload)
|
||||
if self._state_topic is None:
|
||||
# Force into optimistic mode.
|
||||
self._optimistic = True
|
||||
@ -149,7 +149,7 @@ class MqttCover(CoverDevice):
|
||||
self._qos, self._retain)
|
||||
if self._optimistic:
|
||||
# Optimistically assume that cover has changed state.
|
||||
self._state = 100
|
||||
self._state = False
|
||||
self.update_ha_state()
|
||||
|
||||
def close_cover(self, **kwargs):
|
||||
@ -158,7 +158,7 @@ class MqttCover(CoverDevice):
|
||||
self._qos, self._retain)
|
||||
if self._optimistic:
|
||||
# Optimistically assume that cover has changed state.
|
||||
self._state = 0
|
||||
self._state = True
|
||||
self.update_ha_state()
|
||||
|
||||
def stop_cover(self, **kwargs):
|
||||
|
@ -7,64 +7,69 @@ https://github.com/andrewshilliday/garage-door-controller
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/cover.rpi_gpio/
|
||||
"""
|
||||
|
||||
import logging
|
||||
from time import sleep
|
||||
|
||||
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.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__)
|
||||
|
||||
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(
|
||||
cv.ensure_list,
|
||||
[
|
||||
vol.Schema({
|
||||
'name': str,
|
||||
'relay_pin': int,
|
||||
'state_pin': int,
|
||||
CONF_NAME: cv.string,
|
||||
CONF_RELAY_PIN: cv.positive_int,
|
||||
CONF_STATE_PIN: cv.positive_int,
|
||||
})
|
||||
]
|
||||
)
|
||||
PLATFORM_SCHEMA = vol.Schema({
|
||||
'platform': str,
|
||||
vol.Required('covers'): _COVERS_SCHEMA,
|
||||
vol.Optional(STATE_PULL_MODE, default=DEFAULT_PULL_MODE): cv.string,
|
||||
vol.Optional(RELAY_TIME, default=DEFAULT_RELAY_TIME): vol.Coerce(int),
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COVERS): _COVERS_SCHEMA,
|
||||
vol.Optional(CONF_STATE_PULL_MODE, default=DEFAULT_STATE_PULL_MODE):
|
||||
cv.string,
|
||||
vol.Optional(CONF_RELAY_TIME, default=DEFAULT_RELAY_TIME): cv.positive_int,
|
||||
})
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the cover platform."""
|
||||
relay_time = config.get(RELAY_TIME)
|
||||
state_pull_mode = config.get(STATE_PULL_MODE)
|
||||
"""Setup the RPi cover platform."""
|
||||
relay_time = config.get(CONF_RELAY_TIME)
|
||||
state_pull_mode = config.get(CONF_STATE_PULL_MODE)
|
||||
covers = []
|
||||
covers_conf = config.get('covers')
|
||||
covers_conf = config.get(CONF_COVERS)
|
||||
|
||||
for cover in covers_conf:
|
||||
covers.append(RPiGPIOCover(cover['name'], cover['relay_pin'],
|
||||
cover['state_pin'],
|
||||
state_pull_mode,
|
||||
relay_time))
|
||||
covers.append(RPiGPIOCover(
|
||||
cover[CONF_NAME], cover[CONF_RELAY_PIN], cover[CONF_STATE_PIN],
|
||||
state_pull_mode, relay_time))
|
||||
add_devices(covers)
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class RPiGPIOCover(CoverDevice):
|
||||
"""Representation of a Raspberry cover."""
|
||||
"""Representation of a Raspberry GPIO cover."""
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, name, relay_pin, state_pin,
|
||||
state_pull_mode, relay_time):
|
||||
def __init__(self, name, relay_pin, state_pin, state_pull_mode,
|
||||
relay_time):
|
||||
"""Initialize the cover."""
|
||||
self._name = name
|
||||
self._state = False
|
||||
@ -79,7 +84,7 @@ class RPiGPIOCover(CoverDevice):
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the ID of this cover."""
|
||||
return "{}.{}".format(self.__class__, self._name)
|
||||
return '{}.{}'.format(self.__class__, self._name)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -6,37 +6,43 @@ https://home-assistant.io/components/cover.scsgate/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.scsgate as scsgate
|
||||
from homeassistant.components.cover import CoverDevice
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import (CONF_DEVICES, CONF_NAME)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
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."""
|
||||
devices = config.get('devices')
|
||||
devices = config.get(CONF_DEVICES)
|
||||
covers = []
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if devices:
|
||||
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
|
||||
|
||||
logger.info("Adding %s scsgate.cover", entity_info[CONF_NAME])
|
||||
|
||||
name = entity_info[CONF_NAME]
|
||||
scs_id = entity_info[SCS_ID]
|
||||
cover = SCSGateCover(
|
||||
name=name,
|
||||
scs_id=scs_id,
|
||||
logger=logger)
|
||||
scs_id = entity_info[scsgate.CONF_SCS_ID]
|
||||
|
||||
logger.info("Adding %s scsgate.cover", name)
|
||||
|
||||
cover = SCSGateCover(name=name, scs_id=scs_id, logger=logger)
|
||||
scsgate.SCSGATE.add_device(cover)
|
||||
covers.append(cover)
|
||||
|
||||
add_devices_callback(covers)
|
||||
add_devices(covers)
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||
@ -91,6 +97,5 @@ class SCSGateCover(CoverDevice):
|
||||
|
||||
def process_event(self, message):
|
||||
"""Handle a SCSGate message related with this cover."""
|
||||
self._logger.debug(
|
||||
"Rollershutter %s, got message %s",
|
||||
self._scs_id, message.toggled)
|
||||
self._logger.debug("Cover %s, got message %s",
|
||||
self._scs_id, message.toggled)
|
||||
|
70
homeassistant/components/cover/vera.py
Normal file
70
homeassistant/components/cover/vera.py
Normal 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()
|
@ -4,30 +4,17 @@ Support for Wink Covers.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/cover.wink/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.cover import CoverDevice
|
||||
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):
|
||||
"""Setup the Wink cover platform."""
|
||||
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
|
||||
pywink.get_shades())
|
||||
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):
|
||||
"""Representation of a Wink covers."""
|
||||
"""Representation of a Wink cover device."""
|
||||
|
||||
def __init__(self, wink):
|
||||
"""Initialize the cover."""
|
||||
WinkDevice.__init__(self, wink)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Wink Shades don't track their position."""
|
||||
return False
|
||||
|
||||
def close_cover(self):
|
||||
"""Close the shade."""
|
||||
self.wink.set_state(0)
|
||||
|
@ -139,7 +139,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
|
||||
|
||||
def set_cover_position(self, position, **kwargs):
|
||||
"""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):
|
||||
"""Stop the roller shutter."""
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Tracking for bluetooth devices."""
|
||||
"""Tracking for bluetooth low energy devices."""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
|
@ -6,9 +6,11 @@ https://home-assistant.io/components/device_tracker.locative/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.device_tracker import DOMAIN
|
||||
from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
# pylint: disable=unused-import
|
||||
from homeassistant.components.device_tracker import ( # NOQA
|
||||
DOMAIN, PLATFORM_SCHEMA)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -25,8 +27,8 @@ def setup_scanner(hass, config, see):
|
||||
class LocativeView(HomeAssistantView):
|
||||
"""View to handle locative requests."""
|
||||
|
||||
url = "/api/locative"
|
||||
name = "api:locative"
|
||||
url = '/api/locative'
|
||||
name = 'api:locative'
|
||||
|
||||
def __init__(self, hass, see):
|
||||
"""Initialize Locative url endpoints."""
|
||||
@ -43,22 +45,22 @@ class LocativeView(HomeAssistantView):
|
||||
data = request.values
|
||||
|
||||
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)
|
||||
|
||||
if 'device' not in data:
|
||||
_LOGGER.error("Device id not specified.")
|
||||
return ("Device id not specified.",
|
||||
_LOGGER.error('Device id not specified.')
|
||||
return ('Device id not specified.',
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
||||
if 'id' not in data:
|
||||
_LOGGER.error("Location id not specified.")
|
||||
return ("Location id not specified.",
|
||||
_LOGGER.error('Location id not specified.')
|
||||
return ('Location id not specified.',
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
||||
if 'trigger' not in data:
|
||||
_LOGGER.error("Trigger is not specified.")
|
||||
return ("Trigger is not specified.",
|
||||
_LOGGER.error('Trigger is not specified.')
|
||||
return ('Trigger is not specified.',
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
||||
device = data['device'].replace('-', '')
|
||||
@ -67,15 +69,15 @@ class LocativeView(HomeAssistantView):
|
||||
|
||||
if direction == 'enter':
|
||||
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':
|
||||
current_state = self.hass.states.get(
|
||||
"{}.{}".format(DOMAIN, device))
|
||||
'{}.{}'.format(DOMAIN, device))
|
||||
|
||||
if current_state is None or current_state.state == location_name:
|
||||
self.see(dev_id=device, location_name=STATE_NOT_HOME)
|
||||
return "Setting location to not home"
|
||||
return 'Setting location to not home'
|
||||
else:
|
||||
# Ignore the message if it is telling us to exit a zone that we
|
||||
# aren't currently in. This occurs when a zone is entered
|
||||
@ -87,10 +89,10 @@ class LocativeView(HomeAssistantView):
|
||||
elif direction == 'test':
|
||||
# In the app, a test message can be sent. Just return something to
|
||||
# the user to let them know that it works.
|
||||
return "Received test message."
|
||||
return 'Received test message.'
|
||||
|
||||
else:
|
||||
_LOGGER.error("Received unidentified message from Locative: %s",
|
||||
_LOGGER.error('Received unidentified message from Locative: %s',
|
||||
direction)
|
||||
return ("Received unidentified message: {}".format(direction),
|
||||
return ('Received unidentified message: {}'.format(direction),
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
@ -10,11 +10,13 @@ import subprocess
|
||||
from collections import namedtuple
|
||||
from datetime import timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
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.helpers import validate_config
|
||||
from homeassistant.util import Throttle, convert
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
# Return cached results if last scan was less then this time ago
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||
@ -27,18 +29,21 @@ CONF_EXCLUDE = 'exclude'
|
||||
|
||||
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):
|
||||
"""Validate the configuration and return a Nmap scanner."""
|
||||
if not validate_config(config, {DOMAIN: [CONF_HOSTS]},
|
||||
_LOGGER):
|
||||
return None
|
||||
|
||||
scanner = NmapDeviceScanner(config[DOMAIN])
|
||||
|
||||
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):
|
||||
@ -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))
|
||||
if match:
|
||||
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
|
||||
|
||||
|
||||
class NmapDeviceScanner(object):
|
||||
"""This class scans for devices using nmap."""
|
||||
|
||||
exclude = []
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize the scanner."""
|
||||
self.last_results = []
|
||||
|
||||
self.hosts = config[CONF_HOSTS]
|
||||
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.success_init = self._update_info()
|
||||
_LOGGER.info("nmap scanner initialized")
|
||||
_LOGGER.info('nmap scanner initialized')
|
||||
|
||||
def scan_devices(self):
|
||||
"""Scan for new devices and return a list with found device IDs."""
|
||||
@ -90,21 +97,18 @@ class NmapDeviceScanner(object):
|
||||
|
||||
Returns boolean if scanning successful.
|
||||
"""
|
||||
_LOGGER.info("Scanning")
|
||||
_LOGGER.info('Scanning')
|
||||
|
||||
from nmap import PortScanner, PortScannerError
|
||||
scanner = PortScanner()
|
||||
|
||||
options = "-F --host-timeout 5s "
|
||||
exclude = "--exclude "
|
||||
options = '-F --host-timeout 5s '
|
||||
|
||||
if self.home_interval:
|
||||
boundary = dt_util.now() - self.home_interval
|
||||
last_results = [device for device in self.last_results
|
||||
if device.last_update > boundary]
|
||||
if last_results:
|
||||
# Pylint is confused here.
|
||||
# pylint: disable=no-member
|
||||
exclude_hosts = self.exclude + [device.ip for device
|
||||
in last_results]
|
||||
else:
|
||||
@ -113,8 +117,7 @@ class NmapDeviceScanner(object):
|
||||
last_results = []
|
||||
exclude_hosts = self.exclude
|
||||
if exclude_hosts:
|
||||
exclude = " --exclude {}".format(",".join(exclude_hosts))
|
||||
options += exclude
|
||||
options += ' --exclude {}'.format(','.join(exclude_hosts))
|
||||
|
||||
try:
|
||||
result = scanner.scan(hosts=self.hosts, arguments=options)
|
||||
@ -134,5 +137,5 @@ class NmapDeviceScanner(object):
|
||||
|
||||
self.last_results = last_results
|
||||
|
||||
_LOGGER.info("nmap scan successful")
|
||||
_LOGGER.info('nmap scan successful')
|
||||
return True
|
||||
|
@ -218,9 +218,18 @@ def setup_scanner(hass, config, see):
|
||||
lat = wayp[WAYPOINT_LAT_KEY]
|
||||
lon = wayp[WAYPOINT_LON_KEY]
|
||||
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_comp.ICON_IMPORT, False, True)
|
||||
zone_comp.add_zone(hass, pretty_name, zone)
|
||||
zone_comp.ICON_IMPORT, False)
|
||||
zone.entity_id = entity_id
|
||||
zone.update_ha_state()
|
||||
|
||||
def see_beacons(dev_id, kwargs_param):
|
||||
"""Set active beacons to the current location."""
|
||||
|
@ -10,9 +10,11 @@ import telnetlib
|
||||
import threading
|
||||
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.helpers import validate_config
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
# 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__)
|
||||
|
||||
_DEVICES_REGEX = re.compile(
|
||||
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<status>([^\s]+))\s+' +
|
||||
r'(?P<type>([^\s]+))\s+' +
|
||||
r'(?P<intf>([^\s]+))\s+' +
|
||||
r'(?P<hwintf>([^\s]+))\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<status>([^\s]+))\s+'
|
||||
r'(?P<type>([^\s]+))\s+'
|
||||
r'(?P<intf>([^\s]+))\s+'
|
||||
r'(?P<hwintf>([^\s]+))\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
|
||||
def get_scanner(hass, config):
|
||||
"""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])
|
||||
|
||||
return scanner if scanner.success_init else None
|
||||
@ -84,7 +87,7 @@ class ThomsonDeviceScanner(object):
|
||||
return False
|
||||
|
||||
with self.lock:
|
||||
_LOGGER.info("Checking ARP")
|
||||
_LOGGER.info('Checking ARP')
|
||||
data = self.get_thomson_data()
|
||||
if not data:
|
||||
return False
|
||||
@ -108,11 +111,11 @@ class ThomsonDeviceScanner(object):
|
||||
devices_result = telnet.read_until(b'=>').split(b'\r\n')
|
||||
telnet.write('exit\r\n'.encode('ascii'))
|
||||
except EOFError:
|
||||
_LOGGER.exception("Unexpected response from router")
|
||||
_LOGGER.exception('Unexpected response from router')
|
||||
return
|
||||
except ConnectionRefusedError:
|
||||
_LOGGER.exception("Connection refused by router," +
|
||||
" is telnet enabled?")
|
||||
_LOGGER.exception('Connection refused by router,'
|
||||
' is telnet enabled?')
|
||||
return
|
||||
|
||||
devices = {}
|
||||
|
@ -12,10 +12,11 @@ import threading
|
||||
from datetime import timedelta
|
||||
|
||||
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.helpers import validate_config
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
# Return cached results if last scan was less then this time ago
|
||||
@ -23,26 +24,22 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||
|
||||
_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):
|
||||
"""Validate the configuration and return a TP-Link scanner."""
|
||||
if not validate_config(config,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
return None
|
||||
for cls in [Tplink4DeviceScanner, Tplink3DeviceScanner,
|
||||
Tplink2DeviceScanner, TplinkDeviceScanner]:
|
||||
scanner = cls(config[DOMAIN])
|
||||
if scanner.success_init:
|
||||
return scanner
|
||||
|
||||
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
|
||||
return None
|
||||
|
||||
|
||||
class TplinkDeviceScanner(object):
|
||||
|
1085
homeassistant/components/emulated_hue.py
Executable file → Normal file
1085
homeassistant/components/emulated_hue.py
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.helpers.entity import Entity
|
||||
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__)
|
||||
DOMAIN = 'envisalink'
|
||||
@ -34,12 +34,14 @@ CONF_PARTITIONS = 'partitions'
|
||||
CONF_ZONENAME = 'name'
|
||||
CONF_ZONETYPE = 'type'
|
||||
CONF_PARTITIONNAME = 'name'
|
||||
CONF_PANIC = 'panic_type'
|
||||
|
||||
DEFAULT_PORT = 4025
|
||||
DEFAULT_EVL_VERSION = 3
|
||||
DEFAULT_KEEPALIVE = 60
|
||||
DEFAULT_ZONEDUMP_INTERVAL = 30
|
||||
DEFAULT_ZONETYPE = 'opening'
|
||||
DEFAULT_PANIC = 'Police'
|
||||
|
||||
SIGNAL_ZONE_UPDATE = 'zones_updated'
|
||||
SIGNAL_PARTITION_UPDATE = 'partition_updated'
|
||||
@ -60,6 +62,7 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASS): 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_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA},
|
||||
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)
|
||||
_code = config.get(CONF_CODE)
|
||||
_panel_type = config.get(CONF_PANEL_TYPE)
|
||||
_panic_type = config.get(CONF_PANIC)
|
||||
_version = config.get(CONF_EVL_VERSION)
|
||||
_user = config.get(CONF_USERNAME)
|
||||
_pass = config.get(CONF_PASS)
|
||||
@ -104,7 +108,8 @@ def setup(hass, base_config):
|
||||
_user,
|
||||
_pass,
|
||||
_zone_dump,
|
||||
_keep_alive)
|
||||
_keep_alive,
|
||||
hass.loop)
|
||||
|
||||
def login_fail_callback(data):
|
||||
"""Callback for when the evl rejects our login."""
|
||||
@ -149,7 +154,7 @@ def setup(hass, base_config):
|
||||
|
||||
def start_envisalink(event):
|
||||
"""Startup process for the Envisalink."""
|
||||
EVL_CONTROLLER.start()
|
||||
hass.loop.call_soon_threadsafe(EVL_CONTROLLER.start)
|
||||
for _ in range(10):
|
||||
if 'success' in _connect_status:
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink)
|
||||
@ -177,14 +182,15 @@ def setup(hass, base_config):
|
||||
# Load sub-components for Envisalink
|
||||
if _partitions:
|
||||
load_platform(hass, 'alarm_control_panel', 'envisalink',
|
||||
{'partitions': _partitions,
|
||||
'code': _code}, config)
|
||||
{CONF_PARTITIONS: _partitions,
|
||||
CONF_CODE: _code,
|
||||
CONF_PANIC: _panic_type}, config)
|
||||
load_platform(hass, 'sensor', 'envisalink',
|
||||
{'partitions': _partitions,
|
||||
'code': _code}, config)
|
||||
{CONF_PARTITIONS: _partitions,
|
||||
CONF_CODE: _code}, config)
|
||||
if _zones:
|
||||
load_platform(hass, 'binary_sensor', 'envisalink',
|
||||
{'zones': _zones}, config)
|
||||
{CONF_ZONES: _zones}, config)
|
||||
|
||||
return True
|
||||
|
||||
|
120
homeassistant/components/fan/isy994.py
Normal file
120
homeassistant/components/fan/isy994.py
Normal 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
|
@ -5,17 +5,16 @@ For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/fan.mqtt/
|
||||
"""
|
||||
import logging
|
||||
from functools import partial
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.mqtt as mqtt
|
||||
from homeassistant.const import (CONF_NAME, CONF_OPTIMISTIC, CONF_STATE,
|
||||
STATE_ON, STATE_OFF)
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_OPTIMISTIC, CONF_STATE, STATE_ON, STATE_OFF,
|
||||
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON)
|
||||
from homeassistant.components.mqtt import (
|
||||
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
|
||||
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,
|
||||
SPEED_HIGH, FanEntity,
|
||||
SUPPORT_SET_SPEED, SUPPORT_OSCILLATE,
|
||||
@ -23,33 +22,31 @@ from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_MEDIUM,
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ["mqtt"]
|
||||
DEPENDENCIES = ['mqtt']
|
||||
|
||||
CONF_STATE_VALUE_TEMPLATE = "state_value_template"
|
||||
CONF_SPEED_STATE_TOPIC = "speed_state_topic"
|
||||
CONF_SPEED_COMMAND_TOPIC = "speed_command_topic"
|
||||
CONF_SPEED_VALUE_TEMPLATE = "speed_value_template"
|
||||
CONF_OSCILLATION_STATE_TOPIC = "oscillation_state_topic"
|
||||
CONF_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic"
|
||||
CONF_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template"
|
||||
CONF_PAYLOAD_ON = "payload_on"
|
||||
CONF_PAYLOAD_OFF = "payload_off"
|
||||
CONF_PAYLOAD_OSCILLATION_ON = "payload_oscillation_on"
|
||||
CONF_PAYLOAD_OSCILLATION_OFF = "payload_oscillation_off"
|
||||
CONF_PAYLOAD_LOW_SPEED = "payload_low_speed"
|
||||
CONF_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed"
|
||||
CONF_PAYLOAD_HIGH_SPEED = "payload_high_speed"
|
||||
CONF_SPEED_LIST = "speeds"
|
||||
CONF_STATE_VALUE_TEMPLATE = 'state_value_template'
|
||||
CONF_SPEED_STATE_TOPIC = 'speed_state_topic'
|
||||
CONF_SPEED_COMMAND_TOPIC = 'speed_command_topic'
|
||||
CONF_SPEED_VALUE_TEMPLATE = 'speed_value_template'
|
||||
CONF_OSCILLATION_STATE_TOPIC = 'oscillation_state_topic'
|
||||
CONF_OSCILLATION_COMMAND_TOPIC = 'oscillation_command_topic'
|
||||
CONF_OSCILLATION_VALUE_TEMPLATE = 'oscillation_value_template'
|
||||
CONF_PAYLOAD_OSCILLATION_ON = 'payload_oscillation_on'
|
||||
CONF_PAYLOAD_OSCILLATION_OFF = 'payload_oscillation_off'
|
||||
CONF_PAYLOAD_LOW_SPEED = 'payload_low_speed'
|
||||
CONF_PAYLOAD_MEDIUM_SPEED = 'payload_medium_speed'
|
||||
CONF_PAYLOAD_HIGH_SPEED = 'payload_high_speed'
|
||||
CONF_SPEED_LIST = 'speeds'
|
||||
|
||||
DEFAULT_NAME = "MQTT Fan"
|
||||
DEFAULT_PAYLOAD_ON = "ON"
|
||||
DEFAULT_PAYLOAD_OFF = "OFF"
|
||||
DEFAULT_NAME = 'MQTT Fan'
|
||||
DEFAULT_PAYLOAD_ON = 'ON'
|
||||
DEFAULT_PAYLOAD_OFF = 'OFF'
|
||||
DEFAULT_OPTIMISTIC = False
|
||||
|
||||
OSCILLATE_ON_PAYLOAD = "oscillate_on"
|
||||
OSCILLATE_OFF_PAYLOAD = "oscillate_off"
|
||||
OSCILLATE_ON_PAYLOAD = 'oscillate_on'
|
||||
OSCILLATE_OFF_PAYLOAD = 'oscillate_off'
|
||||
|
||||
OSCILLATION = "oscillation"
|
||||
OSCILLATION = 'oscillation'
|
||||
|
||||
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
|
||||
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
|
||||
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."""
|
||||
add_devices_callback([MqttFan(
|
||||
add_devices([MqttFan(
|
||||
hass,
|
||||
config[CONF_NAME],
|
||||
config.get(CONF_NAME),
|
||||
{
|
||||
key: config.get(key) for key in (
|
||||
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),
|
||||
OSCILLATION: config.get(CONF_OSCILLATION_VALUE_TEMPLATE)
|
||||
},
|
||||
config[CONF_QOS],
|
||||
config[CONF_RETAIN],
|
||||
config.get(CONF_QOS),
|
||||
config.get(CONF_RETAIN),
|
||||
{
|
||||
STATE_ON: config[CONF_PAYLOAD_ON],
|
||||
STATE_OFF: config[CONF_PAYLOAD_OFF],
|
||||
OSCILLATE_ON_PAYLOAD: config[CONF_PAYLOAD_OSCILLATION_ON],
|
||||
OSCILLATE_OFF_PAYLOAD: config[CONF_PAYLOAD_OSCILLATION_OFF],
|
||||
SPEED_LOW: config[CONF_PAYLOAD_LOW_SPEED],
|
||||
SPEED_MEDIUM: config[CONF_PAYLOAD_MEDIUM_SPEED],
|
||||
SPEED_HIGH: config[CONF_PAYLOAD_HIGH_SPEED],
|
||||
STATE_ON: config.get(CONF_PAYLOAD_ON),
|
||||
STATE_OFF: config.get(CONF_PAYLOAD_OFF),
|
||||
OSCILLATE_ON_PAYLOAD: config.get(CONF_PAYLOAD_OSCILLATION_ON),
|
||||
OSCILLATE_OFF_PAYLOAD: config.get(CONF_PAYLOAD_OSCILLATION_OFF),
|
||||
SPEED_LOW: config.get(CONF_PAYLOAD_LOW_SPEED),
|
||||
SPEED_MEDIUM: config.get(CONF_PAYLOAD_MEDIUM_SPEED),
|
||||
SPEED_HIGH: config.get(CONF_PAYLOAD_HIGH_SPEED),
|
||||
},
|
||||
config[CONF_SPEED_LIST],
|
||||
config[CONF_OPTIMISTIC],
|
||||
config.get(CONF_SPEED_LIST),
|
||||
config.get(CONF_OPTIMISTIC),
|
||||
)])
|
||||
|
||||
|
||||
@ -120,7 +117,7 @@ class MqttFan(FanEntity):
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, hass, name, topic, templates, qos, retain, payload,
|
||||
speed_list, optimistic):
|
||||
"""Initialize MQTT fan."""
|
||||
"""Initialize the MQTT fan."""
|
||||
self._hass = hass
|
||||
self._name = name
|
||||
self._topic = topic
|
||||
@ -129,11 +126,10 @@ class MqttFan(FanEntity):
|
||||
self._payload = payload
|
||||
self._speed_list = speed_list
|
||||
self._optimistic = optimistic or topic[CONF_STATE_TOPIC] is None
|
||||
self._optimistic_oscillation = (optimistic or
|
||||
topic[CONF_OSCILLATION_STATE_TOPIC]
|
||||
is None)
|
||||
self._optimistic_speed = (optimistic or
|
||||
topic[CONF_SPEED_STATE_TOPIC] is None)
|
||||
self._optimistic_oscillation = (
|
||||
optimistic or topic[CONF_OSCILLATION_STATE_TOPIC] is None)
|
||||
self._optimistic_speed = (
|
||||
optimistic or topic[CONF_SPEED_STATE_TOPIC] is None)
|
||||
self._state = False
|
||||
self._supported_features = 0
|
||||
self._supported_features |= (topic[CONF_OSCILLATION_STATE_TOPIC]
|
||||
@ -141,9 +137,12 @@ class MqttFan(FanEntity):
|
||||
self._supported_features |= (topic[CONF_SPEED_STATE_TOPIC]
|
||||
is not None and SUPPORT_SET_SPEED)
|
||||
|
||||
templates = {key: ((lambda value: value) if tpl is None else
|
||||
partial(render_with_possible_json_value, hass, tpl))
|
||||
for key, tpl in templates.items()}
|
||||
for key, tpl in list(templates.items()):
|
||||
if tpl is None:
|
||||
templates[key] = lambda value: value
|
||||
else:
|
||||
tpl.hass = hass
|
||||
templates[key] = tpl.render_with_possible_json_value
|
||||
|
||||
def state_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
|
70
homeassistant/components/ffmpeg.py
Normal file
70
homeassistant/components/ffmpeg.py
Normal 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
|
@ -1,14 +1,14 @@
|
||||
"""DO NOT MODIFY. Auto-generated by script/fingerprint_frontend."""
|
||||
|
||||
FINGERPRINTS = {
|
||||
"core.js": "1fd10c1fcdf56a61f60cf861d5a0368c",
|
||||
"frontend.html": "20defe06c11b2fa2f076dc92b6c3b0dd",
|
||||
"mdi.html": "710b84acc99b32514f52291aba9cd8e8",
|
||||
"panels/ha-panel-dev-event.html": "3cc881ae8026c0fba5aa67d334a3ab2b",
|
||||
"core.js": "78862c0984279b6876f594ffde45177c",
|
||||
"frontend.html": "c1753e1ce530f978036742477c96d2fd",
|
||||
"mdi.html": "6bd013a8252e19b3c1f1de52994cfbe4",
|
||||
"panels/ha-panel-dev-event.html": "c4a5f70eece9f92616a65e8d26be803e",
|
||||
"panels/ha-panel-dev-info.html": "34e2df1af32e60fffcafe7e008a92169",
|
||||
"panels/ha-panel-dev-service.html": "bb5c587ada694e0fd42ceaaedd6fe6aa",
|
||||
"panels/ha-panel-dev-state.html": "4608326978256644c42b13940c028e0a",
|
||||
"panels/ha-panel-dev-template.html": "0a099d4589636ed3038a3e9f020468a7",
|
||||
"panels/ha-panel-dev-service.html": "07e83c6b7f79d78a59258f6dba477b54",
|
||||
"panels/ha-panel-dev-state.html": "fd8eb946856b1346a87a51d0c86854ff",
|
||||
"panels/ha-panel-dev-template.html": "7cff8a2ef3f44fdaf357a0d41696bf6d",
|
||||
"panels/ha-panel-history.html": "efe1bcdd7733b09e55f4f965d171c295",
|
||||
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
|
||||
"panels/ha-panel-logbook.html": "66108d82763359a218c9695f0553de40",
|
||||
|
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +1 @@
|
||||
Subproject commit ba588fc779d34a2fbf7cc9a23103c38e3e3e0356
|
||||
Subproject commit 0a4454c68f3c29c77cd60f4315d410d8b3737543
|
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -16,7 +16,6 @@ from homeassistant.const import (
|
||||
from homeassistant.components.mqtt import (
|
||||
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import template
|
||||
|
||||
_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):
|
||||
"""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(
|
||||
hass,
|
||||
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_CLOSE],
|
||||
config[CONF_OPTIMISTIC],
|
||||
config.get(CONF_VALUE_TEMPLATE))])
|
||||
value_template)])
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||
@ -84,8 +86,8 @@ class MqttGarageDoor(GarageDoorDevice):
|
||||
def message_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
if value_template is not None:
|
||||
payload = template.render_with_possible_json_value(
|
||||
hass, value_template, payload)
|
||||
payload = value_template.render_with_possible_json_value(
|
||||
payload)
|
||||
if payload == self._state_open:
|
||||
self._state = True
|
||||
self.update_ha_state()
|
||||
|
@ -4,30 +4,17 @@ Support for Wink garage doors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/garage_door.wink/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.garage_door import GarageDoorDevice
|
||||
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):
|
||||
"""Setup the Wink garage door platform."""
|
||||
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
|
||||
pywink.get_garage_doors())
|
||||
|
||||
|
@ -23,7 +23,7 @@ from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
DOMAIN = 'homematic'
|
||||
REQUIREMENTS = ["pyhomematic==0.1.13"]
|
||||
REQUIREMENTS = ["pyhomematic==0.1.14"]
|
||||
|
||||
HOMEMATIC = None
|
||||
HOMEMATIC_LINK_DELAY = 0.5
|
||||
|
@ -25,9 +25,10 @@ import homeassistant.util.dt as dt_util
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
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_APPROVED_IPS = 'approved_ips'
|
||||
CONF_SERVER_HOST = 'server_host'
|
||||
CONF_SERVER_PORT = 'server_port'
|
||||
CONF_DEVELOPMENT = 'development'
|
||||
@ -71,7 +72,8 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_DEVELOPMENT): cv.string,
|
||||
vol.Optional(CONF_SSL_CERTIFICATE): 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)
|
||||
|
||||
@ -108,6 +110,7 @@ def setup(hass, config):
|
||||
ssl_certificate = conf.get(CONF_SSL_CERTIFICATE)
|
||||
ssl_key = conf.get(CONF_SSL_KEY)
|
||||
cors_origins = conf.get(CONF_CORS_ORIGINS, [])
|
||||
approved_ips = conf.get(CONF_APPROVED_IPS, [])
|
||||
|
||||
server = HomeAssistantWSGI(
|
||||
hass,
|
||||
@ -117,7 +120,8 @@ def setup(hass, config):
|
||||
api_password=api_password,
|
||||
ssl_certificate=ssl_certificate,
|
||||
ssl_key=ssl_key,
|
||||
cors_origins=cors_origins
|
||||
cors_origins=cors_origins,
|
||||
approved_ips=approved_ips
|
||||
)
|
||||
|
||||
def start_wsgi_server(event):
|
||||
@ -249,7 +253,8 @@ class HomeAssistantWSGI(object):
|
||||
# pylint: disable=too-many-arguments
|
||||
|
||||
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."""
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
@ -268,6 +273,7 @@ class HomeAssistantWSGI(object):
|
||||
self.server_host = server_host
|
||||
self.server_port = server_port
|
||||
self.cors_origins = cors_origins
|
||||
self.approved_ips = approved_ips
|
||||
self.event_forwarder = None
|
||||
self.server = None
|
||||
|
||||
@ -468,6 +474,9 @@ class HomeAssistantView(object):
|
||||
if self.hass.wsgi.api_password is None:
|
||||
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, ''),
|
||||
self.hass.wsgi.api_password):
|
||||
# A valid auth header has been set
|
||||
|
@ -6,65 +6,71 @@ https://home-assistant.io/components/influxdb/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import homeassistant.util as util
|
||||
from homeassistant.const import (EVENT_STATE_CHANGED, STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN)
|
||||
import voluptuous as vol
|
||||
|
||||
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 validate_config
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "influxdb"
|
||||
DEPENDENCIES = []
|
||||
|
||||
DEFAULT_HOST = 'localhost'
|
||||
DEFAULT_PORT = 8086
|
||||
DEFAULT_DATABASE = 'home_assistant'
|
||||
DEFAULT_SSL = False
|
||||
DEFAULT_VERIFY_SSL = False
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['influxdb==3.0.0']
|
||||
|
||||
CONF_HOST = 'host'
|
||||
CONF_PORT = 'port'
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
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'
|
||||
|
||||
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
|
||||
def setup(hass, config):
|
||||
"""Setup the InfluxDB component."""
|
||||
from influxdb import InfluxDBClient, exceptions
|
||||
|
||||
if not validate_config(config, {DOMAIN: ['host',
|
||||
CONF_USERNAME,
|
||||
CONF_PASSWORD]}, _LOGGER):
|
||||
return False
|
||||
|
||||
conf = config[DOMAIN]
|
||||
|
||||
host = conf[CONF_HOST]
|
||||
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
|
||||
database = util.convert(conf.get(CONF_DB_NAME), str, DEFAULT_DATABASE)
|
||||
username = util.convert(conf.get(CONF_USERNAME), str)
|
||||
password = util.convert(conf.get(CONF_PASSWORD), str)
|
||||
ssl = util.convert(conf.get(CONF_SSL), bool, DEFAULT_SSL)
|
||||
verify_ssl = util.convert(conf.get(CONF_VERIFY_SSL), bool,
|
||||
DEFAULT_VERIFY_SSL)
|
||||
blacklist = conf.get(CONF_BLACKLIST, [])
|
||||
whitelist = conf.get(CONF_WHITELIST, [])
|
||||
tags = conf.get(CONF_TAGS, {})
|
||||
host = conf.get(CONF_HOST)
|
||||
port = conf.get(CONF_PORT)
|
||||
database = conf.get(CONF_DB_NAME)
|
||||
username = conf.get(CONF_USERNAME)
|
||||
password = conf.get(CONF_PASSWORD)
|
||||
ssl = conf.get(CONF_SSL)
|
||||
verify_ssl = conf.get(CONF_VERIFY_SSL)
|
||||
blacklist = conf.get(CONF_BLACKLIST)
|
||||
whitelist = conf.get(CONF_WHITELIST)
|
||||
tags = conf.get(CONF_TAGS)
|
||||
|
||||
try:
|
||||
influx = InfluxDBClient(host=host, port=port, username=username,
|
||||
password=password, database=database,
|
||||
ssl=ssl, verify_ssl=verify_ssl)
|
||||
influx = InfluxDBClient(
|
||||
host=host, port=port, username=username, password=password,
|
||||
database=database, ssl=ssl, verify_ssl=verify_ssl)
|
||||
influx.query("select * from /.*/ LIMIT 1;")
|
||||
except exceptions.InfluxDBClientError as exc:
|
||||
_LOGGER.error("Database host is not accessible due to '%s', please "
|
||||
@ -106,8 +112,11 @@ def setup(hass, config):
|
||||
}
|
||||
]
|
||||
|
||||
for tag in tags:
|
||||
json_body[0]['tags'][tag] = tags[tag]
|
||||
for key, value in state.attributes.items():
|
||||
if key != 'unit_of_measurement':
|
||||
json_body[0]['fields'][key] = value
|
||||
|
||||
json_body[0]['tags'].update(tags)
|
||||
|
||||
try:
|
||||
influx.write_points(json_body)
|
||||
|
@ -9,11 +9,11 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
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
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.util import slugify
|
||||
|
||||
DOMAIN = 'input_boolean'
|
||||
|
||||
@ -21,14 +21,19 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_NAME = "name"
|
||||
CONF_INITIAL = "initial"
|
||||
CONF_ICON = "icon"
|
||||
CONF_INITIAL = 'initial'
|
||||
|
||||
TOGGLE_SERVICE_SCHEMA = vol.Schema({
|
||||
SERVICE_SCHEMA = vol.Schema({
|
||||
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):
|
||||
"""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})
|
||||
|
||||
|
||||
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):
|
||||
"""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)
|
||||
|
||||
entities = []
|
||||
|
||||
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:
|
||||
cfg = {}
|
||||
|
||||
@ -72,20 +74,24 @@ def setup(hass, config):
|
||||
if not entities:
|
||||
return False
|
||||
|
||||
def toggle_service(service):
|
||||
def handler_service(service):
|
||||
"""Handle a calls to the input boolean services."""
|
||||
target_inputs = component.extract_from_service(service)
|
||||
|
||||
for input_b in target_inputs:
|
||||
if service.service == SERVICE_TURN_ON:
|
||||
input_b.turn_on()
|
||||
else:
|
||||
elif service.service == SERVICE_TURN_OFF:
|
||||
input_b.turn_off()
|
||||
else:
|
||||
input_b.toggle()
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_OFF, toggle_service,
|
||||
schema=TOGGLE_SERVICE_SCHEMA)
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_ON, toggle_service,
|
||||
schema=TOGGLE_SERVICE_SCHEMA)
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handler_service,
|
||||
schema=SERVICE_SCHEMA)
|
||||
hass.services.register(DOMAIN, SERVICE_TURN_ON, handler_service,
|
||||
schema=SERVICE_SCHEMA)
|
||||
hass.services.register(DOMAIN, SERVICE_TOGGLE, handler_service,
|
||||
schema=SERVICE_SCHEMA)
|
||||
|
||||
component.add_entities(entities)
|
||||
|
||||
|
@ -8,19 +8,17 @@ import logging
|
||||
|
||||
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
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.util import slugify
|
||||
|
||||
|
||||
DOMAIN = 'input_select'
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_NAME = 'name'
|
||||
CONF_INITIAL = 'initial'
|
||||
CONF_ICON = 'icon'
|
||||
CONF_OPTIONS = 'options'
|
||||
|
||||
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):
|
||||
"""Set input_select to False."""
|
||||
hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, {
|
||||
@ -44,39 +62,15 @@ def select_option(hass, entity_id, option):
|
||||
|
||||
def setup(hass, config):
|
||||
"""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)
|
||||
|
||||
entities = []
|
||||
|
||||
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)
|
||||
options = cfg.get(CONF_OPTIONS)
|
||||
|
||||
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]
|
||||
|
||||
state = cfg.get(CONF_INITIAL, options[0])
|
||||
icon = cfg.get(CONF_ICON)
|
||||
|
||||
entities.append(InputSelect(object_id, name, state, options, icon))
|
||||
|
||||
if not entities:
|
||||
|
@ -8,21 +8,19 @@ import logging
|
||||
|
||||
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
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.util import slugify
|
||||
|
||||
DOMAIN = 'input_slider'
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_NAME = 'name'
|
||||
CONF_INITIAL = 'initial'
|
||||
CONF_MIN = 'min'
|
||||
CONF_MAX = 'max'
|
||||
CONF_ICON = 'icon'
|
||||
CONF_STEP = 'step'
|
||||
|
||||
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):
|
||||
"""Set input_slider to value."""
|
||||
hass.services.call(DOMAIN, SERVICE_SELECT_VALUE, {
|
||||
@ -48,36 +73,19 @@ def select_value(hass, entity_id, value):
|
||||
|
||||
def setup(hass, config):
|
||||
"""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)
|
||||
|
||||
entities = []
|
||||
|
||||
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)
|
||||
minimum = cfg.get(CONF_MIN)
|
||||
maximum = cfg.get(CONF_MAX)
|
||||
state = cfg.get(CONF_INITIAL, minimum)
|
||||
step = cfg.get(CONF_STEP, 1)
|
||||
step = cfg.get(CONF_STEP)
|
||||
icon = cfg.get(CONF_ICON)
|
||||
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,
|
||||
step, icon, unit))
|
||||
|
||||
|
@ -6,26 +6,33 @@ https://home-assistant.io/components/insteon_hub/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.helpers import validate_config, discovery
|
||||
import voluptuous as vol
|
||||
|
||||
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']
|
||||
INSTEON = None
|
||||
|
||||
_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):
|
||||
"""Setup Insteon Hub component.
|
||||
|
||||
This will automatically import associated lights.
|
||||
"""
|
||||
if not validate_config(
|
||||
config,
|
||||
{DOMAIN: [CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY]},
|
||||
_LOGGER):
|
||||
return False
|
||||
|
||||
import insteon
|
||||
|
||||
username = config[DOMAIN][CONF_USERNAME]
|
||||
@ -36,8 +43,8 @@ def setup(hass, config):
|
||||
INSTEON = insteon.Insteon(username, password, api_key)
|
||||
|
||||
if INSTEON is None:
|
||||
_LOGGER.error("Could not connect to Insteon service.")
|
||||
return
|
||||
_LOGGER.error("Could not connect to Insteon service")
|
||||
return False
|
||||
|
||||
discovery.load_platform(hass, 'light', DOMAIN, {}, config)
|
||||
|
||||
|
@ -6,43 +6,150 @@ https://home-assistant.io/components/isy994/
|
||||
"""
|
||||
import logging
|
||||
from urllib.parse import urlparse
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import HomeAssistant # noqa
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_PASSWORD, CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.helpers import validate_config, discovery
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.helpers import discovery, config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.typing import ConfigType, Dict # noqa
|
||||
|
||||
|
||||
DOMAIN = "isy994"
|
||||
REQUIREMENTS = ['PyISY==1.0.6']
|
||||
REQUIREMENTS = ['PyISY==1.0.7']
|
||||
|
||||
ISY = None
|
||||
SENSOR_STRING = 'Sensor'
|
||||
HIDDEN_STRING = '{HIDE ME}'
|
||||
DEFAULT_SENSOR_STRING = 'sensor'
|
||||
DEFAULT_HIDDEN_STRING = '{HIDE ME}'
|
||||
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__)
|
||||
|
||||
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):
|
||||
"""Setup ISY994 component.
|
||||
SENSOR_NODES = []
|
||||
NODES = []
|
||||
GROUPS = []
|
||||
PROGRAMS = {}
|
||||
|
||||
This will automatically import associated lights, switches, and sensors.
|
||||
"""
|
||||
import PyISY
|
||||
PYISY = None
|
||||
|
||||
# pylint: disable=global-statement
|
||||
# check for required values in configuration file
|
||||
if not validate_config(config,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
return False
|
||||
HIDDEN_STRING = DEFAULT_HIDDEN_STRING
|
||||
|
||||
# Pull and parse standard configuration.
|
||||
user = config[DOMAIN][CONF_USERNAME]
|
||||
password = config[DOMAIN][CONF_PASSWORD]
|
||||
host = urlparse(config[DOMAIN][CONF_HOST])
|
||||
SUPPORTED_DOMAINS = ['binary_sensor', 'cover', 'fan', 'light', 'lock',
|
||||
'sensor', 'switch']
|
||||
|
||||
|
||||
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()
|
||||
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':
|
||||
addr = addr.replace('http://', '')
|
||||
https = False
|
||||
@ -50,169 +157,125 @@ def setup(hass, config):
|
||||
addr = addr.replace('https://', '')
|
||||
https = True
|
||||
else:
|
||||
_LOGGER.error('isy994 host value in configuration file is invalid.')
|
||||
_LOGGER.error('isy994 host value in configuration is invalid.')
|
||||
return False
|
||||
port = host.port
|
||||
|
||||
addr = addr.replace(':{}'.format(port), '')
|
||||
|
||||
# Pull and parse optional configuration.
|
||||
global SENSOR_STRING
|
||||
global HIDDEN_STRING
|
||||
SENSOR_STRING = str(config[DOMAIN].get('sensor_string', SENSOR_STRING))
|
||||
HIDDEN_STRING = str(config[DOMAIN].get('hidden_string', HIDDEN_STRING))
|
||||
tls_version = config[DOMAIN].get(CONF_TLS_VER, None)
|
||||
import PyISY
|
||||
|
||||
global PYISY
|
||||
PYISY = PyISY
|
||||
|
||||
# Connect to ISY controller.
|
||||
global ISY
|
||||
ISY = PyISY.ISY(addr, port, user, password, use_https=https,
|
||||
tls_ver=tls_version, log=_LOGGER)
|
||||
ISY = PyISY.ISY(addr, port, username=user, password=password,
|
||||
use_https=https, tls_ver=tls_version, log=_LOGGER)
|
||||
if not ISY.connected:
|
||||
return False
|
||||
|
||||
_categorize_nodes(hidden_identifier, sensor_identifier)
|
||||
|
||||
_categorize_programs()
|
||||
|
||||
# Listen for HA stop to disconnect.
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop)
|
||||
|
||||
# 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)
|
||||
|
||||
ISY.auto_update = True
|
||||
return True
|
||||
|
||||
|
||||
def stop(event):
|
||||
"""Cleanup the ISY subscription."""
|
||||
# pylint: disable=unused-argument
|
||||
def stop(event: object) -> None:
|
||||
"""Stop ISY auto updates."""
|
||||
ISY.auto_update = False
|
||||
|
||||
|
||||
class ISYDeviceABC(ToggleEntity):
|
||||
"""An abstract Class for an ISY device."""
|
||||
class ISYDevice(Entity):
|
||||
"""Representation of an ISY994 device."""
|
||||
|
||||
_attrs = {}
|
||||
_onattrs = []
|
||||
_states = []
|
||||
_dtype = None
|
||||
_domain = None
|
||||
_name = None
|
||||
_domain = None # type: str
|
||||
_name = None # type: str
|
||||
|
||||
def __init__(self, node):
|
||||
"""Initialize the device."""
|
||||
# setup properties
|
||||
self.node = node
|
||||
def __init__(self, node) -> None:
|
||||
"""Initialize the insteon device."""
|
||||
self._node = node
|
||||
|
||||
# track changes
|
||||
self._change_handler = self.node.status. \
|
||||
subscribe('changed', self.on_update)
|
||||
self._change_handler = self._node.status.subscribe('changed',
|
||||
self.on_update)
|
||||
|
||||
def __del__(self):
|
||||
"""Cleanup subscriptions because it is the right thing to do."""
|
||||
def __del__(self) -> None:
|
||||
"""Cleanup the subscriptions."""
|
||||
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
|
||||
def domain(self):
|
||||
"""Return the domain of the entity."""
|
||||
def domain(self) -> str:
|
||||
"""Get the domain of the device."""
|
||||
return self._domain
|
||||
|
||||
@property
|
||||
def dtype(self):
|
||||
"""Return the data type of the entity (binary or analog)."""
|
||||
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."""
|
||||
def unique_id(self) -> str:
|
||||
"""Get the unique identifier of the device."""
|
||||
# pylint: disable=protected-access
|
||||
return self.node.status._val
|
||||
return self._node._id
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
"""Return the state attributes for the node."""
|
||||
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."""
|
||||
def raw_name(self) -> str:
|
||||
"""Get the raw name of the device."""
|
||||
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
|
||||
def name(self):
|
||||
"""Return the cleaned name of the node."""
|
||||
def name(self) -> str:
|
||||
"""Get the name of the device."""
|
||||
return self.raw_name.replace(HIDDEN_STRING, '').strip() \
|
||||
.replace('_', ' ')
|
||||
|
||||
@property
|
||||
def hidden(self):
|
||||
"""Suggestion if the entity should be hidden from UIs."""
|
||||
def should_poll(self) -> bool:
|
||||
"""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
|
||||
|
||||
def update(self):
|
||||
"""Update state of the sensor."""
|
||||
# ISY objects are automatically updated by the ISY's event stream
|
||||
@property
|
||||
def unit_of_measurement(self) -> str:
|
||||
"""Get the device unit of measure."""
|
||||
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
|
||||
|
||||
def on_update(self, event):
|
||||
"""Handle the update received event."""
|
||||
self.update_ha_state()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return a boolean response if the node is on."""
|
||||
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
|
||||
|
@ -50,6 +50,7 @@ def media_prev_track(hass):
|
||||
|
||||
def setup(hass, config):
|
||||
"""Listen for keyboard events."""
|
||||
# pylint: disable=import-error
|
||||
import pykeyboard
|
||||
|
||||
keyboard = pykeyboard.PyKeyboard()
|
||||
|
135
homeassistant/components/keyboard_remote.py
Normal file
135
homeassistant/components/keyboard_remote.py
Normal 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}
|
||||
)
|
@ -6,22 +6,31 @@ https://home-assistant.io/components/knx/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.helpers.entity import Entity
|
||||
import voluptuous as vol
|
||||
|
||||
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']
|
||||
|
||||
EVENT_KNX_FRAME_RECEIVED = "knx_frame_received"
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_HOST = "host"
|
||||
CONF_PORT = "port"
|
||||
DEFAULT_HOST = '0.0.0.0'
|
||||
DEFAULT_PORT = '3671'
|
||||
DOMAIN = 'knx'
|
||||
|
||||
DEFAULT_PORT = "3671"
|
||||
EVENT_KNX_FRAME_RECEIVED = 'knx_frame_received'
|
||||
|
||||
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):
|
||||
@ -31,17 +40,11 @@ def setup(hass, config):
|
||||
from knxip.ip import KNXIPTunnel
|
||||
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")
|
||||
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)
|
||||
try:
|
||||
@ -78,21 +81,21 @@ class KNXConfig(object):
|
||||
from knxip.core import parse_group_address
|
||||
|
||||
self.config = config
|
||||
self.should_poll = config.get("poll", True)
|
||||
if config.get("address"):
|
||||
self._address = parse_group_address(config.get("address"))
|
||||
self.should_poll = config.get('poll', True)
|
||||
if config.get('address'):
|
||||
self._address = parse_group_address(config.get('address'))
|
||||
else:
|
||||
self._address = None
|
||||
if self.config.get("state_address"):
|
||||
if self.config.get('state_address'):
|
||||
self._state_address = parse_group_address(
|
||||
self.config.get("state_address"))
|
||||
self.config.get('state_address'))
|
||||
else:
|
||||
self._state_address = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name given to the entity."""
|
||||
return self.config["name"]
|
||||
return self.config['name']
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
@ -175,7 +178,7 @@ class KNXGroupAddress(Entity):
|
||||
@property
|
||||
def cache(self):
|
||||
"""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):
|
||||
"""Write to the group address."""
|
||||
@ -187,22 +190,21 @@ class KNXGroupAddress(Entity):
|
||||
|
||||
try:
|
||||
if self.state_address:
|
||||
res = KNXTUNNEL.group_read(self.state_address,
|
||||
use_cache=self.cache)
|
||||
res = KNXTUNNEL.group_read(
|
||||
self.state_address, use_cache=self.cache)
|
||||
else:
|
||||
res = KNXTUNNEL.group_read(self.address,
|
||||
use_cache=self.cache)
|
||||
res = KNXTUNNEL.group_read(self.address, use_cache=self.cache)
|
||||
|
||||
if res:
|
||||
self._state = res[0]
|
||||
self._data = res
|
||||
else:
|
||||
_LOGGER.debug("Unable to read from KNX address: %s (None)",
|
||||
self.address)
|
||||
_LOGGER.debug(
|
||||
"Unable to read from KNX address: %s (None)", self.address)
|
||||
|
||||
except KNXException:
|
||||
_LOGGER.exception("Unable to read from KNX address: %s",
|
||||
self.address)
|
||||
_LOGGER.exception(
|
||||
"Unable to read from KNX address: %s", self.address)
|
||||
return False
|
||||
|
||||
|
||||
@ -234,19 +236,19 @@ class KNXMultiAddressDevice(Entity):
|
||||
# parse required addresses
|
||||
for name in required:
|
||||
_LOGGER.info(name)
|
||||
paramname = name + "_address"
|
||||
paramname = '{}{}'.format(name, '_address')
|
||||
addr = self._config.config.get(paramname)
|
||||
if addr is None:
|
||||
_LOGGER.exception("Required KNX group address %s missing",
|
||||
paramname)
|
||||
raise KNXException("Group address for %s missing "
|
||||
"in configuration", paramname)
|
||||
_LOGGER.exception(
|
||||
"Required KNX group address %s missing", paramname)
|
||||
raise KNXException(
|
||||
"Group address for %s missing in configuration", paramname)
|
||||
addr = parse_group_address(addr)
|
||||
self.names[addr] = name
|
||||
|
||||
# parse optional addresses
|
||||
for name in optional:
|
||||
paramname = name + "_address"
|
||||
paramname = '{}{}'.format(name, '_address')
|
||||
addr = self._config.config.get(paramname)
|
||||
if addr:
|
||||
try:
|
||||
@ -273,7 +275,7 @@ class KNXMultiAddressDevice(Entity):
|
||||
@property
|
||||
def cache(self):
|
||||
"""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):
|
||||
"""Check if the attribute with the given name is defined.
|
||||
@ -301,8 +303,7 @@ class KNXMultiAddressDevice(Entity):
|
||||
try:
|
||||
res = KNXTUNNEL.group_read(addr, use_cache=self.cache)
|
||||
except KNXException:
|
||||
_LOGGER.exception("Unable to read from KNX address: %s",
|
||||
addr)
|
||||
_LOGGER.exception("Unable to read from KNX address: %s", addr)
|
||||
return False
|
||||
|
||||
return res
|
||||
@ -323,8 +324,7 @@ class KNXMultiAddressDevice(Entity):
|
||||
try:
|
||||
KNXTUNNEL.group_write(addr, value)
|
||||
except KNXException:
|
||||
_LOGGER.exception("Unable to write to KNX address: %s",
|
||||
addr)
|
||||
_LOGGER.exception("Unable to write to KNX address: %s", addr)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
@ -39,6 +39,7 @@ SUPPORT_FLASH = 8
|
||||
SUPPORT_RGB_COLOR = 16
|
||||
SUPPORT_TRANSITION = 32
|
||||
SUPPORT_XY_COLOR = 64
|
||||
SUPPORT_WHITE_VALUE = 128
|
||||
|
||||
# Integer that represents transition time in seconds to make change.
|
||||
ATTR_TRANSITION = "transition"
|
||||
@ -48,6 +49,7 @@ ATTR_RGB_COLOR = "rgb_color"
|
||||
ATTR_XY_COLOR = "xy_color"
|
||||
ATTR_COLOR_TEMP = "color_temp"
|
||||
ATTR_COLOR_NAME = "color_name"
|
||||
ATTR_WHITE_VALUE = "white_value"
|
||||
|
||||
# int with value 0 .. 255 representing brightness of the light.
|
||||
ATTR_BRIGHTNESS = "brightness"
|
||||
@ -73,6 +75,7 @@ PROP_TO_ATTR = {
|
||||
'color_temp': ATTR_COLOR_TEMP,
|
||||
'rgb_color': ATTR_RGB_COLOR,
|
||||
'xy_color': ATTR_XY_COLOR,
|
||||
'white_value': ATTR_WHITE_VALUE,
|
||||
'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)),
|
||||
vol.Coerce(tuple)),
|
||||
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_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
|
||||
def turn_on(hass, entity_id=None, transition=None, brightness=None,
|
||||
rgb_color=None, xy_color=None, color_temp=None, profile=None,
|
||||
flash=None, effect=None, color_name=None):
|
||||
rgb_color=None, xy_color=None, color_temp=None, white_value=None,
|
||||
profile=None, flash=None, effect=None, color_name=None):
|
||||
"""Turn all or specified light on."""
|
||||
data = {
|
||||
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_XY_COLOR, xy_color),
|
||||
(ATTR_COLOR_TEMP, color_temp),
|
||||
(ATTR_WHITE_VALUE, white_value),
|
||||
(ATTR_FLASH, flash),
|
||||
(ATTR_EFFECT, effect),
|
||||
(ATTR_COLOR_NAME, color_name),
|
||||
@ -283,6 +288,11 @@ class Light(ToggleEntity):
|
||||
"""Return the CT color value in mireds."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def white_value(self):
|
||||
"""Return the white value of this light between 0..255."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
"""Return optional state attributes."""
|
||||
|
@ -8,14 +8,19 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.light import (ATTR_RGB_COLOR, SUPPORT_RGB_COLOR,
|
||||
Light, PLATFORM_SCHEMA)
|
||||
from homeassistant.components.light import (
|
||||
ATTR_RGB_COLOR, SUPPORT_RGB_COLOR, Light, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['blinkstick==1.1.8']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_SERIAL = 'serial'
|
||||
|
||||
DEFAULT_NAME = 'Blinkstick'
|
||||
REQUIREMENTS = ["blinkstick==1.1.8"]
|
||||
|
||||
SUPPORT_BLINKSTICK = SUPPORT_RGB_COLOR
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
@ -23,8 +28,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
})
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
@ -7,8 +7,9 @@ https://home-assistant.io/components/demo/
|
||||
import random
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, Light)
|
||||
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_WHITE_VALUE,
|
||||
ATTR_XY_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR,
|
||||
SUPPORT_WHITE_VALUE, Light)
|
||||
|
||||
LIGHT_COLORS = [
|
||||
[237, 224, 33],
|
||||
@ -17,7 +18,8 @@ LIGHT_COLORS = [
|
||||
|
||||
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):
|
||||
@ -33,13 +35,17 @@ class DemoLight(Light):
|
||||
"""Represenation of a demo light."""
|
||||
|
||||
# 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."""
|
||||
self._name = name
|
||||
self._state = state
|
||||
self._rgb = rgb or random.choice(LIGHT_COLORS)
|
||||
self._rgb = rgb
|
||||
self._ct = ct or random.choice(LIGHT_TEMPS)
|
||||
self._brightness = brightness
|
||||
self._xy_color = xy_color
|
||||
self._white = white
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
@ -56,6 +62,11 @@ class DemoLight(Light):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return self._brightness
|
||||
|
||||
@property
|
||||
def xy_color(self):
|
||||
"""Return the XY color value [float, float]."""
|
||||
return self._xy_color
|
||||
|
||||
@property
|
||||
def rgb_color(self):
|
||||
"""Return the RBG color value."""
|
||||
@ -66,6 +77,11 @@ class DemoLight(Light):
|
||||
"""Return the CT color temperature."""
|
||||
return self._ct
|
||||
|
||||
@property
|
||||
def white_value(self):
|
||||
"""Return the white value of this light between 0..255."""
|
||||
return self._white
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if light is on."""
|
||||
@ -89,6 +105,12 @@ class DemoLight(Light):
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
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()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user