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