commit
e9fa1f1f83
20
.coveragerc
20
.coveragerc
|
@ -6,10 +6,17 @@ omit =
|
|||
|
||||
# omit pieces of code that rely on external devices being present
|
||||
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
||||
homeassistant/components/alarm_control_panel/nx584.py
|
||||
|
||||
homeassistant/components/arduino.py
|
||||
homeassistant/components/*/arduino.py
|
||||
|
||||
homeassistant/components/apcupsd.py
|
||||
homeassistant/components/*/apcupsd.py
|
||||
|
||||
homeassistant/components/bloomsky.py
|
||||
homeassistant/components/*/bloomsky.py
|
||||
|
||||
homeassistant/components/insteon_hub.py
|
||||
homeassistant/components/*/insteon_hub.py
|
||||
|
||||
|
@ -53,6 +60,9 @@ omit =
|
|||
homeassistant/components/rpi_gpio.py
|
||||
homeassistant/components/*/rpi_gpio.py
|
||||
|
||||
homeassistant/components/scsgate.py
|
||||
homeassistant/components/*/scsgate.py
|
||||
|
||||
homeassistant/components/binary_sensor/arest.py
|
||||
homeassistant/components/binary_sensor/rest.py
|
||||
homeassistant/components/browser.py
|
||||
|
@ -73,9 +83,8 @@ omit =
|
|||
homeassistant/components/device_tracker/ubus.py
|
||||
homeassistant/components/discovery.py
|
||||
homeassistant/components/downloader.py
|
||||
homeassistant/components/garage_door/wink.py
|
||||
homeassistant/components/ifttt.py
|
||||
homeassistant/components/statsd.py
|
||||
homeassistant/components/influxdb.py
|
||||
homeassistant/components/keyboard.py
|
||||
homeassistant/components/light/blinksticklight.py
|
||||
homeassistant/components/light/hue.py
|
||||
|
@ -89,21 +98,24 @@ omit =
|
|||
homeassistant/components/media_player/kodi.py
|
||||
homeassistant/components/media_player/mpd.py
|
||||
homeassistant/components/media_player/plex.py
|
||||
homeassistant/components/media_player/samsungtv.py
|
||||
homeassistant/components/media_player/snapcast.py
|
||||
homeassistant/components/media_player/sonos.py
|
||||
homeassistant/components/media_player/squeezebox.py
|
||||
homeassistant/components/notify/free_mobile.py
|
||||
homeassistant/components/notify/googlevoice.py
|
||||
homeassistant/components/notify/instapush.py
|
||||
homeassistant/components/notify/nma.py
|
||||
homeassistant/components/notify/pushbullet.py
|
||||
homeassistant/components/notify/pushetta.py
|
||||
homeassistant/components/notify/pushover.py
|
||||
homeassistant/components/notify/rest.py
|
||||
homeassistant/components/notify/slack.py
|
||||
homeassistant/components/notify/smtp.py
|
||||
homeassistant/components/notify/syslog.py
|
||||
homeassistant/components/notify/telegram.py
|
||||
homeassistant/components/notify/twitter.py
|
||||
homeassistant/components/notify/xmpp.py
|
||||
homeassistant/components/notify/googlevoice.py
|
||||
homeassistant/components/sensor/arest.py
|
||||
homeassistant/components/sensor/bitcoin.py
|
||||
homeassistant/components/sensor/cpuspeed.py
|
||||
|
@ -118,6 +130,7 @@ omit =
|
|||
homeassistant/components/sensor/openweathermap.py
|
||||
homeassistant/components/sensor/rest.py
|
||||
homeassistant/components/sensor/sabnzbd.py
|
||||
homeassistant/components/sensor/speedtest.py
|
||||
homeassistant/components/sensor/swiss_public_transport.py
|
||||
homeassistant/components/sensor/systemmonitor.py
|
||||
homeassistant/components/sensor/temper.py
|
||||
|
@ -140,7 +153,6 @@ omit =
|
|||
homeassistant/components/thermostat/proliphix.py
|
||||
homeassistant/components/thermostat/radiotherm.py
|
||||
|
||||
|
||||
[report]
|
||||
# Regexes for lines to exclude from consideration
|
||||
exclude_lines =
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
""" Starts home assistant. """
|
||||
from __future__ import print_function
|
||||
|
||||
from multiprocessing import Process
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
import os
|
||||
import argparse
|
||||
import time
|
||||
|
||||
from homeassistant import bootstrap
|
||||
import homeassistant.config as config_util
|
||||
from homeassistant.const import __version__, EVENT_HOMEASSISTANT_START
|
||||
from homeassistant.const import (__version__, EVENT_HOMEASSISTANT_START,
|
||||
RESTART_EXIT_CODE)
|
||||
|
||||
|
||||
def validate_python():
|
||||
|
@ -73,6 +78,11 @@ def get_arguments():
|
|||
'--demo-mode',
|
||||
action='store_true',
|
||||
help='Start Home Assistant in demo mode')
|
||||
parser.add_argument(
|
||||
'--debug',
|
||||
action='store_true',
|
||||
help='Start Home Assistant in debug mode. Runs in single process to '
|
||||
'enable use of interactive debuggers.')
|
||||
parser.add_argument(
|
||||
'--open-ui',
|
||||
action='store_true',
|
||||
|
@ -204,35 +214,11 @@ def uninstall_osx():
|
|||
print("Home Assistant has been uninstalled.")
|
||||
|
||||
|
||||
def main():
|
||||
""" Starts Home Assistant. """
|
||||
validate_python()
|
||||
|
||||
args = get_arguments()
|
||||
|
||||
config_dir = os.path.join(os.getcwd(), args.config)
|
||||
ensure_config_path(config_dir)
|
||||
|
||||
# os x launchd functions
|
||||
if args.install_osx:
|
||||
install_osx()
|
||||
return
|
||||
if args.uninstall_osx:
|
||||
uninstall_osx()
|
||||
return
|
||||
if args.restart_osx:
|
||||
uninstall_osx()
|
||||
install_osx()
|
||||
return
|
||||
|
||||
# daemon functions
|
||||
if args.pid_file:
|
||||
check_pid(args.pid_file)
|
||||
if args.daemon:
|
||||
daemonize()
|
||||
if args.pid_file:
|
||||
write_pid(args.pid_file)
|
||||
|
||||
def setup_and_run_hass(config_dir, args, top_process=False):
|
||||
"""
|
||||
Setup HASS and run. Block until stopped. Will assume it is running in a
|
||||
subprocess unless top_process is set to true.
|
||||
"""
|
||||
if args.demo_mode:
|
||||
config = {
|
||||
'frontend': {},
|
||||
|
@ -259,7 +245,91 @@ def main():
|
|||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser)
|
||||
|
||||
hass.start()
|
||||
hass.block_till_stopped()
|
||||
exit_code = int(hass.block_till_stopped())
|
||||
|
||||
if not top_process:
|
||||
sys.exit(exit_code)
|
||||
return exit_code
|
||||
|
||||
|
||||
def run_hass_process(hass_proc):
|
||||
""" Runs a child hass process. Returns True if it should be restarted. """
|
||||
requested_stop = threading.Event()
|
||||
hass_proc.daemon = True
|
||||
|
||||
def request_stop(*args):
|
||||
""" request hass stop, *args is for signal handler callback """
|
||||
requested_stop.set()
|
||||
hass_proc.terminate()
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGTERM, request_stop)
|
||||
except ValueError:
|
||||
print('Could not bind to SIGTERM. Are you running in a thread?')
|
||||
|
||||
hass_proc.start()
|
||||
try:
|
||||
hass_proc.join()
|
||||
except KeyboardInterrupt:
|
||||
request_stop()
|
||||
try:
|
||||
hass_proc.join()
|
||||
except KeyboardInterrupt:
|
||||
return False
|
||||
|
||||
return (not requested_stop.isSet() and
|
||||
hass_proc.exitcode == RESTART_EXIT_CODE,
|
||||
hass_proc.exitcode)
|
||||
|
||||
|
||||
def main():
|
||||
""" Starts Home Assistant. """
|
||||
validate_python()
|
||||
|
||||
args = get_arguments()
|
||||
|
||||
config_dir = os.path.join(os.getcwd(), args.config)
|
||||
ensure_config_path(config_dir)
|
||||
|
||||
# os x launchd functions
|
||||
if args.install_osx:
|
||||
install_osx()
|
||||
return 0
|
||||
if args.uninstall_osx:
|
||||
uninstall_osx()
|
||||
return 0
|
||||
if args.restart_osx:
|
||||
uninstall_osx()
|
||||
# A small delay is needed on some systems to let the unload finish.
|
||||
time.sleep(0.5)
|
||||
install_osx()
|
||||
return 0
|
||||
|
||||
# daemon functions
|
||||
if args.pid_file:
|
||||
check_pid(args.pid_file)
|
||||
if args.daemon:
|
||||
daemonize()
|
||||
if args.pid_file:
|
||||
write_pid(args.pid_file)
|
||||
|
||||
# Run hass in debug mode if requested
|
||||
if args.debug:
|
||||
sys.stderr.write('Running in debug mode. '
|
||||
'Home Assistant will not be able to restart.\n')
|
||||
exit_code = setup_and_run_hass(config_dir, args, top_process=True)
|
||||
if exit_code == RESTART_EXIT_CODE:
|
||||
sys.stderr.write('Home Assistant requested a '
|
||||
'restart in debug mode.\n')
|
||||
return exit_code
|
||||
|
||||
# Run hass as child process. Restart if necessary.
|
||||
keep_running = True
|
||||
while keep_running:
|
||||
hass_proc = Process(target=setup_and_run_hass, args=(config_dir, args))
|
||||
keep_running, exit_code = run_hass_process(hass_proc)
|
||||
return exit_code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(main())
|
||||
|
|
|
@ -61,6 +61,8 @@ def setup(hass, config):
|
|||
|
||||
for alarm in target_alarms:
|
||||
getattr(alarm, method)(code)
|
||||
if alarm.should_poll:
|
||||
alarm.update_ha_state(True)
|
||||
|
||||
descriptions = load_yaml_config_file(
|
||||
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
||||
|
|
|
@ -57,10 +57,12 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
|||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" No polling needed. """
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
|
@ -88,7 +90,6 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
|||
# Open another session to alarm.com to fire off the command
|
||||
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
|
||||
_alarm.disarm()
|
||||
self.update_ha_state()
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
""" Send arm home command. """
|
||||
|
@ -98,7 +99,6 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
|||
# Open another session to alarm.com to fire off the command
|
||||
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
|
||||
_alarm.arm_stay()
|
||||
self.update_ha_state()
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
""" Send arm away command. """
|
||||
|
@ -108,7 +108,6 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
|||
# Open another session to alarm.com to fire off the command
|
||||
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
|
||||
_alarm.arm_away()
|
||||
self.update_ha_state()
|
||||
|
||||
def _validate_code(self, code, state):
|
||||
""" Validate given code. """
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
"""
|
||||
homeassistant.components.alarm_control_panel.nx584
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for NX584 alarm control panels.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.nx584/
|
||||
"""
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from homeassistant.const import (STATE_UNKNOWN, STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY)
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
|
||||
REQUIREMENTS = ['pynx584==0.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Setup nx584. """
|
||||
host = config.get('host', 'localhost:5007')
|
||||
|
||||
try:
|
||||
add_devices([NX584Alarm(hass, host, config.get('name', 'NX584'))])
|
||||
except requests.exceptions.ConnectionError as ex:
|
||||
_LOGGER.error('Unable to connect to NX584: %s', str(ex))
|
||||
return False
|
||||
|
||||
|
||||
class NX584Alarm(alarm.AlarmControlPanel):
|
||||
""" NX584-based alarm panel. """
|
||||
def __init__(self, hass, host, name):
|
||||
from nx584 import client
|
||||
self._hass = hass
|
||||
self._host = host
|
||||
self._name = name
|
||||
self._alarm = client.Client('http://%s' % host)
|
||||
# Do an initial list operation so that we will try to actually
|
||||
# talk to the API and trigger a requests exception for setup_platform()
|
||||
# to catch
|
||||
self._alarm.list_zones()
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" Polling needed. """
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
""" Characters if code is defined. """
|
||||
return '[0-9]{4}([0-9]{2})?'
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
try:
|
||||
part = self._alarm.list_partitions()[0]
|
||||
zones = self._alarm.list_zones()
|
||||
except requests.exceptions.ConnectionError as ex:
|
||||
_LOGGER.error('Unable to connect to %(host)s: %(reason)s',
|
||||
dict(host=self._host, reason=ex))
|
||||
return STATE_UNKNOWN
|
||||
except IndexError:
|
||||
_LOGGER.error('nx584 reports no partitions')
|
||||
return STATE_UNKNOWN
|
||||
|
||||
bypassed = False
|
||||
for zone in zones:
|
||||
if zone['bypassed']:
|
||||
_LOGGER.debug('Zone %(zone)s is bypassed, '
|
||||
'assuming HOME',
|
||||
dict(zone=zone['number']))
|
||||
bypassed = True
|
||||
break
|
||||
|
||||
if not part['armed']:
|
||||
return STATE_ALARM_DISARMED
|
||||
elif bypassed:
|
||||
return STATE_ALARM_ARMED_HOME
|
||||
else:
|
||||
return STATE_ALARM_ARMED_AWAY
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
""" Send disarm command. """
|
||||
self._alarm.disarm(code)
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
""" Send arm home command. """
|
||||
self._alarm.arm('home')
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
""" Send arm away command. """
|
||||
self._alarm.arm('auto')
|
||||
|
||||
def alarm_trigger(self, code=None):
|
||||
""" Alarm trigger command. """
|
||||
raise NotImplementedError()
|
|
@ -0,0 +1,84 @@
|
|||
"""
|
||||
homeassistant.components.apcupsd
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets up and provides access to the status output of APCUPSd via its Network
|
||||
Information Server (NIS).
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/apcupsd/
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
DOMAIN = "apcupsd"
|
||||
REQUIREMENTS = ("apcaccess==0.0.4",)
|
||||
|
||||
CONF_HOST = "host"
|
||||
CONF_PORT = "port"
|
||||
CONF_TYPE = "type"
|
||||
|
||||
DEFAULT_HOST = "localhost"
|
||||
DEFAULT_PORT = 3551
|
||||
|
||||
KEY_STATUS = "STATUS"
|
||||
|
||||
VALUE_ONLINE = "ONLINE"
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
||||
|
||||
DATA = None
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Use config values to set up a function enabling status retrieval. """
|
||||
global DATA
|
||||
|
||||
host = config[DOMAIN].get(CONF_HOST, DEFAULT_HOST)
|
||||
port = config[DOMAIN].get(CONF_PORT, DEFAULT_PORT)
|
||||
|
||||
DATA = APCUPSdData(host, port)
|
||||
|
||||
# It doesn't really matter why we're not able to get the status, just that
|
||||
# we can't.
|
||||
# pylint: disable=broad-except
|
||||
try:
|
||||
DATA.update(no_throttle=True)
|
||||
except Exception:
|
||||
_LOGGER.exception("Failure while testing APCUPSd status retrieval.")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class APCUPSdData(object):
|
||||
"""
|
||||
Stores the data retrieved from APCUPSd for each entity to use, acts as the
|
||||
single point responsible for fetching updates from the server.
|
||||
"""
|
||||
def __init__(self, host, port):
|
||||
from apcaccess import status
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._status = None
|
||||
self._get = status.get
|
||||
self._parse = status.parse
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
""" Get latest update if throttle allows. Return status. """
|
||||
self.update()
|
||||
return self._status
|
||||
|
||||
def _get_status(self):
|
||||
""" Get the status from APCUPSd and parse it into a dict. """
|
||||
return self._parse(self._get(host=self._host, port=self._port))
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self, **kwargs):
|
||||
"""
|
||||
Fetch the latest status from APCUPSd and store it in self._status.
|
||||
"""
|
||||
self._status = self._get_status()
|
|
@ -0,0 +1,44 @@
|
|||
"""
|
||||
homeassistant.components.binary_sensor.apcupsd
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Provides a binary sensor to track online status of a UPS.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.apcupsd/
|
||||
"""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components import apcupsd
|
||||
|
||||
DEPENDENCIES = [apcupsd.DOMAIN]
|
||||
DEFAULT_NAME = "UPS Online Status"
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
""" Instantiate an OnlineStatus binary sensor entity and add it to HA. """
|
||||
add_entities((OnlineStatus(config, apcupsd.DATA),))
|
||||
|
||||
|
||||
class OnlineStatus(BinarySensorDevice):
|
||||
""" Binary sensor to represent UPS online status. """
|
||||
def __init__(self, config, data):
|
||||
self._config = config
|
||||
self._data = data
|
||||
self._state = None
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" The name of the UPS online status sensor. """
|
||||
return self._config.get("name", DEFAULT_NAME)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
""" True if the UPS is online, else False. """
|
||||
return self._state == apcupsd.VALUE_ONLINE
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Get the status report from APCUPSd (or cache) and set this entity's
|
||||
state.
|
||||
"""
|
||||
self._state = self._data.status[apcupsd.KEY_STATUS]
|
|
@ -1,8 +1,11 @@
|
|||
"""
|
||||
homeassistant.components.binary_sensor.command_sensor
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Allows to configure custom shell commands to turn a value
|
||||
into a logical value for a binary sensor.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.command/
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
|
|
@ -13,10 +13,11 @@ import homeassistant.components.nest as nest
|
|||
from homeassistant.components.sensor.nest import NestSensor
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
|
||||
|
||||
DEPENDENCIES = ['nest']
|
||||
BINARY_TYPES = ['fan',
|
||||
'hvac_ac_state',
|
||||
'hvac_aux_heater_state',
|
||||
'hvac_heater_state',
|
||||
'hvac_heat_x2_state',
|
||||
'hvac_heat_x3_state',
|
||||
'hvac_alt_heat_state',
|
||||
|
|
|
@ -21,7 +21,7 @@ DEFAULT_METHOD = 'GET'
|
|||
|
||||
# pylint: disable=unused-variable
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup REST binary sensors."""
|
||||
""" Setup REST binary sensors. """
|
||||
resource = config.get('resource', None)
|
||||
method = config.get('method', DEFAULT_METHOD)
|
||||
payload = config.get('payload', None)
|
||||
|
@ -41,10 +41,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
# pylint: disable=too-many-arguments
|
||||
class RestBinarySensor(BinarySensorDevice):
|
||||
"""REST binary sensor."""
|
||||
""" A REST binary sensor. """
|
||||
|
||||
def __init__(self, hass, rest, name, value_template):
|
||||
"""Initialize a REST binary sensor."""
|
||||
""" Initialize a REST binary sensor. """
|
||||
self._hass = hass
|
||||
self.rest = rest
|
||||
self._name = name
|
||||
|
@ -54,12 +54,12 @@ class RestBinarySensor(BinarySensorDevice):
|
|||
|
||||
@property
|
||||
def name(self):
|
||||
"""Name of the binary sensor."""
|
||||
""" Name of the binary sensor. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return if the binary sensor is on."""
|
||||
""" Return if the binary sensor is on. """
|
||||
if self.rest.data is None:
|
||||
return False
|
||||
|
||||
|
@ -69,5 +69,5 @@ class RestBinarySensor(BinarySensorDevice):
|
|||
return bool(int(self.rest.data))
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data from REST API and updates the state."""
|
||||
""" Get the latest data from REST API and updates the state. """
|
||||
self.rest.update()
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
"""
|
||||
homeassistant.components.binary_sensor.zigbee
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Contains functionality to use a ZigBee device as a binary sensor.
|
||||
"""
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.zigbee/
|
||||
"""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.zigbee import (
|
||||
ZigBeeDigitalIn, ZigBeeDigitalInConfig)
|
||||
|
@ -13,9 +15,7 @@ DEPENDENCIES = ["zigbee"]
|
|||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""
|
||||
Create and add an entity based on the configuration.
|
||||
"""
|
||||
""" Create and add an entity based on the configuration. """
|
||||
add_entities([
|
||||
ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))
|
||||
])
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
"""
|
||||
homeassistant.components.bloomsky
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for BloomSky weather station.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/bloomsky/
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import requests
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
|
||||
DOMAIN = "bloomsky"
|
||||
BLOOMSKY = None
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# The BloomSky only updates every 5-8 minutes as per the API spec so there's
|
||||
# no point in polling the API more frequently
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument,too-few-public-methods
|
||||
def setup(hass, config):
|
||||
""" Setup BloomSky component. """
|
||||
if not validate_config(
|
||||
config,
|
||||
{DOMAIN: [CONF_API_KEY]},
|
||||
_LOGGER):
|
||||
return False
|
||||
|
||||
api_key = config[DOMAIN][CONF_API_KEY]
|
||||
|
||||
global BLOOMSKY
|
||||
try:
|
||||
BLOOMSKY = BloomSky(api_key)
|
||||
except RuntimeError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class BloomSky(object):
|
||||
""" Handle all communication with the BloomSky API. """
|
||||
|
||||
# API documentation at http://weatherlution.com/bloomsky-api/
|
||||
|
||||
API_URL = "https://api.bloomsky.com/api/skydata"
|
||||
|
||||
def __init__(self, api_key):
|
||||
self._api_key = api_key
|
||||
self.devices = {}
|
||||
_LOGGER.debug("Initial bloomsky device load...")
|
||||
self.refresh_devices()
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def refresh_devices(self):
|
||||
"""
|
||||
Uses the API to retreive a list of devices associated with an
|
||||
account along with all the sensors on the device.
|
||||
"""
|
||||
_LOGGER.debug("Fetching bloomsky update")
|
||||
response = requests.get(self.API_URL,
|
||||
headers={"Authorization": self._api_key},
|
||||
timeout=10)
|
||||
if response.status_code == 401:
|
||||
raise RuntimeError("Invalid API_KEY")
|
||||
elif response.status_code != 200:
|
||||
_LOGGER.error("Invalid HTTP response: %s", response.status_code)
|
||||
return
|
||||
# create dictionary keyed off of the device unique id
|
||||
self.devices.update({
|
||||
device["DeviceID"]: device for device in response.json()
|
||||
})
|
|
@ -33,8 +33,6 @@ SWITCH_ACTION_SNAPSHOT = 'snapshot'
|
|||
|
||||
SERVICE_CAMERA = 'camera_service'
|
||||
|
||||
STATE_RECORDING = 'recording'
|
||||
|
||||
DEFAULT_RECORDING_SECONDS = 30
|
||||
|
||||
# Maps discovered services to their platforms
|
||||
|
@ -46,6 +44,7 @@ DIR_DATETIME_FORMAT = '%Y-%m-%d_%H-%M-%S'
|
|||
REC_DIR_PREFIX = 'recording-'
|
||||
REC_IMG_PREFIX = 'recording_image-'
|
||||
|
||||
STATE_RECORDING = 'recording'
|
||||
STATE_STREAMING = 'streaming'
|
||||
STATE_IDLE = 'idle'
|
||||
|
||||
|
@ -121,33 +120,7 @@ def setup(hass, config):
|
|||
try:
|
||||
camera.is_streaming = True
|
||||
camera.update_ha_state()
|
||||
|
||||
handler.request.sendall(bytes('HTTP/1.1 200 OK\r\n', 'utf-8'))
|
||||
handler.request.sendall(bytes(
|
||||
'Content-type: multipart/x-mixed-replace; \
|
||||
boundary=--jpgboundary\r\n\r\n', 'utf-8'))
|
||||
handler.request.sendall(bytes('--jpgboundary\r\n', 'utf-8'))
|
||||
|
||||
# MJPEG_START_HEADER.format()
|
||||
|
||||
while True:
|
||||
img_bytes = camera.camera_image()
|
||||
if img_bytes is None:
|
||||
continue
|
||||
headers_str = '\r\n'.join((
|
||||
'Content-length: {}'.format(len(img_bytes)),
|
||||
'Content-type: image/jpeg',
|
||||
)) + '\r\n\r\n'
|
||||
|
||||
handler.request.sendall(
|
||||
bytes(headers_str, 'utf-8') +
|
||||
img_bytes +
|
||||
bytes('\r\n', 'utf-8'))
|
||||
|
||||
handler.request.sendall(
|
||||
bytes('--jpgboundary\r\n', 'utf-8'))
|
||||
|
||||
time.sleep(0.5)
|
||||
camera.mjpeg_stream(handler)
|
||||
|
||||
except (requests.RequestException, IOError):
|
||||
camera.is_streaming = False
|
||||
|
@ -190,6 +163,34 @@ class Camera(Entity):
|
|||
""" Return bytes of camera image. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def mjpeg_stream(self, handler):
|
||||
""" Generate an HTTP MJPEG stream from camera images. """
|
||||
handler.request.sendall(bytes('HTTP/1.1 200 OK\r\n', 'utf-8'))
|
||||
handler.request.sendall(bytes(
|
||||
'Content-type: multipart/x-mixed-replace; \
|
||||
boundary=--jpgboundary\r\n\r\n', 'utf-8'))
|
||||
handler.request.sendall(bytes('--jpgboundary\r\n', 'utf-8'))
|
||||
|
||||
# MJPEG_START_HEADER.format()
|
||||
while True:
|
||||
img_bytes = self.camera_image()
|
||||
if img_bytes is None:
|
||||
continue
|
||||
headers_str = '\r\n'.join((
|
||||
'Content-length: {}'.format(len(img_bytes)),
|
||||
'Content-type: image/jpeg',
|
||||
)) + '\r\n\r\n'
|
||||
|
||||
handler.request.sendall(
|
||||
bytes(headers_str, 'utf-8') +
|
||||
img_bytes +
|
||||
bytes('\r\n', 'utf-8'))
|
||||
|
||||
handler.request.sendall(
|
||||
bytes('--jpgboundary\r\n', 'utf-8'))
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the entity. """
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
"""
|
||||
homeassistant.components.camera.bloomsky
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for a camera of a BloomSky weather station.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/camera.bloomsky/
|
||||
"""
|
||||
import logging
|
||||
import requests
|
||||
import homeassistant.components.bloomsky as bloomsky
|
||||
from homeassistant.components.camera import Camera
|
||||
|
||||
DEPENDENCIES = ["bloomsky"]
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
""" set up access to BloomSky cameras """
|
||||
for device in bloomsky.BLOOMSKY.devices.values():
|
||||
add_devices_callback([BloomSkyCamera(bloomsky.BLOOMSKY, device)])
|
||||
|
||||
|
||||
class BloomSkyCamera(Camera):
|
||||
""" Represents the images published from the BloomSky's camera. """
|
||||
|
||||
def __init__(self, bs, device):
|
||||
""" set up for access to the BloomSky camera images """
|
||||
super(BloomSkyCamera, self).__init__()
|
||||
self._name = device["DeviceName"]
|
||||
self._id = device["DeviceID"]
|
||||
self._bloomsky = bs
|
||||
self._url = ""
|
||||
self._last_url = ""
|
||||
# _last_image will store images as they are downloaded so that the
|
||||
# frequent updates in home-assistant don't keep poking the server
|
||||
# to download the same image over and over
|
||||
self._last_image = ""
|
||||
self._logger = logging.getLogger(__name__)
|
||||
|
||||
def camera_image(self):
|
||||
""" Update the camera's image if it has changed. """
|
||||
try:
|
||||
self._url = self._bloomsky.devices[self._id]["Data"]["ImageURL"]
|
||||
self._bloomsky.refresh_devices()
|
||||
# if the url hasn't changed then the image hasn't changed
|
||||
if self._url != self._last_url:
|
||||
response = requests.get(self._url, timeout=10)
|
||||
self._last_url = self._url
|
||||
self._last_image = response.content
|
||||
except requests.exceptions.RequestException as error:
|
||||
self._logger.error("Error getting bloomsky image: %s", error)
|
||||
return None
|
||||
|
||||
return self._last_image
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" The name of this BloomSky device. """
|
||||
return self._name
|
|
@ -14,6 +14,9 @@ from requests.auth import HTTPBasicAuth
|
|||
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.components.camera import DOMAIN, Camera
|
||||
from homeassistant.const import HTTP_OK
|
||||
|
||||
CONTENT_TYPE_HEADER = 'Content-Type'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -41,6 +44,17 @@ class MjpegCamera(Camera):
|
|||
self._password = device_info.get('password')
|
||||
self._mjpeg_url = device_info['mjpeg_url']
|
||||
|
||||
def camera_stream(self):
|
||||
""" Return a mjpeg stream image response directly from the camera. """
|
||||
if self._username and self._password:
|
||||
return requests.get(self._mjpeg_url,
|
||||
auth=HTTPBasicAuth(self._username,
|
||||
self._password),
|
||||
stream=True)
|
||||
else:
|
||||
return requests.get(self._mjpeg_url,
|
||||
stream=True)
|
||||
|
||||
def camera_image(self):
|
||||
""" Return a still image response from the camera. """
|
||||
|
||||
|
@ -55,16 +69,22 @@ class MjpegCamera(Camera):
|
|||
jpg = data[jpg_start:jpg_end + 2]
|
||||
return jpg
|
||||
|
||||
if self._username and self._password:
|
||||
with closing(requests.get(self._mjpeg_url,
|
||||
auth=HTTPBasicAuth(self._username,
|
||||
self._password),
|
||||
stream=True)) as response:
|
||||
return process_response(response)
|
||||
else:
|
||||
with closing(requests.get(self._mjpeg_url,
|
||||
stream=True)) as response:
|
||||
return process_response(response)
|
||||
with closing(self.camera_stream()) as response:
|
||||
return process_response(response)
|
||||
|
||||
def mjpeg_stream(self, handler):
|
||||
""" Generate an HTTP MJPEG stream from the camera. """
|
||||
response = self.camera_stream()
|
||||
content_type = response.headers[CONTENT_TYPE_HEADER]
|
||||
|
||||
handler.send_response(HTTP_OK)
|
||||
handler.send_header(CONTENT_TYPE_HEADER, content_type)
|
||||
handler.end_headers()
|
||||
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
if not chunk:
|
||||
break
|
||||
handler.wfile.write(chunk)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
"""
|
||||
homeassistant.components.camera.uvc
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for Ubiquiti's UVC cameras.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/camera.uvc/
|
||||
"""
|
||||
import logging
|
||||
import socket
|
||||
|
||||
import requests
|
||||
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.components.camera import DOMAIN, Camera
|
||||
|
||||
REQUIREMENTS = ['uvcclient==0.5']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Discover cameras on a Unifi NVR. """
|
||||
if not validate_config({DOMAIN: config}, {DOMAIN: ['nvr', 'key']},
|
||||
_LOGGER):
|
||||
return None
|
||||
|
||||
addr = config.get('nvr')
|
||||
port = int(config.get('port', 7080))
|
||||
key = config.get('key')
|
||||
|
||||
from uvcclient import nvr
|
||||
nvrconn = nvr.UVCRemote(addr, port, key)
|
||||
try:
|
||||
cameras = nvrconn.index()
|
||||
except nvr.NotAuthorized:
|
||||
_LOGGER.error('Authorization failure while connecting to NVR')
|
||||
return False
|
||||
except nvr.NvrError:
|
||||
_LOGGER.error('NVR refuses to talk to me')
|
||||
return False
|
||||
except requests.exceptions.ConnectionError as ex:
|
||||
_LOGGER.error('Unable to connect to NVR: %s', str(ex))
|
||||
return False
|
||||
|
||||
for camera in cameras:
|
||||
add_devices([UnifiVideoCamera(nvrconn,
|
||||
camera['uuid'],
|
||||
camera['name'])])
|
||||
|
||||
|
||||
class UnifiVideoCamera(Camera):
|
||||
""" A Ubiquiti Unifi Video Camera. """
|
||||
|
||||
def __init__(self, nvr, uuid, name):
|
||||
super(UnifiVideoCamera, self).__init__()
|
||||
self._nvr = nvr
|
||||
self._uuid = uuid
|
||||
self._name = name
|
||||
self.is_streaming = False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_recording(self):
|
||||
caminfo = self._nvr.get_camera(self._uuid)
|
||||
return caminfo['recordingSettings']['fullTimeRecordEnabled']
|
||||
|
||||
def camera_image(self):
|
||||
from uvcclient import camera as uvc_camera
|
||||
|
||||
caminfo = self._nvr.get_camera(self._uuid)
|
||||
camera = None
|
||||
for addr in [caminfo['host'], caminfo['internalHost']]:
|
||||
try:
|
||||
camera = uvc_camera.UVCCameraClient(addr,
|
||||
caminfo['username'],
|
||||
'ubnt')
|
||||
_LOGGER.debug('Logged into UVC camera %(name)s via %(addr)s',
|
||||
dict(name=self._name, addr=addr))
|
||||
except socket.error:
|
||||
pass
|
||||
|
||||
if not camera:
|
||||
_LOGGER.error('Unable to login to camera')
|
||||
return None
|
||||
|
||||
camera.login()
|
||||
return camera.get_snapshot()
|
|
@ -141,7 +141,7 @@ class Configurator(object):
|
|||
|
||||
state = self.hass.states.get(entity_id)
|
||||
|
||||
new_data = state.attributes
|
||||
new_data = dict(state.attributes)
|
||||
new_data[ATTR_ERRORS] = error
|
||||
|
||||
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
|
||||
|
|
|
@ -21,6 +21,7 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
|
|||
'binary_sensor',
|
||||
'camera',
|
||||
'device_tracker',
|
||||
'garage_door',
|
||||
'light',
|
||||
'lock',
|
||||
'media_player',
|
||||
|
|
|
@ -11,7 +11,6 @@ import logging
|
|||
from datetime import timedelta
|
||||
import re
|
||||
import threading
|
||||
import telnetlib
|
||||
|
||||
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
||||
from homeassistant.helpers import validate_config
|
||||
|
@ -21,6 +20,7 @@ from homeassistant.components.device_tracker import DOMAIN
|
|||
# Return cached results if last scan was less then this time ago
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
|
||||
REQUIREMENTS = ['pexpect==4.0.1']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_DEVICES_REGEX = re.compile(
|
||||
|
@ -44,6 +44,7 @@ def get_scanner(hass, config):
|
|||
|
||||
class ArubaDeviceScanner(object):
|
||||
""" This class queries a Aruba Acces Point for connected devices. """
|
||||
|
||||
def __init__(self, config):
|
||||
self.host = config[CONF_HOST]
|
||||
self.username = config[CONF_USERNAME]
|
||||
|
@ -93,23 +94,39 @@ class ArubaDeviceScanner(object):
|
|||
|
||||
def get_aruba_data(self):
|
||||
""" Retrieve data from Aruba Access Point and return parsed result. """
|
||||
try:
|
||||
telnet = telnetlib.Telnet(self.host)
|
||||
telnet.read_until(b'User: ')
|
||||
telnet.write((self.username + '\r\n').encode('ascii'))
|
||||
telnet.read_until(b'Password: ')
|
||||
telnet.write((self.password + '\r\n').encode('ascii'))
|
||||
telnet.read_until(b'#')
|
||||
telnet.write(('show clients\r\n').encode('ascii'))
|
||||
devices_result = telnet.read_until(b'#').split(b'\r\n')
|
||||
telnet.write('exit\r\n'.encode('ascii'))
|
||||
except EOFError:
|
||||
_LOGGER.exception("Unexpected response from router")
|
||||
|
||||
import pexpect
|
||||
connect = "ssh {}@{}"
|
||||
ssh = pexpect.spawn(connect.format(self.username, self.host))
|
||||
query = ssh.expect(['password:', pexpect.TIMEOUT, pexpect.EOF,
|
||||
'continue connecting (yes/no)?',
|
||||
'Host key verification failed.',
|
||||
'Connection refused',
|
||||
'Connection timed out'], timeout=120)
|
||||
if query == 1:
|
||||
_LOGGER.error("Timeout")
|
||||
return
|
||||
except ConnectionRefusedError:
|
||||
_LOGGER.exception("Connection refused by router," +
|
||||
" is telnet enabled?")
|
||||
elif query == 2:
|
||||
_LOGGER.error("Unexpected response from router")
|
||||
return
|
||||
elif query == 3:
|
||||
ssh.sendline('yes')
|
||||
ssh.expect('password:')
|
||||
elif query == 4:
|
||||
_LOGGER.error("Host key Changed")
|
||||
return
|
||||
elif query == 5:
|
||||
_LOGGER.error("Connection refused by server")
|
||||
return
|
||||
elif query == 6:
|
||||
_LOGGER.error("Connection timed out")
|
||||
return
|
||||
ssh.sendline(self.password)
|
||||
ssh.expect('#')
|
||||
ssh.sendline('show clients')
|
||||
ssh.expect('#')
|
||||
devices_result = ssh.before.split(b'\r\n')
|
||||
ssh.sendline('exit')
|
||||
|
||||
devices = {}
|
||||
for device in devices_result:
|
||||
|
@ -119,5 +136,5 @@ class ArubaDeviceScanner(object):
|
|||
'ip': match.group('ip'),
|
||||
'mac': match.group('mac').upper(),
|
||||
'name': match.group('name')
|
||||
}
|
||||
}
|
||||
return devices
|
||||
|
|
|
@ -95,7 +95,8 @@ def setup_scanner(hass, config, see):
|
|||
MOBILE_BEACONS_ACTIVE[dev_id].append(location)
|
||||
else:
|
||||
# Normal region
|
||||
kwargs['location_name'] = location
|
||||
if not zone.attributes.get('passive'):
|
||||
kwargs['location_name'] = location
|
||||
|
||||
regions = REGIONS_ENTERED[dev_id]
|
||||
if location not in regions:
|
||||
|
@ -115,7 +116,8 @@ def setup_scanner(hass, config, see):
|
|||
if new_region:
|
||||
# Exit to previous region
|
||||
zone = hass.states.get("zone.{}".format(new_region))
|
||||
kwargs['location_name'] = new_region
|
||||
if not zone.attributes.get('passive'):
|
||||
kwargs['location_name'] = new_region
|
||||
_set_gps_from_zone(kwargs, zone)
|
||||
_LOGGER.info("Exit from to %s", new_region)
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
""" DO NOT MODIFY. Auto-generated by update_mdi script """
|
||||
VERSION = "a2605736c8d959d50c4bcbba1e6a6aa5"
|
||||
VERSION = "a1a203680639ff1abcc7b68cdb29c57a"
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||
VERSION = "1e89871aaae43c91b2508f52bc161b69"
|
||||
VERSION = "833d09737fec24f9219efae87c5bfd2a"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
Subproject commit 472f485a7e17d4ec4fc7cc6c17bcd6c41830d6fa
|
||||
Subproject commit 1380e59e182c7d5468c55c67c8c363ff9248349a
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
|||
!function(e){function t(r){if(n[r])return n[r].exports;var s=n[r]={exports:{},id:r,loaded:!1};return e[r].call(s.exports,s,s.exports,t),s.loaded=!0,s.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([/*!*************************************!*\
|
||||
!*** ./src/service-worker/index.js ***!
|
||||
\*************************************/
|
||||
function(e,t,n){"use strict";var r="0.10",s="/",c=["/","/logbook","/history","/map","/devService","/devState","/devEvent","/devInfo","/states"],i=["/static/favicon-192x192.png"];self.addEventListener("install",function(e){e.waitUntil(caches.open(r).then(function(e){return e.addAll(i.concat(s))}))}),self.addEventListener("activate",function(e){}),self.addEventListener("message",function(e){}),self.addEventListener("fetch",function(e){var t=e.request.url.substr(e.request.url.indexOf("/",8));i.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(e.request)})),c.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(s).then(function(n){var r=fetch(e.request).then(function(e){return t.put(s,e.clone()),e});return n||r})}))})}]);
|
||||
function(e,t,n){"use strict";var r="0.10",s="/",c=["/","/logbook","/history","/map","/devService","/devState","/devEvent","/devInfo","/states"],i=["/static/favicon-192x192.png"];self.addEventListener("install",function(e){e.waitUntil(caches.open(r).then(function(e){return e.addAll(i.concat(s))}))}),self.addEventListener("activate",function(e){}),self.addEventListener("message",function(e){}),self.addEventListener("fetch",function(e){var t=e.request.url.substr(e.request.url.indexOf("/",8));i.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(e.request)})),c.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(s).then(function(n){return n||fetch(e.request).then(function(e){return t.put(s,e.clone()),e})})}))})}]);
|
||||
//# sourceMappingURL=service_worker.js.map
|
|
@ -0,0 +1,108 @@
|
|||
"""
|
||||
homeassistant.components.garage_door
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Component to interface with garage doors that can be controlled remotely.
|
||||
|
||||
For more details about this component, please refer to the documentation
|
||||
at https://home-assistant.io/components/garage_door/
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from homeassistant.const import (
|
||||
STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN, SERVICE_CLOSE, SERVICE_OPEN,
|
||||
ATTR_ENTITY_ID)
|
||||
from homeassistant.components import (group, wink)
|
||||
|
||||
DOMAIN = 'garage_door'
|
||||
SCAN_INTERVAL = 30
|
||||
|
||||
GROUP_NAME_ALL_GARAGE_DOORS = 'all garage doors'
|
||||
ENTITY_ID_ALL_GARAGE_DOORS = group.ENTITY_ID_FORMAT.format('all_garage_doors')
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
# Maps discovered services to their platforms
|
||||
DISCOVERY_PLATFORMS = {
|
||||
wink.DISCOVER_GARAGE_DOORS: 'wink'
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def is_closed(hass, entity_id=None):
|
||||
""" Returns if the garage door is closed based on the statemachine. """
|
||||
entity_id = entity_id or ENTITY_ID_ALL_GARAGE_DOORS
|
||||
return hass.states.is_state(entity_id, STATE_CLOSED)
|
||||
|
||||
|
||||
def close_door(hass, entity_id=None):
|
||||
""" Closes all or specified garage door. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||
hass.services.call(DOMAIN, SERVICE_CLOSE, data)
|
||||
|
||||
|
||||
def open_door(hass, entity_id=None):
|
||||
""" Open all or specified garage door. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||
hass.services.call(DOMAIN, SERVICE_OPEN, data)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Track states and offer events for garage door. """
|
||||
component = EntityComponent(
|
||||
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
|
||||
GROUP_NAME_ALL_GARAGE_DOORS)
|
||||
component.setup(config)
|
||||
|
||||
def handle_garage_door_service(service):
|
||||
""" Handles calls to the garage door services. """
|
||||
target_locks = component.extract_from_service(service)
|
||||
|
||||
for item in target_locks:
|
||||
if service.service == SERVICE_CLOSE:
|
||||
item.close_door()
|
||||
else:
|
||||
item.open_door()
|
||||
|
||||
if item.should_poll:
|
||||
item.update_ha_state(True)
|
||||
|
||||
descriptions = load_yaml_config_file(
|
||||
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
||||
hass.services.register(DOMAIN, SERVICE_OPEN, handle_garage_door_service,
|
||||
descriptions.get(SERVICE_OPEN))
|
||||
hass.services.register(DOMAIN, SERVICE_CLOSE, handle_garage_door_service,
|
||||
descriptions.get(SERVICE_CLOSE))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class GarageDoorDevice(Entity):
|
||||
""" Represents a garage door. """
|
||||
# pylint: disable=no-self-use
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
""" Is the garage door closed or opened. """
|
||||
return None
|
||||
|
||||
def close_door(self):
|
||||
""" Closes the garage door. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def open_door(self):
|
||||
""" Opens the garage door. """
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" State of the garage door. """
|
||||
closed = self.is_closed
|
||||
if closed is None:
|
||||
return STATE_UNKNOWN
|
||||
return STATE_CLOSED if closed else STATE_OPEN
|
|
@ -0,0 +1,48 @@
|
|||
"""
|
||||
homeassistant.components.garage_door.demo
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Demo platform that has two fake garage doors.
|
||||
"""
|
||||
from homeassistant.components.garage_door import GarageDoorDevice
|
||||
from homeassistant.const import STATE_CLOSED, STATE_OPEN
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
""" Find and return demo garage doors. """
|
||||
add_devices_callback([
|
||||
DemoGarageDoor('Left Garage Door', STATE_CLOSED),
|
||||
DemoGarageDoor('Right Garage Door', STATE_OPEN)
|
||||
])
|
||||
|
||||
|
||||
class DemoGarageDoor(GarageDoorDevice):
|
||||
""" Provides a demo garage door. """
|
||||
def __init__(self, name, state):
|
||||
self._name = name
|
||||
self._state = state
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" No polling needed for a demo garage door. """
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device if any. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
""" True if device is closed. """
|
||||
return self._state == STATE_CLOSED
|
||||
|
||||
def close_door(self, **kwargs):
|
||||
""" Close the device. """
|
||||
self._state = STATE_CLOSED
|
||||
self.update_ha_state()
|
||||
|
||||
def open_door(self, **kwargs):
|
||||
""" Open the device. """
|
||||
self._state = STATE_OPEN
|
||||
self.update_ha_state()
|
|
@ -0,0 +1,67 @@
|
|||
"""
|
||||
homeassistant.components.garage_door.wink
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for Wink garage doors.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/garage_door.wink/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.garage_door import GarageDoorDevice
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
|
||||
REQUIREMENTS = ['python-wink==0.5.0']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the Wink platform. """
|
||||
import pywink
|
||||
|
||||
if discovery_info is None:
|
||||
token = config.get(CONF_ACCESS_TOKEN)
|
||||
|
||||
if token is None:
|
||||
logging.getLogger(__name__).error(
|
||||
"Missing wink access_token. "
|
||||
"Get one at https://winkbearertoken.appspot.com/")
|
||||
return
|
||||
|
||||
pywink.set_bearer_token(token)
|
||||
|
||||
add_devices(WinkGarageDoorDevice(door) for door in
|
||||
pywink.get_garage_doors())
|
||||
|
||||
|
||||
class WinkGarageDoorDevice(GarageDoorDevice):
|
||||
""" Represents a Wink garage door. """
|
||||
|
||||
def __init__(self, wink):
|
||||
self.wink = wink
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
""" Returns the id of this wink garage door """
|
||||
return "{}.{}".format(self.__class__, self.wink.device_id())
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the garage door if any. """
|
||||
return self.wink.name()
|
||||
|
||||
def update(self):
|
||||
""" Update the state of the garage door. """
|
||||
self.wink.update_state()
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
""" True if device is closed. """
|
||||
return self.wink.state() == 0
|
||||
|
||||
def close_door(self):
|
||||
""" Close the device. """
|
||||
self.wink.set_state(0)
|
||||
|
||||
def open_door(self):
|
||||
""" Open the device. """
|
||||
self.wink.set_state(1)
|
|
@ -0,0 +1,122 @@
|
|||
"""
|
||||
homeassistant.components.graphite
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Component that records all events and state changes and feeds the data to
|
||||
a graphite installation.
|
||||
|
||||
Example configuration:
|
||||
|
||||
graphite:
|
||||
host: foobar
|
||||
port: 2003
|
||||
prefix: ha
|
||||
|
||||
All config elements are optional, and assumed to be on localhost at the
|
||||
default port if not specified. Prefix is the metric prefix in graphite,
|
||||
and defaults to 'ha'.
|
||||
"""
|
||||
import logging
|
||||
import queue
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
from homeassistant.const import (
|
||||
EVENT_STATE_CHANGED,
|
||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.helpers import state
|
||||
|
||||
DOMAIN = "graphite"
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Setup graphite feeder. """
|
||||
graphite_config = config.get('graphite', {})
|
||||
host = graphite_config.get('host', 'localhost')
|
||||
prefix = graphite_config.get('prefix', 'ha')
|
||||
try:
|
||||
port = int(graphite_config.get('port', 2003))
|
||||
except ValueError:
|
||||
_LOGGER.error('Invalid port specified')
|
||||
return False
|
||||
|
||||
GraphiteFeeder(hass, host, port, prefix)
|
||||
return True
|
||||
|
||||
|
||||
class GraphiteFeeder(threading.Thread):
|
||||
""" Feeds data to graphite. """
|
||||
def __init__(self, hass, host, port, prefix):
|
||||
super(GraphiteFeeder, self).__init__(daemon=True)
|
||||
self._hass = hass
|
||||
self._host = host
|
||||
self._port = port
|
||||
# rstrip any trailing dots in case they think they
|
||||
# need it
|
||||
self._prefix = prefix.rstrip('.')
|
||||
self._queue = queue.Queue()
|
||||
self._quit_object = object()
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START,
|
||||
self.start_listen)
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
|
||||
self.shutdown)
|
||||
hass.bus.listen(EVENT_STATE_CHANGED, self.event_listener)
|
||||
|
||||
def start_listen(self, event):
|
||||
""" Start event-processing thread. """
|
||||
self.start()
|
||||
|
||||
def shutdown(self, event):
|
||||
""" Tell the thread that we are done.
|
||||
|
||||
This does not block because there is nothing to
|
||||
clean up (and no penalty for killing in-process
|
||||
connections to graphite.
|
||||
"""
|
||||
self._queue.put(self._quit_object)
|
||||
|
||||
def event_listener(self, event):
|
||||
""" Queue an event for processing. """
|
||||
self._queue.put(event)
|
||||
|
||||
def _send_to_graphite(self, data):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(10)
|
||||
sock.connect((self._host, self._port))
|
||||
sock.sendall(data.encode('ascii'))
|
||||
sock.send('\n'.encode('ascii'))
|
||||
sock.close()
|
||||
|
||||
def _report_attributes(self, entity_id, new_state):
|
||||
now = time.time()
|
||||
things = dict(new_state.attributes)
|
||||
try:
|
||||
things['state'] = state.state_as_number(new_state)
|
||||
except ValueError:
|
||||
pass
|
||||
lines = ['%s.%s.%s %f %i' % (self._prefix,
|
||||
entity_id, key.replace(' ', '_'),
|
||||
value, now)
|
||||
for key, value in things.items()
|
||||
if isinstance(value, (float, int))]
|
||||
if not lines:
|
||||
return
|
||||
_LOGGER.debug('Sending to graphite: %s', lines)
|
||||
try:
|
||||
self._send_to_graphite('\n'.join(lines))
|
||||
except socket.error:
|
||||
_LOGGER.exception('Failed to send data to graphite')
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
event = self._queue.get()
|
||||
if event == self._quit_object:
|
||||
self._queue.task_done()
|
||||
return
|
||||
elif (event.event_type == EVENT_STATE_CHANGED and
|
||||
'new_state' in event.data):
|
||||
self._report_attributes(event.data['entity_id'],
|
||||
event.data['new_state'])
|
||||
self._queue.task_done()
|
|
@ -71,7 +71,7 @@ def expand_entity_ids(hass, entity_ids):
|
|||
if domain == DOMAIN:
|
||||
found_ids.extend(
|
||||
ent_id for ent_id
|
||||
in get_entity_ids(hass, entity_id)
|
||||
in expand_entity_ids(hass, get_entity_ids(hass, entity_id))
|
||||
if ent_id not in found_ids)
|
||||
|
||||
else:
|
||||
|
|
|
@ -18,6 +18,8 @@ from homeassistant.const import HTTP_BAD_REQUEST
|
|||
DOMAIN = 'history'
|
||||
DEPENDENCIES = ['recorder', 'http']
|
||||
|
||||
SIGNIFICANT_DOMAINS = ('thermostat',)
|
||||
|
||||
URL_HISTORY_PERIOD = re.compile(
|
||||
r'/api/history/period(?:/(?P<date>\d{4}-\d{1,2}-\d{1,2})|)')
|
||||
|
||||
|
@ -35,6 +37,37 @@ def last_5_states(entity_id):
|
|||
return recorder.query_states(query, (entity_id, ))
|
||||
|
||||
|
||||
def get_significant_states(start_time, end_time=None, entity_id=None):
|
||||
"""Return states changes during UTC period start_time - end_time.
|
||||
|
||||
Significant states are all states where there is a state change,
|
||||
as well as all states from certain domains (for instance
|
||||
thermostat so that we get current temperature in our graphs).
|
||||
|
||||
"""
|
||||
where = """
|
||||
(domain in ({}) or last_changed=last_updated)
|
||||
AND last_updated > ?
|
||||
""".format(",".join(["'%s'" % x for x in SIGNIFICANT_DOMAINS]))
|
||||
|
||||
data = [start_time]
|
||||
|
||||
if end_time is not None:
|
||||
where += "AND last_updated < ? "
|
||||
data.append(end_time)
|
||||
|
||||
if entity_id is not None:
|
||||
where += "AND entity_id = ? "
|
||||
data.append(entity_id.lower())
|
||||
|
||||
query = ("SELECT * FROM states WHERE {} "
|
||||
"ORDER BY entity_id, last_updated ASC").format(where)
|
||||
|
||||
states = recorder.query_states(query, data)
|
||||
|
||||
return states_to_json(states, start_time, entity_id)
|
||||
|
||||
|
||||
def state_changes_during_period(start_time, end_time=None, entity_id=None):
|
||||
"""
|
||||
Return states changes during UTC period start_time - end_time.
|
||||
|
@ -55,20 +88,7 @@ def state_changes_during_period(start_time, end_time=None, entity_id=None):
|
|||
|
||||
states = recorder.query_states(query, data)
|
||||
|
||||
result = defaultdict(list)
|
||||
|
||||
entity_ids = [entity_id] if entity_id is not None else None
|
||||
|
||||
# Get the states at the start time
|
||||
for state in get_states(start_time, entity_ids):
|
||||
state.last_changed = start_time
|
||||
result[state.entity_id].append(state)
|
||||
|
||||
# Append all changes to it
|
||||
for entity_id, group in groupby(states, lambda state: state.entity_id):
|
||||
result[entity_id].extend(group)
|
||||
|
||||
return result
|
||||
return states_to_json(states, start_time, entity_id)
|
||||
|
||||
|
||||
def get_states(utc_point_in_time, entity_ids=None, run=None):
|
||||
|
@ -100,6 +120,33 @@ def get_states(utc_point_in_time, entity_ids=None, run=None):
|
|||
return recorder.query_states(query, where_data)
|
||||
|
||||
|
||||
def states_to_json(states, start_time, entity_id):
|
||||
"""Converts SQL results into JSON friendly data structure.
|
||||
|
||||
This takes our state list and turns it into a JSON friendly data
|
||||
structure {'entity_id': [list of states], 'entity_id2': [list of states]}
|
||||
|
||||
We also need to go back and create a synthetic zero data point for
|
||||
each list of states, otherwise our graphs won't start on the Y
|
||||
axis correctly.
|
||||
"""
|
||||
|
||||
result = defaultdict(list)
|
||||
|
||||
entity_ids = [entity_id] if entity_id is not None else None
|
||||
|
||||
# Get the states at the start time
|
||||
for state in get_states(start_time, entity_ids):
|
||||
state.last_changed = start_time
|
||||
state.last_updated = start_time
|
||||
result[state.entity_id].append(state)
|
||||
|
||||
# Append all changes to it
|
||||
for entity_id, group in groupby(states, lambda state: state.entity_id):
|
||||
result[entity_id].extend(group)
|
||||
return result
|
||||
|
||||
|
||||
def get_state(utc_point_in_time, entity_id, run=None):
|
||||
""" Return a state at a specific point in time. """
|
||||
states = get_states(utc_point_in_time, (entity_id,), run)
|
||||
|
@ -152,4 +199,4 @@ def _api_history_period(handler, path_match, data):
|
|||
entity_id = data.get('filter_entity_id')
|
||||
|
||||
handler.write_json(
|
||||
state_changes_during_period(start_time, end_time, entity_id).values())
|
||||
get_significant_states(start_time, end_time, entity_id).values())
|
||||
|
|
|
@ -9,10 +9,8 @@ https://home-assistant.io/components/influxdb/
|
|||
import logging
|
||||
import homeassistant.util as util
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.const import (EVENT_STATE_CHANGED, STATE_ON, STATE_OFF,
|
||||
STATE_UNLOCKED, STATE_LOCKED, STATE_UNKNOWN)
|
||||
from homeassistant.components.sun import (STATE_ABOVE_HORIZON,
|
||||
STATE_BELOW_HORIZON)
|
||||
from homeassistant.helpers import state as state_helper
|
||||
from homeassistant.const import (EVENT_STATE_CHANGED, STATE_UNKNOWN)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -22,14 +20,18 @@ DEPENDENCIES = []
|
|||
DEFAULT_HOST = 'localhost'
|
||||
DEFAULT_PORT = 8086
|
||||
DEFAULT_DATABASE = 'home_assistant'
|
||||
DEFAULT_SSL = False
|
||||
DEFAULT_VERIFY_SSL = False
|
||||
|
||||
REQUIREMENTS = ['influxdb==2.11.0']
|
||||
REQUIREMENTS = ['influxdb==2.12.0']
|
||||
|
||||
CONF_HOST = 'host'
|
||||
CONF_PORT = 'port'
|
||||
CONF_DB_NAME = 'database'
|
||||
CONF_USERNAME = 'username'
|
||||
CONF_PASSWORD = 'password'
|
||||
CONF_SSL = 'ssl'
|
||||
CONF_VERIFY_SSL = 'verify_ssl'
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
|
@ -37,7 +39,9 @@ def setup(hass, config):
|
|||
|
||||
from influxdb import InfluxDBClient, exceptions
|
||||
|
||||
if not validate_config(config, {DOMAIN: ['host']}, _LOGGER):
|
||||
if not validate_config(config, {DOMAIN: ['host',
|
||||
CONF_USERNAME,
|
||||
CONF_PASSWORD]}, _LOGGER):
|
||||
return False
|
||||
|
||||
conf = config[DOMAIN]
|
||||
|
@ -47,10 +51,14 @@ def setup(hass, config):
|
|||
database = util.convert(conf.get(CONF_DB_NAME), str, DEFAULT_DATABASE)
|
||||
username = util.convert(conf.get(CONF_USERNAME), str)
|
||||
password = util.convert(conf.get(CONF_PASSWORD), str)
|
||||
ssl = util.convert(conf.get(CONF_SSL), bool, DEFAULT_SSL)
|
||||
verify_ssl = util.convert(conf.get(CONF_VERIFY_SSL), bool,
|
||||
DEFAULT_VERIFY_SSL)
|
||||
|
||||
try:
|
||||
influx = InfluxDBClient(host=host, port=port, username=username,
|
||||
password=password, database=database)
|
||||
password=password, database=database,
|
||||
ssl=ssl, verify_ssl=verify_ssl)
|
||||
influx.query("select * from /.*/ LIMIT 1;")
|
||||
except exceptions.InfluxDBClientError as exc:
|
||||
_LOGGER.error("Database host is not accessible due to '%s', please "
|
||||
|
@ -62,25 +70,17 @@ def setup(hass, config):
|
|||
""" Listen for new messages on the bus and sends them to Influx. """
|
||||
|
||||
state = event.data.get('new_state')
|
||||
|
||||
if state is None:
|
||||
if state is None or state.state in (STATE_UNKNOWN, ''):
|
||||
return
|
||||
|
||||
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON):
|
||||
_state = 1
|
||||
elif state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
|
||||
STATE_BELOW_HORIZON):
|
||||
_state = 0
|
||||
else:
|
||||
try:
|
||||
_state = state_helper.state_as_number(state)
|
||||
except ValueError:
|
||||
_state = state.state
|
||||
if _state == '':
|
||||
return
|
||||
try:
|
||||
_state = float(_state)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
measurement = state.attributes.get('unit_of_measurement', state.domain)
|
||||
measurement = state.attributes.get('unit_of_measurement')
|
||||
if measurement in (None, ''):
|
||||
measurement = state.entity_id
|
||||
|
||||
json_body = [
|
||||
{
|
||||
|
|
|
@ -41,7 +41,7 @@ def turn_off(hass, entity_id):
|
|||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up input booleans."""
|
||||
""" Set up input boolean. """
|
||||
if not isinstance(config.get(DOMAIN), dict):
|
||||
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
|
||||
return False
|
||||
|
@ -68,7 +68,7 @@ def setup(hass, config):
|
|||
return False
|
||||
|
||||
def toggle_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)
|
||||
|
||||
for input_b in target_inputs:
|
||||
|
@ -86,10 +86,10 @@ def setup(hass, config):
|
|||
|
||||
|
||||
class InputBoolean(ToggleEntity):
|
||||
"""Represent a boolean input within Home Assistant."""
|
||||
""" Represent a boolean input. """
|
||||
|
||||
def __init__(self, object_id, name, state, icon):
|
||||
"""Initialize a boolean input."""
|
||||
""" Initialize a boolean input. """
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
|
||||
self._name = name
|
||||
self._state = state
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
"""
|
||||
homeassistant.components.input_select
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Component to offer a way to select an option from a list.
|
||||
|
||||
For more details about this component, please refer to the documentation
|
||||
at https://home-assistant.io/components/input_select/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import slugify
|
||||
|
||||
DOMAIN = 'input_select'
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_NAME = 'name'
|
||||
CONF_INITIAL = 'initial'
|
||||
CONF_ICON = 'icon'
|
||||
CONF_OPTIONS = 'options'
|
||||
|
||||
ATTR_OPTION = 'option'
|
||||
ATTR_OPTIONS = 'options'
|
||||
|
||||
SERVICE_SELECT_OPTION = 'select_option'
|
||||
|
||||
|
||||
def select_option(hass, entity_id, option):
|
||||
""" Set input_select to False. """
|
||||
hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, {
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_OPTION: option,
|
||||
})
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Set up input select. """
|
||||
if not isinstance(config.get(DOMAIN), dict):
|
||||
_LOGGER.error('Expected %s config to be a dictionary', DOMAIN)
|
||||
return False
|
||||
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
||||
|
||||
entities = []
|
||||
|
||||
for object_id, cfg in config[DOMAIN].items():
|
||||
if object_id != slugify(object_id):
|
||||
_LOGGER.warning("Found invalid key for boolean input: %s. "
|
||||
"Use %s instead", object_id, slugify(object_id))
|
||||
continue
|
||||
if not cfg:
|
||||
_LOGGER.warning("No configuration specified for %s", object_id)
|
||||
continue
|
||||
|
||||
name = cfg.get(CONF_NAME)
|
||||
options = cfg.get(CONF_OPTIONS)
|
||||
|
||||
if not isinstance(options, list) or len(options) == 0:
|
||||
_LOGGER.warning('Key %s should be a list of options', CONF_OPTIONS)
|
||||
continue
|
||||
|
||||
options = [str(val) for val in options]
|
||||
|
||||
state = cfg.get(CONF_INITIAL)
|
||||
|
||||
if state not in options:
|
||||
state = options[0]
|
||||
|
||||
icon = cfg.get(CONF_ICON)
|
||||
|
||||
entities.append(InputSelect(object_id, name, state, options, icon))
|
||||
|
||||
if not entities:
|
||||
return False
|
||||
|
||||
def select_option_service(call):
|
||||
""" Handle a calls to the input select services. """
|
||||
target_inputs = component.extract_from_service(call)
|
||||
|
||||
for input_select in target_inputs:
|
||||
input_select.select_option(call.data.get(ATTR_OPTION))
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_SELECT_OPTION,
|
||||
select_option_service)
|
||||
|
||||
component.add_entities(entities)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class InputSelect(Entity):
|
||||
""" Represent a select input. """
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, object_id, name, state, options, icon):
|
||||
""" Initialize a select input. """
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
|
||||
self._name = name
|
||||
self._current_option = state
|
||||
self._options = options
|
||||
self._icon = icon
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" If entity should be polled. """
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Name of the select input. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
""" Icon to be used for this entity. """
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" State of the component. """
|
||||
return self._current_option
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" State attributes. """
|
||||
return {
|
||||
ATTR_OPTIONS: self._options,
|
||||
}
|
||||
|
||||
def select_option(self, option):
|
||||
""" Select new option. """
|
||||
if option not in self._options:
|
||||
_LOGGER.warning('Invalid option: %s (possible options: %s)',
|
||||
option, ', '.join(self._options))
|
||||
return
|
||||
self._current_option = option
|
||||
self.update_ha_state()
|
|
@ -1,10 +1,10 @@
|
|||
"""
|
||||
homeassistant.components.insteon
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
homeassistant.components.insteon_hub
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for Insteon Hub.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/insteon/
|
||||
https://home-assistant.io/components/insteon_hub/
|
||||
"""
|
||||
import logging
|
||||
import homeassistant.bootstrap as bootstrap
|
||||
|
|
|
@ -11,7 +11,7 @@ import os
|
|||
import csv
|
||||
|
||||
from homeassistant.components import (
|
||||
group, discovery, wink, isy994, zwave, insteon_hub)
|
||||
group, discovery, wink, isy994, zwave, insteon_hub, mysensors)
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.const import (
|
||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
||||
|
@ -64,6 +64,7 @@ DISCOVERY_PLATFORMS = {
|
|||
isy994.DISCOVER_LIGHTS: 'isy994',
|
||||
discovery.SERVICE_HUE: 'hue',
|
||||
zwave.DISCOVER_LIGHTS: 'zwave',
|
||||
mysensors.DISCOVER_LIGHTS: 'mysensors',
|
||||
}
|
||||
|
||||
PROP_TO_ATTR = {
|
||||
|
@ -300,11 +301,6 @@ class Light(ToggleEntity):
|
|||
""" CT color value in mirads. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
""" Returns device specific state attributes. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Returns optional state attributes. """
|
||||
|
@ -322,9 +318,4 @@ class Light(ToggleEntity):
|
|||
data[ATTR_XY_COLOR][0], data[ATTR_XY_COLOR][1],
|
||||
data[ATTR_BRIGHTNESS])
|
||||
|
||||
device_attr = self.device_state_attributes
|
||||
|
||||
if device_attr is not None:
|
||||
data.update(device_attr)
|
||||
|
||||
return data
|
||||
|
|
|
@ -16,7 +16,7 @@ from homeassistant.components.light import \
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['liffylights==0.9.0']
|
||||
REQUIREMENTS = ['liffylights==0.9.4']
|
||||
DEPENDENCIES = []
|
||||
|
||||
CONF_SERVER = "server" # server address configuration item
|
||||
|
@ -58,10 +58,18 @@ class LIFX():
|
|||
bulb = self.find_bulb(ipaddr)
|
||||
|
||||
if bulb is None:
|
||||
_LOGGER.debug("new bulb %s %s %d %d %d %d %d",
|
||||
ipaddr, name, power, hue, sat, bri, kel)
|
||||
bulb = LIFXLight(self._liffylights, ipaddr, name,
|
||||
power, hue, sat, bri, kel)
|
||||
self._devices.append(bulb)
|
||||
self._add_devices_callback([bulb])
|
||||
else:
|
||||
_LOGGER.debug("update bulb %s %s %d %d %d %d %d",
|
||||
ipaddr, name, power, hue, sat, bri, kel)
|
||||
bulb.set_power(power)
|
||||
bulb.set_color(hue, sat, bri, kel)
|
||||
bulb.update_ha_state()
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def on_color(self, ipaddr, hue, sat, bri, kel):
|
||||
|
@ -95,7 +103,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
lifx_library = LIFX(add_devices_callback, server_addr, broadcast_addr)
|
||||
|
||||
# register our poll service
|
||||
track_time_change(hass, lifx_library.poll, second=10)
|
||||
track_time_change(hass, lifx_library.poll, second=[10, 40])
|
||||
|
||||
lifx_library.probe()
|
||||
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
"""
|
||||
homeassistant.components.light.mysensors.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for MySensors lights.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/light.mysensors.html
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.light import (
|
||||
Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR)
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL,
|
||||
STATE_ON, STATE_OFF)
|
||||
|
||||
import homeassistant.components.mysensors as mysensors
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
ATTR_RGB_WHITE = 'rgb_white'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the mysensors platform for sensors."""
|
||||
# Only act if loaded via mysensors by discovery event.
|
||||
# Otherwise gateway is not setup.
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
for gateway in mysensors.GATEWAYS.values():
|
||||
# Define the S_TYPES and V_TYPES that the platform should handle as
|
||||
# states. Map them in a dict of lists.
|
||||
pres = gateway.const.Presentation
|
||||
set_req = gateway.const.SetReq
|
||||
map_sv_types = {
|
||||
pres.S_LIGHT: [set_req.V_LIGHT],
|
||||
pres.S_DIMMER: [set_req.V_DIMMER],
|
||||
}
|
||||
if float(gateway.version) >= 1.5:
|
||||
# Add V_RGBW when rgb_white is implemented in the frontend
|
||||
map_sv_types.update({
|
||||
pres.S_RGB_LIGHT: [set_req.V_RGB],
|
||||
})
|
||||
map_sv_types[pres.S_LIGHT].append(set_req.V_STATUS)
|
||||
map_sv_types[pres.S_DIMMER].append(set_req.V_PERCENTAGE)
|
||||
|
||||
devices = {}
|
||||
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
|
||||
map_sv_types, devices, add_devices, MySensorsLight))
|
||||
|
||||
|
||||
class MySensorsLight(Light):
|
||||
"""Represent the value of a MySensors child node."""
|
||||
|
||||
# pylint: disable=too-many-arguments,too-many-instance-attributes
|
||||
|
||||
def __init__(self, gateway, node_id, child_id, name, value_type):
|
||||
"""Setup instance attributes."""
|
||||
self.gateway = gateway
|
||||
self.node_id = node_id
|
||||
self.child_id = child_id
|
||||
self._name = name
|
||||
self.value_type = value_type
|
||||
self.battery_level = 0
|
||||
self._values = {}
|
||||
self._state = None
|
||||
self._rgb = None
|
||||
self._brightness = None
|
||||
self._white = None
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""MySensor gateway pushes its state to HA."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of this entity."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Brightness of this light between 0..255."""
|
||||
return self._brightness
|
||||
|
||||
@property
|
||||
def rgb_color(self):
|
||||
"""RGB color value [int, int, int]."""
|
||||
return self._rgb
|
||||
|
||||
@property
|
||||
def rgb_white(self): # not implemented in the frontend yet
|
||||
"""White value in RGBW, value between 0..255."""
|
||||
return self._white
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
device_attr = {
|
||||
mysensors.ATTR_PORT: self.gateway.port,
|
||||
mysensors.ATTR_NODE_ID: self.node_id,
|
||||
mysensors.ATTR_CHILD_ID: self.child_id,
|
||||
ATTR_BATTERY_LEVEL: self.battery_level,
|
||||
}
|
||||
for value_type, value in self._values.items():
|
||||
device_attr[self.gateway.const.SetReq(value_type).name] = value
|
||||
return device_attr
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""True if device is on."""
|
||||
return self._state
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
rgb = self._rgb
|
||||
brightness = self._brightness
|
||||
white = self._white
|
||||
|
||||
if set_req.V_LIGHT in self._values and not self._state:
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, set_req.V_LIGHT, 1)
|
||||
|
||||
if ATTR_BRIGHTNESS in kwargs and set_req.V_DIMMER in self._values and \
|
||||
kwargs[ATTR_BRIGHTNESS] != self._brightness:
|
||||
brightness = kwargs[ATTR_BRIGHTNESS]
|
||||
percent = round(100 * brightness / 255)
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, set_req.V_DIMMER, percent)
|
||||
|
||||
if float(self.gateway.version) >= 1.5:
|
||||
|
||||
if ATTR_RGB_WHITE in kwargs and \
|
||||
self.value_type in (set_req.V_RGB, set_req.V_RGBW) and \
|
||||
kwargs[ATTR_RGB_WHITE] != self._white:
|
||||
white = kwargs[ATTR_RGB_WHITE]
|
||||
|
||||
if ATTR_RGB_COLOR in kwargs and \
|
||||
self.value_type in (set_req.V_RGB, set_req.V_RGBW) and \
|
||||
kwargs[ATTR_RGB_COLOR] != self._rgb:
|
||||
rgb = kwargs[ATTR_RGB_COLOR]
|
||||
if set_req.V_RGBW == self.value_type:
|
||||
hex_template = '%02x%02x%02x%02x'
|
||||
color_list = rgb.append(white)
|
||||
if set_req.V_RGB == self.value_type:
|
||||
hex_template = '%02x%02x%02x'
|
||||
color_list = rgb
|
||||
hex_color = hex_template % tuple(color_list)
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, self.value_type, hex_color)
|
||||
|
||||
if self.gateway.optimistic:
|
||||
# optimistically assume that light has changed state
|
||||
self._state = True
|
||||
self._rgb = rgb
|
||||
self._brightness = brightness
|
||||
self._white = white
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the device off."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
v_type = set_req.V_LIGHT
|
||||
value = 0
|
||||
if set_req.V_LIGHT in self._values:
|
||||
self._values[set_req.V_LIGHT] = STATE_OFF
|
||||
elif set_req.V_DIMMER in self._values:
|
||||
v_type = set_req.V_DIMMER
|
||||
elif float(self.gateway.version) >= 1.5:
|
||||
if set_req.V_RGB in self._values:
|
||||
v_type = set_req.V_RGB
|
||||
value = '000000'
|
||||
elif set_req.V_RGBW in self._values:
|
||||
v_type = set_req.V_RGBW
|
||||
value = '00000000'
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, v_type, value)
|
||||
|
||||
if self.gateway.optimistic:
|
||||
# optimistically assume that light has changed state
|
||||
self._state = False
|
||||
self.update_ha_state()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return self.value_type in self._values
|
||||
|
||||
def update(self):
|
||||
"""Update the controller with the latest value from a sensor."""
|
||||
node = self.gateway.sensors[self.node_id]
|
||||
child = node.children[self.child_id]
|
||||
set_req = self.gateway.const.SetReq
|
||||
self.battery_level = node.battery_level
|
||||
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_LIGHT:
|
||||
self._values[value_type] = (
|
||||
STATE_ON if int(value) == 1 else STATE_OFF)
|
||||
self._state = self._values[value_type] == STATE_ON
|
||||
else:
|
||||
self._values[value_type] = value
|
||||
if value_type == set_req.V_DIMMER:
|
||||
self._brightness = round(
|
||||
255 * int(self._values[value_type]) / 100)
|
||||
if self._brightness == 0:
|
||||
self._state = False
|
||||
if set_req.V_LIGHT not in self._values:
|
||||
self._state = self._brightness > 0
|
||||
if float(self.gateway.version) >= 1.5 and \
|
||||
value_type in (set_req.V_RGB, set_req.V_RGBW):
|
||||
# convert hex color string to rgb(w) integer list
|
||||
color_list = [int(value[i:i + len(value) // 3], 16)
|
||||
for i in range(0,
|
||||
len(value),
|
||||
len(value) // 3)]
|
||||
if len(color_list) > 3:
|
||||
self._white = color_list.pop()
|
||||
self._rgb = color_list
|
||||
if set_req.V_LIGHT not in self._values or \
|
||||
set_req.V_DIMMER not in self._values:
|
||||
self._state = max(color_list) > 0
|
|
@ -50,7 +50,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
|
||||
def light_update(event):
|
||||
""" Callback for light updates from the RFXtrx gateway. """
|
||||
if not isinstance(event.device, rfxtrxmod.LightingDevice):
|
||||
if not isinstance(event.device, rfxtrxmod.LightingDevice) or \
|
||||
not event.device.known_to_be_dimmable:
|
||||
return
|
||||
|
||||
# Add entity if not exist and the automatic_add is True
|
||||
|
@ -74,13 +75,13 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
add_devices_callback([new_light])
|
||||
|
||||
# Check if entity exists or previously added automatically
|
||||
if entity_id in rfxtrx.RFX_DEVICES \
|
||||
and isinstance(rfxtrx.RFX_DEVICES[entity_id], RfxtrxLight):
|
||||
if entity_id in rfxtrx.RFX_DEVICES:
|
||||
_LOGGER.debug(
|
||||
"EntityID: %s light_update. Command: %s",
|
||||
entity_id,
|
||||
event.values['Command']
|
||||
)
|
||||
|
||||
if event.values['Command'] == 'On'\
|
||||
or event.values['Command'] == 'Off':
|
||||
|
||||
|
@ -90,15 +91,27 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
rfxtrx.RFX_DEVICES[entity_id]._state = is_on
|
||||
rfxtrx.RFX_DEVICES[entity_id].update_ha_state()
|
||||
|
||||
# Fire event
|
||||
if rfxtrx.RFX_DEVICES[entity_id].should_fire_event:
|
||||
rfxtrx.RFX_DEVICES[entity_id].hass.bus.fire(
|
||||
EVENT_BUTTON_PRESSED, {
|
||||
ATTR_ENTITY_ID:
|
||||
rfxtrx.RFX_DEVICES[entity_id].entity_id,
|
||||
ATTR_STATE: event.values['Command'].lower()
|
||||
}
|
||||
)
|
||||
elif event.values['Command'] == 'Set level':
|
||||
# pylint: disable=protected-access
|
||||
rfxtrx.RFX_DEVICES[entity_id]._brightness = \
|
||||
(event.values['Dim level'] * 255 // 100)
|
||||
|
||||
# Update the rfxtrx device state
|
||||
is_on = rfxtrx.RFX_DEVICES[entity_id]._brightness > 0
|
||||
rfxtrx.RFX_DEVICES[entity_id]._state = is_on
|
||||
rfxtrx.RFX_DEVICES[entity_id].update_ha_state()
|
||||
else:
|
||||
return
|
||||
|
||||
# Fire event
|
||||
if rfxtrx.RFX_DEVICES[entity_id].should_fire_event:
|
||||
rfxtrx.RFX_DEVICES[entity_id].hass.bus.fire(
|
||||
EVENT_BUTTON_PRESSED, {
|
||||
ATTR_ENTITY_ID:
|
||||
rfxtrx.RFX_DEVICES[entity_id].entity_id,
|
||||
ATTR_STATE: event.values['Command'].lower()
|
||||
}
|
||||
)
|
||||
|
||||
# Subscribe to main rfxtrx events
|
||||
if light_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS:
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
"""
|
||||
homeassistant.components.light.scsgate
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for SCSGate lights.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/light.scsgate/
|
||||
"""
|
||||
import logging
|
||||
import homeassistant.components.scsgate as scsgate
|
||||
|
||||
from homeassistant.components.light import Light
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
|
||||
DEPENDENCIES = ['scsgate']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
""" Add the SCSGate swiches defined inside of the configuration file. """
|
||||
|
||||
devices = config.get('devices')
|
||||
lights = []
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if devices:
|
||||
for _, entity_info in devices.items():
|
||||
if entity_info['scs_id'] in scsgate.SCSGATE.devices:
|
||||
continue
|
||||
|
||||
logger.info("Adding %s scsgate.light", entity_info['name'])
|
||||
|
||||
name = entity_info['name']
|
||||
scs_id = entity_info['scs_id']
|
||||
light = SCSGateLight(
|
||||
name=name,
|
||||
scs_id=scs_id,
|
||||
logger=logger)
|
||||
lights.append(light)
|
||||
|
||||
add_devices_callback(lights)
|
||||
scsgate.SCSGATE.add_devices_to_register(lights)
|
||||
|
||||
|
||||
class SCSGateLight(Light):
|
||||
""" Provides a SCSGate light. """
|
||||
def __init__(self, scs_id, name, logger):
|
||||
self._name = name
|
||||
self._scs_id = scs_id
|
||||
self._toggled = False
|
||||
self._logger = logger
|
||||
|
||||
@property
|
||||
def scs_id(self):
|
||||
""" SCS ID """
|
||||
return self._scs_id
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" No polling needed for a SCSGate light. """
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device if any. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
""" True if light is on. """
|
||||
return self._toggled
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
""" Turn the device on. """
|
||||
from scsgate.tasks import ToggleStatusTask
|
||||
|
||||
scsgate.SCSGATE.append_task(
|
||||
ToggleStatusTask(
|
||||
target=self._scs_id,
|
||||
toggled=True))
|
||||
|
||||
self._toggled = True
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
""" Turn the device off. """
|
||||
from scsgate.tasks import ToggleStatusTask
|
||||
|
||||
scsgate.SCSGATE.append_task(
|
||||
ToggleStatusTask(
|
||||
target=self._scs_id,
|
||||
toggled=False))
|
||||
|
||||
self._toggled = False
|
||||
self.update_ha_state()
|
||||
|
||||
def process_event(self, message):
|
||||
""" Handle a SCSGate message related with this light """
|
||||
if self._toggled == message.toggled:
|
||||
self._logger.info(
|
||||
"Light %s, ignoring message %s because state already active",
|
||||
self._scs_id, message)
|
||||
# Nothing changed, ignoring
|
||||
return
|
||||
|
||||
self._toggled = message.toggled
|
||||
self.update_ha_state()
|
||||
|
||||
command = "off"
|
||||
if self._toggled:
|
||||
command = "on"
|
||||
|
||||
self.hass.bus.fire(
|
||||
'button_pressed', {
|
||||
ATTR_ENTITY_ID: self._scs_id,
|
||||
'state': command
|
||||
}
|
||||
)
|
|
@ -9,13 +9,20 @@ https://home-assistant.io/components/light.vera/
|
|||
import logging
|
||||
|
||||
from requests.exceptions import RequestException
|
||||
from homeassistant.components.switch.vera import VeraSwitch
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS
|
||||
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
|
||||
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, STATE_ON
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL,
|
||||
ATTR_TRIPPED,
|
||||
ATTR_ARMED,
|
||||
ATTR_LAST_TRIP_TIME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
STATE_ON,
|
||||
STATE_OFF)
|
||||
|
||||
REQUIREMENTS = ['pyvera==0.2.7']
|
||||
REQUIREMENTS = ['pyvera==0.2.8']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -67,17 +74,35 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
add_devices_callback(lights)
|
||||
|
||||
|
||||
class VeraLight(VeraSwitch):
|
||||
class VeraLight(Light):
|
||||
""" Represents a Vera Light, including dimmable. """
|
||||
|
||||
def __init__(self, vera_device, controller, extra_data=None):
|
||||
self.vera_device = vera_device
|
||||
self.extra_data = extra_data
|
||||
self.controller = controller
|
||||
if self.extra_data and self.extra_data.get('name'):
|
||||
self._name = self.extra_data.get('name')
|
||||
else:
|
||||
self._name = self.vera_device.name
|
||||
self._state = STATE_OFF
|
||||
|
||||
self.controller.register(vera_device, self._update_callback)
|
||||
self.update()
|
||||
|
||||
def _update_callback(self, _device):
|
||||
self.update_ha_state(True)
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
attr = super().state_attributes or {}
|
||||
def name(self):
|
||||
""" Get the mame of the switch. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Brightness of the light."""
|
||||
if self.vera_device.is_dimmable:
|
||||
attr[ATTR_BRIGHTNESS] = self.vera_device.get_brightness()
|
||||
|
||||
return attr
|
||||
return self.vera_device.get_brightness()
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
if ATTR_BRIGHTNESS in kwargs and self.vera_device.is_dimmable:
|
||||
|
@ -87,3 +112,49 @@ class VeraLight(VeraSwitch):
|
|||
|
||||
self._state = STATE_ON
|
||||
self.update_ha_state(True)
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
self.vera_device.switch_off()
|
||||
self._state = STATE_OFF
|
||||
self.update_ha_state()
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
attr = {}
|
||||
|
||||
if self.vera_device.has_battery:
|
||||
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
|
||||
|
||||
if self.vera_device.is_armable:
|
||||
armed = self.vera_device.is_armed
|
||||
attr[ATTR_ARMED] = 'True' if armed else 'False'
|
||||
|
||||
if self.vera_device.is_trippable:
|
||||
last_tripped = self.vera_device.last_trip
|
||||
if last_tripped is not None:
|
||||
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
|
||||
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
|
||||
utc_time)
|
||||
else:
|
||||
attr[ATTR_LAST_TRIP_TIME] = None
|
||||
tripped = self.vera_device.is_tripped
|
||||
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
|
||||
|
||||
attr['Vera Device Id'] = self.vera_device.vera_device_id
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" Tells Home Assistant not to poll this entity. """
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
""" True if device is on. """
|
||||
return self._state == STATE_ON
|
||||
|
||||
def update(self):
|
||||
""" Called by the vera device callback to update state. """
|
||||
if self.vera_device.is_switched_on():
|
||||
self._state = STATE_ON
|
||||
else:
|
||||
self._state = STATE_OFF
|
||||
|
|
|
@ -8,11 +8,10 @@ https://home-assistant.io/components/light.wink/
|
|||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS
|
||||
from homeassistant.components.wink import WinkToggleDevice
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
|
||||
REQUIREMENTS = ['python-wink==0.4.2']
|
||||
REQUIREMENTS = ['python-wink==0.5.0']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
|
@ -34,9 +33,32 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||
WinkLight(light) for light in pywink.get_bulbs())
|
||||
|
||||
|
||||
class WinkLight(WinkToggleDevice):
|
||||
class WinkLight(Light):
|
||||
""" Represents a Wink light. """
|
||||
|
||||
def __init__(self, wink):
|
||||
self.wink = wink
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
""" Returns the id of this Wink switch. """
|
||||
return "{}.{}".format(self.__class__, self.wink.device_id())
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the light if any. """
|
||||
return self.wink.name()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
""" True if light is on. """
|
||||
return self.wink.state()
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Brightness of the light."""
|
||||
return int(self.wink.brightness() * 255)
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
def turn_on(self, **kwargs):
|
||||
""" Turns the switch on. """
|
||||
|
@ -48,14 +70,10 @@ class WinkLight(WinkToggleDevice):
|
|||
else:
|
||||
self.wink.set_state(True)
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
attr = super().state_attributes
|
||||
def turn_off(self):
|
||||
""" Turns the switch off. """
|
||||
self.wink.set_state(False)
|
||||
|
||||
if self.is_on:
|
||||
brightness = self.wink.brightness()
|
||||
|
||||
if brightness is not None:
|
||||
attr[ATTR_BRIGHTNESS] = int(brightness * 255)
|
||||
|
||||
return attr
|
||||
def update(self):
|
||||
""" Update state of the light. """
|
||||
self.wink.update_state()
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
"""
|
||||
homeassistant.components.light.zigbee
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Contains functionality to use a ZigBee device as a light.
|
||||
"""
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/light.zigbee/
|
||||
"""
|
||||
from homeassistant.components.light import Light
|
||||
from homeassistant.components.zigbee import (
|
||||
ZigBeeDigitalOut, ZigBeeDigitalOutConfig)
|
||||
|
@ -13,9 +15,7 @@ DEPENDENCIES = ["zigbee"]
|
|||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""
|
||||
Create and add an entity based on the configuration.
|
||||
"""
|
||||
""" Create and add an entity based on the configuration. """
|
||||
add_entities([
|
||||
ZigBeeLight(hass, ZigBeeDigitalOutConfig(config))
|
||||
])
|
||||
|
|
|
@ -17,7 +17,7 @@ from homeassistant.helpers.entity import Entity
|
|||
from homeassistant.const import (
|
||||
STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK,
|
||||
ATTR_ENTITY_ID)
|
||||
from homeassistant.components import (group, wink)
|
||||
from homeassistant.components import (group, verisure, wink)
|
||||
|
||||
DOMAIN = 'lock'
|
||||
SCAN_INTERVAL = 30
|
||||
|
@ -28,12 +28,15 @@ ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format('all_locks')
|
|||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
ATTR_LOCKED = "locked"
|
||||
ATTR_CODE = 'code'
|
||||
ATTR_CODE_FORMAT = 'code_format'
|
||||
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
|
||||
# Maps discovered services to their platforms
|
||||
DISCOVERY_PLATFORMS = {
|
||||
wink.DISCOVER_LOCKS: 'wink'
|
||||
wink.DISCOVER_LOCKS: 'wink',
|
||||
verisure.DISCOVER_LOCKS: 'verisure'
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -45,15 +48,25 @@ def is_locked(hass, entity_id=None):
|
|||
return hass.states.is_state(entity_id, STATE_LOCKED)
|
||||
|
||||
|
||||
def lock(hass, entity_id=None):
|
||||
def lock(hass, entity_id=None, code=None):
|
||||
""" Locks all or specified locks. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_LOCK, data)
|
||||
|
||||
|
||||
def unlock(hass, entity_id=None):
|
||||
def unlock(hass, entity_id=None, code=None):
|
||||
""" Unlocks all or specified locks. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_UNLOCK, data)
|
||||
|
||||
|
||||
|
@ -68,11 +81,16 @@ def setup(hass, config):
|
|||
""" Handles calls to the lock services. """
|
||||
target_locks = component.extract_from_service(service)
|
||||
|
||||
if ATTR_CODE not in service.data:
|
||||
code = None
|
||||
else:
|
||||
code = service.data[ATTR_CODE]
|
||||
|
||||
for item in target_locks:
|
||||
if service.service == SERVICE_LOCK:
|
||||
item.lock()
|
||||
item.lock(code=code)
|
||||
else:
|
||||
item.unlock()
|
||||
item.unlock(code=code)
|
||||
|
||||
if item.should_poll:
|
||||
item.update_ha_state(True)
|
||||
|
@ -91,19 +109,34 @@ class LockDevice(Entity):
|
|||
""" Represents a lock within Home Assistant. """
|
||||
# pylint: disable=no-self-use
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
""" regex for code format or None if no code is required. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_locked(self):
|
||||
""" Is the lock locked or unlocked. """
|
||||
return None
|
||||
|
||||
def lock(self):
|
||||
def lock(self, **kwargs):
|
||||
""" Locks the lock. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def unlock(self):
|
||||
def unlock(self, **kwargs):
|
||||
""" Unlocks the lock. """
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Return the state attributes. """
|
||||
if self.code_format is None:
|
||||
return None
|
||||
state_attr = {
|
||||
ATTR_CODE_FORMAT: self.code_format,
|
||||
}
|
||||
return state_attr
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
locked = self.is_locked
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
"""
|
||||
homeassistant.components.lock.verisure
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Interfaces with Verisure locks.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/verisure/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import homeassistant.components.verisure as verisure
|
||||
from homeassistant.components.lock import LockDevice
|
||||
|
||||
from homeassistant.const import (
|
||||
STATE_UNKNOWN,
|
||||
STATE_LOCKED, STATE_UNLOCKED)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
ATTR_CODE = 'code'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the Verisure platform. """
|
||||
|
||||
if not verisure.MY_PAGES:
|
||||
_LOGGER.error('A connection has not been made to Verisure mypages.')
|
||||
return False
|
||||
|
||||
locks = []
|
||||
|
||||
locks.extend([VerisureDoorlock(value)
|
||||
for value in verisure.LOCK_STATUS.values()
|
||||
if verisure.SHOW_LOCKS])
|
||||
|
||||
add_devices(locks)
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class VerisureDoorlock(LockDevice):
|
||||
""" Represents a Verisure doorlock status. """
|
||||
|
||||
def __init__(self, lock_status, code=None):
|
||||
self._id = lock_status.id
|
||||
self._state = STATE_UNKNOWN
|
||||
self._code = code
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return 'Lock {}'.format(self._id)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
""" Six digit code required. """
|
||||
return '^\\d{%s}$' % verisure.CODE_DIGITS
|
||||
|
||||
def update(self):
|
||||
""" Update lock status """
|
||||
verisure.update_lock()
|
||||
|
||||
if verisure.LOCK_STATUS[self._id].status == 'unlocked':
|
||||
self._state = STATE_UNLOCKED
|
||||
elif verisure.LOCK_STATUS[self._id].status == 'locked':
|
||||
self._state = STATE_LOCKED
|
||||
elif verisure.LOCK_STATUS[self._id].status != 'pending':
|
||||
_LOGGER.error(
|
||||
'Unknown lock state %s',
|
||||
verisure.LOCK_STATUS[self._id].status)
|
||||
|
||||
@property
|
||||
def is_locked(self):
|
||||
""" True if device is locked. """
|
||||
return verisure.LOCK_STATUS[self._id].status
|
||||
|
||||
def unlock(self, **kwargs):
|
||||
""" Send unlock command. """
|
||||
verisure.MY_PAGES.lock.set(kwargs[ATTR_CODE], self._id, 'UNLOCKED')
|
||||
_LOGGER.info('verisure doorlock unlocking')
|
||||
verisure.MY_PAGES.lock.wait_while_pending()
|
||||
verisure.update_lock()
|
||||
|
||||
def lock(self, **kwargs):
|
||||
""" Send lock command. """
|
||||
verisure.MY_PAGES.lock.set(kwargs[ATTR_CODE], self._id, 'LOCKED')
|
||||
_LOGGER.info('verisure doorlock locking')
|
||||
verisure.MY_PAGES.lock.wait_while_pending()
|
||||
verisure.update_lock()
|
|
@ -11,7 +11,7 @@ import logging
|
|||
from homeassistant.components.lock import LockDevice
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
|
||||
REQUIREMENTS = ['python-wink==0.4.2']
|
||||
REQUIREMENTS = ['python-wink==0.5.0']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
|
|
@ -32,7 +32,6 @@ DISCOVERY_PLATFORMS = {
|
|||
discovery.SERVICE_PLEX: 'plex',
|
||||
}
|
||||
|
||||
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
|
||||
SERVICE_PLAY_MEDIA = 'play_media'
|
||||
|
||||
ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
|
||||
|
@ -68,14 +67,12 @@ SUPPORT_VOLUME_SET = 4
|
|||
SUPPORT_VOLUME_MUTE = 8
|
||||
SUPPORT_PREVIOUS_TRACK = 16
|
||||
SUPPORT_NEXT_TRACK = 32
|
||||
SUPPORT_YOUTUBE = 64
|
||||
|
||||
SUPPORT_TURN_ON = 128
|
||||
SUPPORT_TURN_OFF = 256
|
||||
SUPPORT_PLAY_MEDIA = 512
|
||||
SUPPORT_VOLUME_STEP = 1024
|
||||
|
||||
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'
|
||||
|
||||
SERVICE_TO_METHOD = {
|
||||
SERVICE_TURN_ON: 'turn_on',
|
||||
SERVICE_TURN_OFF: 'turn_off',
|
||||
|
@ -200,6 +197,13 @@ def media_previous_track(hass, entity_id=None):
|
|||
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data)
|
||||
|
||||
|
||||
def media_seek(hass, position, entity_id=None):
|
||||
""" Send the media player the command to seek in current playing media. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
data[ATTR_MEDIA_SEEK_POSITION] = position
|
||||
hass.services.call(DOMAIN, SERVICE_MEDIA_SEEK, data)
|
||||
|
||||
|
||||
def play_media(hass, media_type, media_id, entity_id=None):
|
||||
""" Send the media player the command for playing media. """
|
||||
data = {"media_type": media_type, "media_id": media_id}
|
||||
|
@ -283,7 +287,7 @@ def setup(hass, config):
|
|||
position = service.data[ATTR_MEDIA_SEEK_POSITION]
|
||||
|
||||
for player in target_players:
|
||||
player.seek(position)
|
||||
player.media_seek(position)
|
||||
|
||||
if player.should_poll:
|
||||
player.update_ha_state(True)
|
||||
|
@ -291,20 +295,6 @@ def setup(hass, config):
|
|||
hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service,
|
||||
descriptions.get(SERVICE_MEDIA_SEEK))
|
||||
|
||||
def play_youtube_video_service(service, media_id=None):
|
||||
""" Plays specified media_id on the media player. """
|
||||
if media_id is None:
|
||||
service.data.get('video')
|
||||
|
||||
if media_id is None:
|
||||
return
|
||||
|
||||
for player in component.extract_from_service(service):
|
||||
player.play_youtube(media_id)
|
||||
|
||||
if player.should_poll:
|
||||
player.update_ha_state(True)
|
||||
|
||||
def play_media_service(service):
|
||||
""" Plays specified media_id on the media player. """
|
||||
media_type = service.data.get('media_type')
|
||||
|
@ -322,20 +312,6 @@ def setup(hass, config):
|
|||
if player.should_poll:
|
||||
player.update_ha_state(True)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, "start_fireplace",
|
||||
lambda service: play_youtube_video_service(service, "eyU3bRy2x44"),
|
||||
descriptions.get('start_fireplace'))
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, "start_epic_sax",
|
||||
lambda service: play_youtube_video_service(service, "kxopViU98Xo"),
|
||||
descriptions.get('start_epic_sax'))
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_YOUTUBE_VIDEO, play_youtube_video_service,
|
||||
descriptions.get(SERVICE_YOUTUBE_VIDEO))
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_PLAY_MEDIA, play_media_service,
|
||||
descriptions.get(SERVICE_PLAY_MEDIA))
|
||||
|
@ -449,11 +425,6 @@ class MediaPlayerDevice(Entity):
|
|||
""" Flags of media commands that are supported. """
|
||||
return 0
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
""" Extra attributes a device wants to expose. """
|
||||
return None
|
||||
|
||||
def turn_on(self):
|
||||
""" turn the media player on. """
|
||||
raise NotImplementedError()
|
||||
|
@ -490,10 +461,6 @@ class MediaPlayerDevice(Entity):
|
|||
""" Send seek command. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def play_youtube(self, media_id):
|
||||
""" Plays a YouTube media. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def play_media(self, media_type, media_id):
|
||||
""" Plays a piece of media. """
|
||||
raise NotImplementedError()
|
||||
|
@ -529,11 +496,6 @@ class MediaPlayerDevice(Entity):
|
|||
""" Boolean if next track command supported. """
|
||||
return bool(self.supported_media_commands & SUPPORT_NEXT_TRACK)
|
||||
|
||||
@property
|
||||
def support_youtube(self):
|
||||
""" Boolean if YouTube is supported. """
|
||||
return bool(self.supported_media_commands & SUPPORT_YOUTUBE)
|
||||
|
||||
@property
|
||||
def support_play_media(self):
|
||||
""" Boolean if play media command supported. """
|
||||
|
@ -579,9 +541,4 @@ class MediaPlayerDevice(Entity):
|
|||
if self.media_image_url:
|
||||
state_attr[ATTR_ENTITY_PICTURE] = self.media_image_url
|
||||
|
||||
device_attr = self.device_state_attributes
|
||||
|
||||
if device_attr:
|
||||
state_attr.update(device_attr)
|
||||
|
||||
return state_attr
|
||||
|
|
|
@ -16,7 +16,7 @@ from homeassistant.const import (
|
|||
from homeassistant.components.media_player import (
|
||||
MediaPlayerDevice,
|
||||
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
|
||||
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_YOUTUBE, SUPPORT_PLAY_MEDIA,
|
||||
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA,
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
|
||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
|
||||
|
||||
|
@ -25,51 +25,48 @@ CONF_IGNORE_CEC = 'ignore_cec'
|
|||
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
|
||||
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \
|
||||
SUPPORT_NEXT_TRACK | SUPPORT_YOUTUBE | SUPPORT_PLAY_MEDIA
|
||||
SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA
|
||||
KNOWN_HOSTS = []
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
cast = None
|
||||
DEFAULT_PORT = 8009
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the cast platform. """
|
||||
global cast
|
||||
import pychromecast
|
||||
cast = pychromecast
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# import CEC IGNORE attributes
|
||||
ignore_cec = config.get(CONF_IGNORE_CEC, [])
|
||||
if isinstance(ignore_cec, list):
|
||||
cast.IGNORE_CEC += ignore_cec
|
||||
pychromecast.IGNORE_CEC += ignore_cec
|
||||
else:
|
||||
logger.error('Chromecast conig, %s must be a list.', CONF_IGNORE_CEC)
|
||||
logger.error('CEC config "%s" must be a list.', CONF_IGNORE_CEC)
|
||||
|
||||
hosts = []
|
||||
|
||||
if discovery_info and discovery_info[0] not in KNOWN_HOSTS:
|
||||
hosts = [discovery_info[0]]
|
||||
if discovery_info and discovery_info in KNOWN_HOSTS:
|
||||
return
|
||||
|
||||
elif discovery_info:
|
||||
hosts = [discovery_info]
|
||||
|
||||
elif CONF_HOST in config:
|
||||
hosts = [config[CONF_HOST]]
|
||||
hosts = [(config[CONF_HOST], DEFAULT_PORT)]
|
||||
|
||||
else:
|
||||
hosts = (host_port[0] for host_port
|
||||
in cast.discover_chromecasts()
|
||||
if host_port[0] not in KNOWN_HOSTS)
|
||||
hosts = [tuple(dev[:2]) for dev in pychromecast.discover_chromecasts()
|
||||
if tuple(dev[:2]) not in KNOWN_HOSTS]
|
||||
|
||||
casts = []
|
||||
|
||||
for host in hosts:
|
||||
try:
|
||||
casts.append(CastDevice(host))
|
||||
except cast.ChromecastConnectionError:
|
||||
pass
|
||||
else:
|
||||
casts.append(CastDevice(*host))
|
||||
KNOWN_HOSTS.append(host)
|
||||
except pychromecast.ChromecastConnectionError:
|
||||
pass
|
||||
|
||||
add_devices(casts)
|
||||
|
||||
|
@ -80,11 +77,9 @@ class CastDevice(MediaPlayerDevice):
|
|||
# pylint: disable=abstract-method
|
||||
# pylint: disable=too-many-public-methods
|
||||
|
||||
def __init__(self, host):
|
||||
import pychromecast.controllers.youtube as youtube
|
||||
self.cast = cast.Chromecast(host)
|
||||
self.youtube = youtube.YouTubeController()
|
||||
self.cast.register_handler(self.youtube)
|
||||
def __init__(self, host, port):
|
||||
import pychromecast
|
||||
self.cast = pychromecast.Chromecast(host, port)
|
||||
|
||||
self.cast.socket_client.receiver_controller.register_status_listener(
|
||||
self)
|
||||
|
@ -224,11 +219,13 @@ class CastDevice(MediaPlayerDevice):
|
|||
""" Turns on the ChromeCast. """
|
||||
# The only way we can turn the Chromecast is on is by launching an app
|
||||
if not self.cast.status or not self.cast.status.is_active_input:
|
||||
import pychromecast
|
||||
|
||||
if self.cast.app_id:
|
||||
self.cast.quit_app()
|
||||
|
||||
self.cast.play_media(
|
||||
CAST_SPLASH, cast.STREAM_TYPE_BUFFERED)
|
||||
CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED)
|
||||
|
||||
def turn_off(self):
|
||||
""" Turns Chromecast off. """
|
||||
|
@ -266,10 +263,6 @@ class CastDevice(MediaPlayerDevice):
|
|||
""" Plays media from a URL """
|
||||
self.cast.media_controller.play_media(media_id, media_type)
|
||||
|
||||
def play_youtube(self, media_id):
|
||||
""" Plays a YouTube media. """
|
||||
self.youtube.play_video(media_id)
|
||||
|
||||
# implementation of chromecast status_listener methods
|
||||
|
||||
def new_cast_status(self, status):
|
||||
|
|
|
@ -7,11 +7,11 @@ from homeassistant.const import (
|
|||
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
MediaPlayerDevice, YOUTUBE_COVER_URL_FORMAT,
|
||||
MediaPlayerDevice,
|
||||
MEDIA_TYPE_VIDEO, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
|
||||
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_YOUTUBE,
|
||||
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
|
||||
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_NEXT_TRACK)
|
||||
SUPPORT_NEXT_TRACK, SUPPORT_PLAY_MEDIA)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
@ -26,9 +26,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
])
|
||||
|
||||
|
||||
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'
|
||||
|
||||
YOUTUBE_PLAYER_SUPPORT = \
|
||||
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||
SUPPORT_YOUTUBE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
||||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA
|
||||
|
||||
MUSIC_PLAYER_SUPPORT = \
|
||||
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||
|
@ -150,10 +152,9 @@ class DemoYoutubePlayer(AbstractDemoPlayer):
|
|||
""" Flags of media commands that are supported. """
|
||||
return YOUTUBE_PLAYER_SUPPORT
|
||||
|
||||
def play_youtube(self, media_id):
|
||||
""" Plays a YouTube media. """
|
||||
def play_media(self, media_type, media_id):
|
||||
""" Plays a piece of media. """
|
||||
self.youtube_id = media_id
|
||||
self._media_title = 'some YouTube video'
|
||||
self.update_ha_state()
|
||||
|
||||
|
||||
|
@ -234,7 +235,7 @@ class DemoMusicPlayer(AbstractDemoPlayer):
|
|||
""" Flags of media commands that are supported. """
|
||||
support = MUSIC_PLAYER_SUPPORT
|
||||
|
||||
if self._cur_track > 1:
|
||||
if self._cur_track > 0:
|
||||
support |= SUPPORT_PREVIOUS_TRACK
|
||||
|
||||
if self._cur_track < len(self.tracks)-1:
|
||||
|
|
|
@ -24,7 +24,6 @@ SUPPORT_DENON = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
|||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the Denon platform. """
|
||||
if not config.get(CONF_HOST):
|
||||
|
@ -48,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
class DenonDevice(MediaPlayerDevice):
|
||||
""" Represents a Denon device. """
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
# pylint: disable=too-many-public-methods, abstract-method
|
||||
|
||||
def __init__(self, name, host):
|
||||
self._name = name
|
||||
|
@ -145,10 +144,6 @@ class DenonDevice(MediaPlayerDevice):
|
|||
""" mute (true) or unmute (false) media player. """
|
||||
self.telnet_command("MU" + ("ON" if mute else "OFF"))
|
||||
|
||||
def media_play_pause(self):
|
||||
""" media_play_pause media player. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def media_play(self):
|
||||
""" media_play media player. """
|
||||
self.telnet_command("NS9A")
|
||||
|
@ -164,9 +159,6 @@ class DenonDevice(MediaPlayerDevice):
|
|||
def media_previous_track(self):
|
||||
self.telnet_command("NS9E")
|
||||
|
||||
def media_seek(self, position):
|
||||
raise NotImplementedError()
|
||||
|
||||
def turn_on(self):
|
||||
""" turn the media player on. """
|
||||
self.telnet_command("PWON")
|
||||
|
|
|
@ -105,6 +105,8 @@ class FireTV(object):
|
|||
class FireTVDevice(MediaPlayerDevice):
|
||||
""" Represents an Amazon Fire TV device on the network. """
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, host, device, name):
|
||||
self._firetv = FireTV(host, device)
|
||||
self._name = name
|
||||
|
@ -176,15 +178,3 @@ class FireTVDevice(MediaPlayerDevice):
|
|||
def media_next_track(self):
|
||||
""" Send next track command (results in fast-forward). """
|
||||
self._firetv.action('media_next')
|
||||
|
||||
def media_seek(self, position):
|
||||
raise NotImplementedError()
|
||||
|
||||
def mute_volume(self, mute):
|
||||
raise NotImplementedError()
|
||||
|
||||
def play_youtube(self, media_id):
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_volume_level(self, volume):
|
||||
raise NotImplementedError()
|
||||
|
|
|
@ -22,43 +22,41 @@ SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
|||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the kodi platform. """
|
||||
|
||||
url = '{}:{}'.format(config.get('host'), config.get('port', '8080'))
|
||||
|
||||
jsonrpc_url = config.get('url') # deprecated
|
||||
if jsonrpc_url:
|
||||
url = jsonrpc_url.rstrip('/jsonrpc')
|
||||
|
||||
add_devices([
|
||||
KodiDevice(
|
||||
config.get('name', 'Kodi'),
|
||||
config.get('url'),
|
||||
url,
|
||||
auth=(
|
||||
config.get('user', ''),
|
||||
config.get('password', ''))),
|
||||
])
|
||||
|
||||
|
||||
def _get_image_url(kodi_url):
|
||||
""" Helper function that parses the thumbnail URLs used by Kodi. """
|
||||
url_components = urllib.parse.urlparse(kodi_url)
|
||||
|
||||
if url_components.scheme == 'image':
|
||||
return urllib.parse.unquote(url_components.netloc)
|
||||
|
||||
|
||||
class KodiDevice(MediaPlayerDevice):
|
||||
""" Represents a XBMC/Kodi device. """
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
# pylint: disable=too-many-public-methods, abstract-method
|
||||
|
||||
def __init__(self, name, url, auth=None):
|
||||
import jsonrpc_requests
|
||||
self._name = name
|
||||
self._url = url
|
||||
self._server = jsonrpc_requests.Server(url, auth=auth)
|
||||
self._server = jsonrpc_requests.Server(
|
||||
'{}/jsonrpc'.format(self._url),
|
||||
auth=auth)
|
||||
self._players = None
|
||||
self._properties = None
|
||||
self._item = None
|
||||
self._app_properties = None
|
||||
|
||||
self.update()
|
||||
|
||||
@property
|
||||
|
@ -156,7 +154,16 @@ class KodiDevice(MediaPlayerDevice):
|
|||
def media_image_url(self):
|
||||
""" Image url of current playing media. """
|
||||
if self._item is not None:
|
||||
return _get_image_url(self._item['thumbnail'])
|
||||
return self._get_image_url()
|
||||
|
||||
def _get_image_url(self):
|
||||
""" Helper function that parses the thumbnail URLs used by Kodi. """
|
||||
url_components = urllib.parse.urlparse(self._item['thumbnail'])
|
||||
|
||||
if url_components.scheme == 'image':
|
||||
return '{}/image/{}'.format(
|
||||
self._url,
|
||||
urllib.parse.quote_plus(self._item['thumbnail']))
|
||||
|
||||
@property
|
||||
def media_title(self):
|
||||
|
@ -263,11 +270,3 @@ class KodiDevice(MediaPlayerDevice):
|
|||
self._server.Player.Seek(players[0]['playerid'], time)
|
||||
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_on(self):
|
||||
""" turn the media player on. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def play_youtube(self, media_id):
|
||||
""" Plays a YouTube media. """
|
||||
raise NotImplementedError()
|
||||
|
|
|
@ -59,7 +59,7 @@ def config_from_file(filename, config=None):
|
|||
return {}
|
||||
|
||||
|
||||
# pylint: disable=abstract-method, unused-argument
|
||||
# pylint: disable=abstract-method
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
""" Sets up the plex platform. """
|
||||
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
"""
|
||||
homeassistant.components.media_player.denon
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Provides an interface to Samsung TV with a Laninterface.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/media_player.samsungtv/
|
||||
"""
|
||||
import logging
|
||||
import socket
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_VOLUME_STEP,
|
||||
SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_NEXT_TRACK, SUPPORT_TURN_OFF,
|
||||
DOMAIN)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_NAME, STATE_OFF,
|
||||
STATE_ON, STATE_UNKNOWN)
|
||||
|
||||
from homeassistant.helpers import validate_config
|
||||
|
||||
CONF_PORT = "port"
|
||||
CONF_TIMEOUT = "timeout"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['samsungctl==0.5.1']
|
||||
|
||||
SUPPORT_SAMSUNGTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
|
||||
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \
|
||||
SUPPORT_NEXT_TRACK | SUPPORT_TURN_OFF
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the Samsung TV platform. """
|
||||
|
||||
# Validate that all required config options are given
|
||||
if not validate_config({DOMAIN: config}, {DOMAIN: [CONF_HOST]}, _LOGGER):
|
||||
return False
|
||||
|
||||
# Default the entity_name to 'Samsung TV Remote'
|
||||
name = config.get(CONF_NAME, 'Samsung TV Remote')
|
||||
|
||||
# Generate a config for the Samsung lib
|
||||
remote_config = {
|
||||
"name": "HomeAssistant",
|
||||
"description": config.get(CONF_NAME, ''),
|
||||
"id": "ha.component.samsung",
|
||||
"port": config.get(CONF_PORT, 55000),
|
||||
"host": config.get(CONF_HOST),
|
||||
"timeout": config.get(CONF_TIMEOUT, 0),
|
||||
}
|
||||
|
||||
add_devices([SamsungTVDevice(name, remote_config)])
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class SamsungTVDevice(MediaPlayerDevice):
|
||||
""" Represents a Samsung TV. """
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
def __init__(self, name, config):
|
||||
from samsungctl import Remote
|
||||
# Save a reference to the imported class
|
||||
self._remote_class = Remote
|
||||
self._name = name
|
||||
# Assume that the TV is not muted
|
||||
self._muted = False
|
||||
# Assume that the TV is in Play mode
|
||||
self._playing = True
|
||||
self._state = STATE_UNKNOWN
|
||||
self._remote = None
|
||||
self._config = config
|
||||
|
||||
def update(self):
|
||||
# Send an empty key to see if we are still connected
|
||||
return self.send_key('KEY_POWER')
|
||||
|
||||
def get_remote(self):
|
||||
""" Creates or Returns a remote control instance """
|
||||
|
||||
if self._remote is None:
|
||||
# We need to create a new instance to reconnect.
|
||||
self._remote = self._remote_class(self._config)
|
||||
|
||||
return self._remote
|
||||
|
||||
def send_key(self, key):
|
||||
""" Sends a key to the tv and handles exceptions """
|
||||
try:
|
||||
self.get_remote().control(key)
|
||||
self._state = STATE_ON
|
||||
except (self._remote_class.UnhandledResponse,
|
||||
self._remote_class.AccessDenied, BrokenPipeError):
|
||||
# We got a response so it's on.
|
||||
# BrokenPipe can occur when the commands is sent to fast
|
||||
self._state = STATE_ON
|
||||
self._remote = None
|
||||
return False
|
||||
except (self._remote_class.ConnectionClosed, socket.timeout,
|
||||
TimeoutError, OSError):
|
||||
self._state = STATE_OFF
|
||||
self._remote = None
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def is_volume_muted(self):
|
||||
""" Boolean if volume is currently muted. """
|
||||
return self._muted
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
""" Flags of media commands that are supported. """
|
||||
return SUPPORT_SAMSUNGTV
|
||||
|
||||
def turn_off(self):
|
||||
""" turn_off media player. """
|
||||
self.send_key("KEY_POWEROFF")
|
||||
|
||||
def volume_up(self):
|
||||
""" volume_up media player. """
|
||||
self.send_key("KEY_VOLUP")
|
||||
|
||||
def volume_down(self):
|
||||
""" volume_down media player. """
|
||||
self.send_key("KEY_VOLDOWN")
|
||||
|
||||
def mute_volume(self, mute):
|
||||
self.send_key("KEY_MUTE")
|
||||
|
||||
def media_play_pause(self):
|
||||
""" Simulate play pause media player. """
|
||||
if self._playing:
|
||||
self.media_pause()
|
||||
else:
|
||||
self.media_play()
|
||||
|
||||
def media_play(self):
|
||||
""" media_play media player. """
|
||||
self._playing = True
|
||||
self.send_key("KEY_PLAY")
|
||||
|
||||
def media_pause(self):
|
||||
""" media_pause media player. """
|
||||
self._playing = False
|
||||
self.send_key("KEY_PAUSE")
|
||||
|
||||
def media_next_track(self):
|
||||
""" Send next track command. """
|
||||
self.send_key("KEY_FF")
|
||||
|
||||
def media_previous_track(self):
|
||||
self.send_key("KEY_REWIND")
|
||||
|
||||
def turn_on(self):
|
||||
""" turn the media player on. """
|
||||
self.send_key("KEY_POWERON")
|
|
@ -0,0 +1,85 @@
|
|||
"""
|
||||
homeassistant.components.media_player.snapcast
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Provides functionality to interact with Snapcast clients.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/media_player.snapcast/
|
||||
"""
|
||||
|
||||
import logging
|
||||
import socket
|
||||
|
||||
from homeassistant.const import (
|
||||
STATE_ON, STATE_OFF)
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
MediaPlayerDevice,
|
||||
SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE)
|
||||
|
||||
SUPPORT_SNAPCAST = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE
|
||||
DOMAIN = 'snapcast'
|
||||
REQUIREMENTS = ['snapcast==1.1.1']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the Snapcast platform. """
|
||||
import snapcast.control
|
||||
host = config.get('host')
|
||||
port = config.get('port', snapcast.control.CONTROL_PORT)
|
||||
if not host:
|
||||
_LOGGER.error('No snapserver host specified')
|
||||
return
|
||||
try:
|
||||
server = snapcast.control.Snapserver(host, port)
|
||||
except socket.gaierror:
|
||||
_LOGGER.error('Could not connect to Snapcast server at %s:%d',
|
||||
host, port)
|
||||
return
|
||||
add_devices([SnapcastDevice(client) for client in server.clients])
|
||||
|
||||
|
||||
class SnapcastDevice(MediaPlayerDevice):
|
||||
""" Represents a Snapcast client device. """
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Device name. """
|
||||
return self._client.identifier
|
||||
|
||||
@property
|
||||
def volume_level(self):
|
||||
""" Volume level. """
|
||||
return self._client.volume / 100
|
||||
|
||||
@property
|
||||
def is_volume_muted(self):
|
||||
""" Volume muted. """
|
||||
return self._client.muted
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
""" Flags of media commands that are supported. """
|
||||
return SUPPORT_SNAPCAST
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" State of the player. """
|
||||
if self._client.connected:
|
||||
return STATE_ON
|
||||
return STATE_OFF
|
||||
|
||||
def mute_volume(self, mute):
|
||||
""" Mute status. """
|
||||
self._client.muted = mute
|
||||
|
||||
def set_volume_level(self, volume):
|
||||
""" Volume level. """
|
||||
self._client.volume = round(volume * 100)
|
|
@ -27,7 +27,6 @@ SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \
|
|||
SUPPORT_SEEK | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the squeezebox platform. """
|
||||
if not config.get(CONF_HOST):
|
||||
|
@ -138,7 +137,7 @@ class LogitechMediaServer(object):
|
|||
class SqueezeBoxDevice(MediaPlayerDevice):
|
||||
""" Represents a SqueezeBox device. """
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
# pylint: disable=too-many-arguments, abstract-method
|
||||
def __init__(self, lms, player_id):
|
||||
super(SqueezeBoxDevice, self).__init__()
|
||||
self._lms = lms
|
||||
|
@ -292,7 +291,3 @@ class SqueezeBoxDevice(MediaPlayerDevice):
|
|||
""" turn the media player on. """
|
||||
self._lms.query(self._id, 'power', '1')
|
||||
self.update_ha_state()
|
||||
|
||||
def play_youtube(self, media_id):
|
||||
""" Plays a YouTube media. """
|
||||
raise NotImplementedError()
|
||||
|
|
|
@ -27,7 +27,7 @@ from homeassistant.components.media_player import (
|
|||
MediaPlayerDevice, DOMAIN,
|
||||
SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
|
||||
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
|
||||
SERVICE_PLAY_MEDIA, SERVICE_YOUTUBE_VIDEO,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
ATTR_SUPPORTED_MEDIA_COMMANDS, ATTR_MEDIA_VOLUME_MUTED,
|
||||
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_DURATION,
|
||||
ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST, ATTR_MEDIA_ALBUM_NAME,
|
||||
|
@ -215,15 +215,6 @@ class UniversalMediaPlayer(MediaPlayerDevice):
|
|||
else:
|
||||
return None
|
||||
|
||||
def _cache_active_child_state(self):
|
||||
""" The state of the active child or None """
|
||||
for child_name in self._children:
|
||||
child_state = self.hass.states.get(child_name)
|
||||
if child_state and child_state.state not in OFF_STATES:
|
||||
self._child_state = child_state
|
||||
return
|
||||
self._child_state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" name of universal player """
|
||||
|
@ -406,11 +397,6 @@ class UniversalMediaPlayer(MediaPlayerDevice):
|
|||
data = {ATTR_MEDIA_SEEK_POSITION: position}
|
||||
self._call_service(SERVICE_MEDIA_SEEK, data)
|
||||
|
||||
def play_youtube(self, media_id):
|
||||
""" Plays a YouTube media. """
|
||||
data = {'media_id': media_id}
|
||||
self._call_service(SERVICE_YOUTUBE_VIDEO, data)
|
||||
|
||||
def play_media(self, media_type, media_id):
|
||||
""" Plays a piece of media. """
|
||||
data = {'media_type': media_type, 'media_id': media_id}
|
||||
|
|
|
@ -12,8 +12,10 @@ import socket
|
|||
import time
|
||||
|
||||
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
import homeassistant.util as util
|
||||
from homeassistant.util import template
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||
|
@ -49,24 +51,34 @@ DEFAULT_PROTOCOL = PROTOCOL_311
|
|||
|
||||
ATTR_TOPIC = 'topic'
|
||||
ATTR_PAYLOAD = 'payload'
|
||||
ATTR_PAYLOAD_TEMPLATE = 'payload_template'
|
||||
ATTR_QOS = 'qos'
|
||||
ATTR_RETAIN = 'retain'
|
||||
|
||||
MAX_RECONNECT_WAIT = 300 # seconds
|
||||
|
||||
|
||||
def publish(hass, topic, payload, qos=None, retain=None):
|
||||
"""Publish message to an MQTT topic."""
|
||||
data = {
|
||||
ATTR_TOPIC: topic,
|
||||
ATTR_PAYLOAD: payload,
|
||||
}
|
||||
def _build_publish_data(topic, qos, retain):
|
||||
"""Build the arguments for the publish service without the payload."""
|
||||
data = {ATTR_TOPIC: topic}
|
||||
if qos is not None:
|
||||
data[ATTR_QOS] = qos
|
||||
|
||||
if retain is not None:
|
||||
data[ATTR_RETAIN] = retain
|
||||
return data
|
||||
|
||||
|
||||
def publish(hass, topic, payload, qos=None, retain=None):
|
||||
"""Publish message to an MQTT topic."""
|
||||
data = _build_publish_data(topic, qos, retain)
|
||||
data[ATTR_PAYLOAD] = payload
|
||||
hass.services.call(DOMAIN, SERVICE_PUBLISH, data)
|
||||
|
||||
|
||||
def publish_template(hass, topic, payload_template, qos=None, retain=None):
|
||||
"""Publish message to an MQTT topic using a template payload."""
|
||||
data = _build_publish_data(topic, qos, retain)
|
||||
data[ATTR_PAYLOAD_TEMPLATE] = payload_template
|
||||
hass.services.call(DOMAIN, SERVICE_PUBLISH, data)
|
||||
|
||||
|
||||
|
@ -132,15 +144,34 @@ def setup(hass, config):
|
|||
"""Handle MQTT publish service calls."""
|
||||
msg_topic = call.data.get(ATTR_TOPIC)
|
||||
payload = call.data.get(ATTR_PAYLOAD)
|
||||
payload_template = call.data.get(ATTR_PAYLOAD_TEMPLATE)
|
||||
qos = call.data.get(ATTR_QOS, DEFAULT_QOS)
|
||||
retain = call.data.get(ATTR_RETAIN, DEFAULT_RETAIN)
|
||||
if payload is None:
|
||||
if payload_template is None:
|
||||
_LOGGER.error(
|
||||
"You must set either '%s' or '%s' to use this service",
|
||||
ATTR_PAYLOAD, ATTR_PAYLOAD_TEMPLATE)
|
||||
return
|
||||
try:
|
||||
payload = template.render(hass, payload_template)
|
||||
except template.jinja2.TemplateError as exc:
|
||||
_LOGGER.error(
|
||||
"Unable to publish to '%s': rendering payload template of "
|
||||
"'%s' failed because %s.",
|
||||
msg_topic, payload_template, exc)
|
||||
return
|
||||
if msg_topic is None or payload is None:
|
||||
return
|
||||
MQTT_CLIENT.publish(msg_topic, payload, qos, retain)
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mqtt)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_PUBLISH, publish_service)
|
||||
descriptions = load_yaml_config_file(
|
||||
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_PUBLISH, publish_service,
|
||||
descriptions.get(SERVICE_PUBLISH))
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
publish:
|
||||
description: Publish a message to an MQTT topic
|
||||
|
||||
fields:
|
||||
topic:
|
||||
description: Topic to publish payload
|
||||
example: /homeassistant/hello
|
||||
|
||||
payload:
|
||||
description: Payload to publish
|
||||
example: This is great
|
||||
|
||||
payload_template:
|
||||
description: Template to render as payload value. Ignored if payload given.
|
||||
example: "{{ states('sensor.temperature') }}"
|
||||
|
||||
qos:
|
||||
description: Quality of Service
|
||||
example: 2
|
||||
values:
|
||||
- 0
|
||||
- 1
|
||||
- 2
|
||||
default: 0
|
||||
|
||||
retain:
|
||||
description: If message should have the retain flag set.
|
||||
example: true
|
||||
default: false
|
|
@ -11,6 +11,7 @@ from homeassistant.core import EventOrigin, State
|
|||
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
|
||||
from homeassistant.components.mqtt import SERVICE_PUBLISH as MQTT_SVC_PUBLISH
|
||||
from homeassistant.const import (
|
||||
ATTR_SERVICE_DATA,
|
||||
MATCH_ALL,
|
||||
EVENT_TIME_CHANGED,
|
||||
EVENT_CALL_SERVICE,
|
||||
|
@ -46,7 +47,7 @@ def setup(hass, config):
|
|||
if (
|
||||
event.data.get('domain') == MQTT_DOMAIN and
|
||||
event.data.get('service') == MQTT_SVC_PUBLISH and
|
||||
event.data.get('topic') == pub_topic
|
||||
event.data[ATTR_SERVICE_DATA].get('topic') == pub_topic
|
||||
):
|
||||
return
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""
|
||||
homeassistant.components.mysensors
|
||||
homeassistant.components.mysensors.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
MySensors component that connects to a MySensors gateway via pymysensors
|
||||
API.
|
||||
|
@ -24,13 +25,16 @@ CONF_DEBUG = 'debug'
|
|||
CONF_PERSISTENCE = 'persistence'
|
||||
CONF_PERSISTENCE_FILE = 'persistence_file'
|
||||
CONF_VERSION = 'version'
|
||||
CONF_BAUD_RATE = 'baud_rate'
|
||||
CONF_OPTIMISTIC = 'optimistic'
|
||||
DEFAULT_VERSION = '1.4'
|
||||
DEFAULT_BAUD_RATE = 115200
|
||||
|
||||
DOMAIN = 'mysensors'
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = [
|
||||
'https://github.com/theolind/pymysensors/archive/'
|
||||
'005bff4c5ca7a56acd30e816bc3bcdb5cb2d46fd.zip#pymysensors==0.4']
|
||||
'f0c928532167fb24823efa793ec21ca646fd37a6.zip#pymysensors==0.5']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
ATTR_NODE_ID = 'node_id'
|
||||
ATTR_CHILD_ID = 'child_id'
|
||||
|
@ -38,13 +42,15 @@ ATTR_PORT = 'port'
|
|||
|
||||
GATEWAYS = None
|
||||
|
||||
DISCOVER_SENSORS = "mysensors.sensors"
|
||||
DISCOVER_SWITCHES = "mysensors.switches"
|
||||
DISCOVER_SENSORS = 'mysensors.sensors'
|
||||
DISCOVER_SWITCHES = 'mysensors.switches'
|
||||
DISCOVER_LIGHTS = 'mysensors.lights'
|
||||
|
||||
# Maps discovered services to their platforms
|
||||
DISCOVERY_COMPONENTS = [
|
||||
('sensor', DISCOVER_SENSORS),
|
||||
('switch', DISCOVER_SWITCHES),
|
||||
('light', DISCOVER_LIGHTS),
|
||||
]
|
||||
|
||||
|
||||
|
@ -54,21 +60,28 @@ def setup(hass, config):
|
|||
{DOMAIN: [CONF_GATEWAYS]},
|
||||
_LOGGER):
|
||||
return False
|
||||
if not all(CONF_PORT in gateway
|
||||
for gateway in config[DOMAIN][CONF_GATEWAYS]):
|
||||
_LOGGER.error('Missing required configuration items '
|
||||
'in %s: %s', DOMAIN, CONF_PORT)
|
||||
return False
|
||||
|
||||
import mysensors.mysensors as mysensors
|
||||
|
||||
version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION))
|
||||
is_metric = (hass.config.temperature_unit == TEMP_CELCIUS)
|
||||
|
||||
def setup_gateway(port, persistence, persistence_file, version):
|
||||
def setup_gateway(port, persistence, persistence_file, version, baud_rate):
|
||||
"""Return gateway after setup of the gateway."""
|
||||
gateway = mysensors.SerialGateway(port, event_callback=None,
|
||||
persistence=persistence,
|
||||
persistence_file=persistence_file,
|
||||
protocol_version=version)
|
||||
protocol_version=version,
|
||||
baud=baud_rate)
|
||||
gateway.metric = is_metric
|
||||
gateway.debug = config[DOMAIN].get(CONF_DEBUG, False)
|
||||
gateway = GatewayWrapper(gateway, version)
|
||||
optimistic = config[DOMAIN].get(CONF_OPTIMISTIC, False)
|
||||
gateway = GatewayWrapper(gateway, version, optimistic)
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
gateway.event_callback = gateway.callback_factory()
|
||||
|
||||
|
@ -98,8 +111,9 @@ def setup(hass, config):
|
|||
persistence_file = gway.get(
|
||||
CONF_PERSISTENCE_FILE,
|
||||
hass.config.path('mysensors{}.pickle'.format(index + 1)))
|
||||
baud_rate = gway.get(CONF_BAUD_RATE, DEFAULT_BAUD_RATE)
|
||||
GATEWAYS[port] = setup_gateway(
|
||||
port, persistence, persistence_file, version)
|
||||
port, persistence, persistence_file, version, baud_rate)
|
||||
|
||||
for (component, discovery_service) in DISCOVERY_COMPONENTS:
|
||||
# Ensure component is loaded
|
||||
|
@ -145,12 +159,13 @@ def pf_callback_factory(map_sv_types, devices, add_devices, entity_class):
|
|||
class GatewayWrapper(object):
|
||||
"""Gateway wrapper class, by subclassing serial gateway."""
|
||||
|
||||
def __init__(self, gateway, version):
|
||||
def __init__(self, gateway, version, optimistic):
|
||||
"""Setup class attributes on instantiation.
|
||||
|
||||
Args:
|
||||
gateway (mysensors.SerialGateway): Gateway to wrap.
|
||||
version (str): Version of mysensors API.
|
||||
optimistic (bool): Send values to actuators without feedback state.
|
||||
|
||||
Attributes:
|
||||
_wrapped_gateway (mysensors.SerialGateway): Wrapped gateway.
|
||||
|
@ -163,6 +178,7 @@ class GatewayWrapper(object):
|
|||
self.version = version
|
||||
self.platform_callbacks = []
|
||||
self.const = self.get_const()
|
||||
self.optimistic = optimistic
|
||||
self.__initialised = True
|
||||
|
||||
def __getattr__(self, name):
|
||||
|
@ -195,7 +211,7 @@ class GatewayWrapper(object):
|
|||
"""Return a new callback function."""
|
||||
def node_update(update_type, node_id):
|
||||
"""Callback for node updates from the MySensors gateway."""
|
||||
_LOGGER.info('update %s: node %s', update_type, node_id)
|
||||
_LOGGER.debug('update %s: node %s', update_type, node_id)
|
||||
for callback in self.platform_callbacks:
|
||||
callback(self, node_id)
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
"""
|
||||
homeassistant.components.notify.rest
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
REST platform for notify component.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/notify.rest/
|
||||
"""
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.components.notify import (
|
||||
DOMAIN, ATTR_TITLE, ATTR_TARGET, BaseNotificationService)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_METHOD = 'GET'
|
||||
DEFAULT_MESSAGE_PARAM_NAME = 'message'
|
||||
DEFAULT_TITLE_PARAM_NAME = None
|
||||
DEFAULT_TARGET_PARAM_NAME = None
|
||||
|
||||
|
||||
def get_service(hass, config):
|
||||
""" Get the REST notification service. """
|
||||
|
||||
if not validate_config({DOMAIN: config},
|
||||
{DOMAIN: ['resource', ]},
|
||||
_LOGGER):
|
||||
return None
|
||||
|
||||
method = config.get('method', DEFAULT_METHOD)
|
||||
message_param_name = config.get('message_param_name',
|
||||
DEFAULT_MESSAGE_PARAM_NAME)
|
||||
title_param_name = config.get('title_param_name',
|
||||
DEFAULT_TITLE_PARAM_NAME)
|
||||
target_param_name = config.get('target_param_name',
|
||||
DEFAULT_TARGET_PARAM_NAME)
|
||||
|
||||
return RestNotificationService(config['resource'], method,
|
||||
message_param_name, title_param_name,
|
||||
target_param_name)
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods, too-many-arguments
|
||||
class RestNotificationService(BaseNotificationService):
|
||||
""" Implements notification service for REST. """
|
||||
|
||||
def __init__(self, resource, method, message_param_name,
|
||||
title_param_name, target_param_name):
|
||||
self._resource = resource
|
||||
self._method = method.upper()
|
||||
self._message_param_name = message_param_name
|
||||
self._title_param_name = title_param_name
|
||||
self._target_param_name = target_param_name
|
||||
|
||||
def send_message(self, message="", **kwargs):
|
||||
""" Send a message to a user. """
|
||||
|
||||
data = {
|
||||
self._message_param_name: message
|
||||
}
|
||||
|
||||
if self._title_param_name is not None:
|
||||
data[self._title_param_name] = kwargs.get(ATTR_TITLE)
|
||||
|
||||
if self._target_param_name is not None:
|
||||
data[self._title_param_name] = kwargs.get(ATTR_TARGET)
|
||||
|
||||
if self._method == 'POST':
|
||||
response = requests.post(self._resource, data=data, timeout=10)
|
||||
elif self._method == 'POST_JSON':
|
||||
response = requests.post(self._resource, json=data, timeout=10)
|
||||
else: # default GET
|
||||
response = requests.get(self._resource, params=data, timeout=10)
|
||||
|
||||
if response.status_code not in (200, 201):
|
||||
_LOGGER.exception(
|
||||
"Error sending message. Response %d: %s:",
|
||||
response.status_code, response.reason)
|
|
@ -0,0 +1,238 @@
|
|||
"""
|
||||
homeassistant.components.proximity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Component to monitor the proximity of devices to a particular zone and the
|
||||
direction of travel.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/proximity/
|
||||
"""
|
||||
import logging
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util.location import distance
|
||||
|
||||
DEPENDENCIES = ['zone', 'device_tracker']
|
||||
|
||||
DOMAIN = 'proximity'
|
||||
|
||||
# Default tolerance
|
||||
DEFAULT_TOLERANCE = 1
|
||||
|
||||
# Default zone
|
||||
DEFAULT_PROXIMITY_ZONE = 'home'
|
||||
|
||||
# Entity attributes
|
||||
ATTR_DIST_FROM = 'dist_to_zone'
|
||||
ATTR_DIR_OF_TRAVEL = 'dir_of_travel'
|
||||
ATTR_NEAREST = 'nearest'
|
||||
ATTR_FRIENDLY_NAME = 'friendly_name'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(hass, config): # pylint: disable=too-many-locals,too-many-statements
|
||||
""" Get the zones and offsets from configuration.yaml. """
|
||||
ignored_zones = []
|
||||
if 'ignored_zones' in config[DOMAIN]:
|
||||
for variable in config[DOMAIN]['ignored_zones']:
|
||||
ignored_zones.append(variable)
|
||||
|
||||
# Get the devices from configuration.yaml
|
||||
if 'devices' not in config[DOMAIN]:
|
||||
_LOGGER.error('devices not found in config')
|
||||
return False
|
||||
|
||||
proximity_devices = []
|
||||
for variable in config[DOMAIN]['devices']:
|
||||
proximity_devices.append(variable)
|
||||
|
||||
# Get the direction of travel tolerance from configuration.yaml
|
||||
tolerance = config[DOMAIN].get('tolerance', DEFAULT_TOLERANCE)
|
||||
|
||||
# Get the zone to monitor proximity to from configuration.yaml
|
||||
proximity_zone = config[DOMAIN].get('zone', DEFAULT_PROXIMITY_ZONE)
|
||||
|
||||
entity_id = DOMAIN + '.' + proximity_zone
|
||||
proximity_zone = 'zone.' + proximity_zone
|
||||
|
||||
state = hass.states.get(proximity_zone)
|
||||
zone_friendly_name = (state.name).lower()
|
||||
|
||||
# set the default values
|
||||
dist_to_zone = 'not set'
|
||||
dir_of_travel = 'not set'
|
||||
nearest = 'not set'
|
||||
|
||||
proximity = Proximity(hass, zone_friendly_name, dist_to_zone,
|
||||
dir_of_travel, nearest, ignored_zones,
|
||||
proximity_devices, tolerance, proximity_zone)
|
||||
proximity.entity_id = entity_id
|
||||
|
||||
proximity.update_ha_state()
|
||||
|
||||
# Main command to monitor proximity of devices
|
||||
track_state_change(hass, proximity_devices,
|
||||
proximity.check_proximity_state_change)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Proximity(Entity): # pylint: disable=too-many-instance-attributes
|
||||
""" Represents a Proximity. """
|
||||
def __init__(self, hass, zone_friendly_name, dist_to, dir_of_travel,
|
||||
nearest, ignored_zones, proximity_devices, tolerance,
|
||||
proximity_zone):
|
||||
# pylint: disable=too-many-arguments
|
||||
self.hass = hass
|
||||
self.friendly_name = zone_friendly_name
|
||||
self.dist_to = dist_to
|
||||
self.dir_of_travel = dir_of_travel
|
||||
self.nearest = nearest
|
||||
self.ignored_zones = ignored_zones
|
||||
self.proximity_devices = proximity_devices
|
||||
self.tolerance = tolerance
|
||||
self.proximity_zone = proximity_zone
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state. """
|
||||
return self.dist_to
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Unit of measurement of this entity. """
|
||||
return "km"
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Returns the state attributes. """
|
||||
return {
|
||||
ATTR_DIR_OF_TRAVEL: self.dir_of_travel,
|
||||
ATTR_NEAREST: self.nearest,
|
||||
ATTR_FRIENDLY_NAME: self.friendly_name
|
||||
}
|
||||
|
||||
def check_proximity_state_change(self, entity, old_state, new_state):
|
||||
# pylint: disable=too-many-branches,too-many-statements,too-many-locals
|
||||
""" Function to perform the proximity checking. """
|
||||
entity_name = new_state.name
|
||||
devices_to_calculate = False
|
||||
devices_in_zone = ''
|
||||
|
||||
zone_state = self.hass.states.get(self.proximity_zone)
|
||||
proximity_latitude = zone_state.attributes.get('latitude')
|
||||
proximity_longitude = zone_state.attributes.get('longitude')
|
||||
|
||||
# Check for devices in the monitored zone
|
||||
for device in self.proximity_devices:
|
||||
device_state = self.hass.states.get(device)
|
||||
|
||||
if device_state.state not in self.ignored_zones:
|
||||
devices_to_calculate = True
|
||||
|
||||
# Check the location of all devices
|
||||
if (device_state.state).lower() == (self.friendly_name).lower():
|
||||
device_friendly = device_state.name
|
||||
if devices_in_zone != '':
|
||||
devices_in_zone = devices_in_zone + ', '
|
||||
devices_in_zone = devices_in_zone + device_friendly
|
||||
|
||||
# No-one to track so reset the entity
|
||||
if not devices_to_calculate:
|
||||
self.dist_to = 'not set'
|
||||
self.dir_of_travel = 'not set'
|
||||
self.nearest = 'not set'
|
||||
self.update_ha_state()
|
||||
return
|
||||
|
||||
# At least one device is in the monitored zone so update the entity
|
||||
if devices_in_zone != '':
|
||||
self.dist_to = 0
|
||||
self.dir_of_travel = 'arrived'
|
||||
self.nearest = devices_in_zone
|
||||
self.update_ha_state()
|
||||
return
|
||||
|
||||
# We can't check proximity because latitude and longitude don't exist
|
||||
if 'latitude' not in new_state.attributes:
|
||||
return
|
||||
|
||||
# Collect distances to the zone for all devices
|
||||
distances_to_zone = {}
|
||||
for device in self.proximity_devices:
|
||||
# Ignore devices in an ignored zone
|
||||
device_state = self.hass.states.get(device)
|
||||
if device_state.state in self.ignored_zones:
|
||||
continue
|
||||
|
||||
# Ignore devices if proximity cannot be calculated
|
||||
if 'latitude' not in device_state.attributes:
|
||||
continue
|
||||
|
||||
# Calculate the distance to the proximity zone
|
||||
dist_to_zone = distance(proximity_latitude,
|
||||
proximity_longitude,
|
||||
device_state.attributes['latitude'],
|
||||
device_state.attributes['longitude'])
|
||||
|
||||
# Add the device and distance to a dictionary
|
||||
distances_to_zone[device] = round(dist_to_zone / 1000, 1)
|
||||
|
||||
# Loop through each of the distances collected and work out the closest
|
||||
closest_device = ''
|
||||
dist_to_zone = 1000000
|
||||
|
||||
for device in distances_to_zone:
|
||||
if distances_to_zone[device] < dist_to_zone:
|
||||
closest_device = device
|
||||
dist_to_zone = distances_to_zone[device]
|
||||
|
||||
# If the closest device is one of the other devices
|
||||
if closest_device != entity:
|
||||
self.dist_to = round(distances_to_zone[closest_device])
|
||||
self.dir_of_travel = 'unknown'
|
||||
device_state = self.hass.states.get(closest_device)
|
||||
self.nearest = device_state.name
|
||||
self.update_ha_state()
|
||||
return
|
||||
|
||||
# Stop if we cannot calculate the direction of travel (i.e. we don't
|
||||
# have a previous state and a current LAT and LONG)
|
||||
if old_state is None or 'latitude' not in old_state.attributes:
|
||||
self.dist_to = round(distances_to_zone[entity])
|
||||
self.dir_of_travel = 'unknown'
|
||||
self.nearest = entity_name
|
||||
self.update_ha_state()
|
||||
return
|
||||
|
||||
# Reset the variables
|
||||
distance_travelled = 0
|
||||
|
||||
# Calculate the distance travelled
|
||||
old_distance = distance(proximity_latitude, proximity_longitude,
|
||||
old_state.attributes['latitude'],
|
||||
old_state.attributes['longitude'])
|
||||
new_distance = distance(proximity_latitude, proximity_longitude,
|
||||
new_state.attributes['latitude'],
|
||||
new_state.attributes['longitude'])
|
||||
distance_travelled = round(new_distance - old_distance, 1)
|
||||
|
||||
# Check for tolerance
|
||||
if distance_travelled < self.tolerance * -1:
|
||||
direction_of_travel = 'towards'
|
||||
elif distance_travelled > self.tolerance:
|
||||
direction_of_travel = 'away_from'
|
||||
else:
|
||||
direction_of_travel = 'stationary'
|
||||
|
||||
# Update the proximity entity
|
||||
self.dist_to = round(dist_to_zone)
|
||||
self.dir_of_travel = direction_of_travel
|
||||
self.nearest = entity_name
|
||||
self.update_ha_state()
|
||||
_LOGGER.debug('proximity.%s update entity: distance=%s: direction=%s: '
|
||||
'device=%s', self.friendly_name, round(dist_to_zone),
|
||||
direction_of_travel, entity_name)
|
||||
|
||||
_LOGGER.info('%s: proximity calculation complete', entity_name)
|
|
@ -226,22 +226,28 @@ class Recorder(threading.Thread):
|
|||
# State got deleted
|
||||
if state is None:
|
||||
state_state = ''
|
||||
state_domain = ''
|
||||
state_attr = '{}'
|
||||
last_changed = last_updated = now
|
||||
else:
|
||||
state_domain = state.domain
|
||||
state_state = state.state
|
||||
state_attr = json.dumps(state.attributes)
|
||||
state_attr = json.dumps(dict(state.attributes))
|
||||
last_changed = state.last_changed
|
||||
last_updated = state.last_updated
|
||||
|
||||
info = (
|
||||
entity_id, state_state, state_attr, last_changed, last_updated,
|
||||
entity_id, state_domain, state_state, state_attr,
|
||||
last_changed, last_updated,
|
||||
now, self.utc_offset, event_id)
|
||||
|
||||
self.query(
|
||||
"INSERT INTO states ("
|
||||
"entity_id, state, attributes, last_changed, last_updated,"
|
||||
"created, utc_offset, event_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"""
|
||||
INSERT INTO states (
|
||||
entity_id, domain, state, attributes, last_changed, last_updated,
|
||||
created, utc_offset, event_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
info)
|
||||
|
||||
def record_event(self, event):
|
||||
|
@ -279,7 +285,8 @@ class Recorder(threading.Thread):
|
|||
else:
|
||||
return cur.fetchall()
|
||||
|
||||
except sqlite3.IntegrityError:
|
||||
except (sqlite3.IntegrityError, sqlite3.OperationalError,
|
||||
sqlite3.ProgrammingError):
|
||||
_LOGGER.exception(
|
||||
"Error querying the database using: %s", sql_query)
|
||||
return []
|
||||
|
@ -323,7 +330,7 @@ class Recorder(threading.Thread):
|
|||
migration_id = 0
|
||||
|
||||
if migration_id < 1:
|
||||
self.query("""
|
||||
cur.execute("""
|
||||
CREATE TABLE recorder_runs (
|
||||
run_id integer primary key,
|
||||
start integer,
|
||||
|
@ -332,7 +339,7 @@ class Recorder(threading.Thread):
|
|||
created integer)
|
||||
""")
|
||||
|
||||
self.query("""
|
||||
cur.execute("""
|
||||
CREATE TABLE events (
|
||||
event_id integer primary key,
|
||||
event_type text,
|
||||
|
@ -340,10 +347,10 @@ class Recorder(threading.Thread):
|
|||
origin text,
|
||||
created integer)
|
||||
""")
|
||||
self.query(
|
||||
cur.execute(
|
||||
'CREATE INDEX events__event_type ON events(event_type)')
|
||||
|
||||
self.query("""
|
||||
cur.execute("""
|
||||
CREATE TABLE states (
|
||||
state_id integer primary key,
|
||||
entity_id text,
|
||||
|
@ -353,57 +360,90 @@ class Recorder(threading.Thread):
|
|||
last_updated integer,
|
||||
created integer)
|
||||
""")
|
||||
self.query('CREATE INDEX states__entity_id ON states(entity_id)')
|
||||
cur.execute('CREATE INDEX states__entity_id ON states(entity_id)')
|
||||
|
||||
save_migration(1)
|
||||
|
||||
if migration_id < 2:
|
||||
self.query("""
|
||||
cur.execute("""
|
||||
ALTER TABLE events
|
||||
ADD COLUMN time_fired integer
|
||||
""")
|
||||
|
||||
self.query('UPDATE events SET time_fired=created')
|
||||
cur.execute('UPDATE events SET time_fired=created')
|
||||
|
||||
save_migration(2)
|
||||
|
||||
if migration_id < 3:
|
||||
utc_offset = self.utc_offset
|
||||
|
||||
self.query("""
|
||||
cur.execute("""
|
||||
ALTER TABLE recorder_runs
|
||||
ADD COLUMN utc_offset integer
|
||||
""")
|
||||
|
||||
self.query("""
|
||||
cur.execute("""
|
||||
ALTER TABLE events
|
||||
ADD COLUMN utc_offset integer
|
||||
""")
|
||||
|
||||
self.query("""
|
||||
cur.execute("""
|
||||
ALTER TABLE states
|
||||
ADD COLUMN utc_offset integer
|
||||
""")
|
||||
|
||||
self.query("UPDATE recorder_runs SET utc_offset=?", [utc_offset])
|
||||
self.query("UPDATE events SET utc_offset=?", [utc_offset])
|
||||
self.query("UPDATE states SET utc_offset=?", [utc_offset])
|
||||
cur.execute("UPDATE recorder_runs SET utc_offset=?", [utc_offset])
|
||||
cur.execute("UPDATE events SET utc_offset=?", [utc_offset])
|
||||
cur.execute("UPDATE states SET utc_offset=?", [utc_offset])
|
||||
|
||||
save_migration(3)
|
||||
|
||||
if migration_id < 4:
|
||||
# We had a bug where we did not save utc offset for recorder runs
|
||||
self.query(
|
||||
cur.execute(
|
||||
"""UPDATE recorder_runs SET utc_offset=?
|
||||
WHERE utc_offset IS NULL""", [self.utc_offset])
|
||||
|
||||
self.query("""
|
||||
cur.execute("""
|
||||
ALTER TABLE states
|
||||
ADD COLUMN event_id integer
|
||||
""")
|
||||
|
||||
save_migration(4)
|
||||
|
||||
if migration_id < 5:
|
||||
# Add domain so that thermostat graphs look right
|
||||
try:
|
||||
cur.execute("""
|
||||
ALTER TABLE states
|
||||
ADD COLUMN domain text
|
||||
""")
|
||||
except sqlite3.OperationalError:
|
||||
# We had a bug in this migration for a while on dev
|
||||
# Without this, dev-users will have to throw away their db
|
||||
pass
|
||||
|
||||
# TravisCI has Python compiled against an old version of SQLite3
|
||||
# which misses the instr method.
|
||||
self.conn.create_function(
|
||||
"instr", 2,
|
||||
lambda string, substring: string.find(substring) + 1)
|
||||
|
||||
# populate domain with defaults
|
||||
cur.execute("""
|
||||
UPDATE states
|
||||
set domain=substr(entity_id, 0, instr(entity_id, '.'))
|
||||
""")
|
||||
|
||||
# add indexes we are going to use a lot on selects
|
||||
cur.execute("""
|
||||
CREATE INDEX states__state_changes ON
|
||||
states (last_changed, last_updated, entity_id)""")
|
||||
cur.execute("""
|
||||
CREATE INDEX states__significant_changes ON
|
||||
states (domain, last_updated, entity_id)""")
|
||||
save_migration(5)
|
||||
|
||||
def _close_connection(self):
|
||||
""" Close connection to the database. """
|
||||
_LOGGER.info("Closing database")
|
||||
|
|
|
@ -9,8 +9,8 @@ https://home-assistant.io/components/rfxtrx/
|
|||
import logging
|
||||
from homeassistant.util import slugify
|
||||
|
||||
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/0.2.zip' +
|
||||
'#RFXtrx==0.2']
|
||||
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/0.4.zip' +
|
||||
'#RFXtrx==0.4']
|
||||
|
||||
DOMAIN = "rfxtrx"
|
||||
|
||||
|
@ -37,6 +37,8 @@ def setup(hass, config):
|
|||
""" Callback all subscribers for RFXtrx gateway. """
|
||||
|
||||
# Log RFXCOM event
|
||||
if not event.device.id_string:
|
||||
return
|
||||
entity_id = slugify(event.device.id_string.lower())
|
||||
packet_id = "".join("{0:02x}".format(x) for x in event.data)
|
||||
entity_name = "%s : %s" % (entity_id, packet_id)
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
"""
|
||||
homeassistant.components.rollershutter.scsgate
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Allows to configure a SCSGate rollershutter.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/rollershutter.scsgate/
|
||||
"""
|
||||
import logging
|
||||
import homeassistant.components.scsgate as scsgate
|
||||
from homeassistant.components.rollershutter import RollershutterDevice
|
||||
|
||||
|
||||
DEPENDENCIES = ['scsgate']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
""" Add the SCSGate swiches defined inside of the configuration file. """
|
||||
|
||||
devices = config.get('devices')
|
||||
rollershutters = []
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if devices:
|
||||
for _, entity_info in devices.items():
|
||||
if entity_info['scs_id'] in scsgate.SCSGATE.devices:
|
||||
continue
|
||||
|
||||
logger.info("Adding %s scsgate.rollershutter", entity_info['name'])
|
||||
|
||||
name = entity_info['name']
|
||||
scs_id = entity_info['scs_id']
|
||||
rollershutter = SCSGateRollerShutter(
|
||||
name=name,
|
||||
scs_id=scs_id,
|
||||
logger=logger)
|
||||
scsgate.SCSGATE.add_device(rollershutter)
|
||||
rollershutters.append(rollershutter)
|
||||
|
||||
add_devices_callback(rollershutters)
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||
class SCSGateRollerShutter(RollershutterDevice):
|
||||
""" Represents a rollershutter that can be controlled using SCSGate. """
|
||||
def __init__(self, scs_id, name, logger):
|
||||
self._scs_id = scs_id
|
||||
self._name = name
|
||||
self._logger = logger
|
||||
|
||||
@property
|
||||
def scs_id(self):
|
||||
""" SCSGate ID """
|
||||
return self._scs_id
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" No polling needed """
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" The name of the rollershutter. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def current_position(self):
|
||||
"""
|
||||
Return current position of rollershutter.
|
||||
None is unknown, 0 is closed, 100 is fully open.
|
||||
"""
|
||||
return None
|
||||
|
||||
def move_up(self, **kwargs):
|
||||
""" Move the rollershutter up. """
|
||||
from scsgate.tasks import RaiseRollerShutterTask
|
||||
|
||||
scsgate.SCSGATE.append_task(
|
||||
RaiseRollerShutterTask(target=self._scs_id))
|
||||
|
||||
def move_down(self, **kwargs):
|
||||
""" Move the rollershutter down. """
|
||||
from scsgate.tasks import LowerRollerShutterTask
|
||||
|
||||
scsgate.SCSGATE.append_task(
|
||||
LowerRollerShutterTask(target=self._scs_id))
|
||||
|
||||
def stop(self, **kwargs):
|
||||
""" Stop the device. """
|
||||
from scsgate.tasks import HaltRollerShutterTask
|
||||
|
||||
scsgate.SCSGATE.append_task(HaltRollerShutterTask(target=self._scs_id))
|
||||
|
||||
def process_event(self, message):
|
||||
""" Handle a SCSGate message related with this rollershutter """
|
||||
self._logger.debug(
|
||||
"Rollershutter %s, got message %s",
|
||||
self._scs_id, message.toggled)
|
|
@ -0,0 +1,156 @@
|
|||
"""
|
||||
homeassistant.components.scsgate
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Provides support for SCSGate components.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/scsgate/
|
||||
"""
|
||||
import logging
|
||||
from threading import Lock
|
||||
from homeassistant.core import EVENT_HOMEASSISTANT_STOP
|
||||
|
||||
REQUIREMENTS = ['scsgate==0.1.0']
|
||||
DOMAIN = "scsgate"
|
||||
SCSGATE = None
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SCSGate:
|
||||
""" Class dealing with the SCSGate device via scsgate.Reactor. """
|
||||
|
||||
def __init__(self, device, logger):
|
||||
self._logger = logger
|
||||
self._devices = {}
|
||||
self._devices_to_register = {}
|
||||
self._devices_to_register_lock = Lock()
|
||||
self._device_being_registered = None
|
||||
self._device_being_registered_lock = Lock()
|
||||
|
||||
from scsgate.connection import Connection
|
||||
connection = Connection(device=device, logger=self._logger)
|
||||
|
||||
from scsgate.reactor import Reactor
|
||||
self._reactor = Reactor(
|
||||
connection=connection,
|
||||
logger=self._logger,
|
||||
handle_message=self.handle_message)
|
||||
|
||||
def handle_message(self, message):
|
||||
""" Method called whenever a message is seen on the bus. """
|
||||
from scsgate.messages import StateMessage, ScenarioTriggeredMessage
|
||||
|
||||
self._logger.debug("Received message {}".format(message))
|
||||
if not isinstance(message, StateMessage) and \
|
||||
not isinstance(message, ScenarioTriggeredMessage):
|
||||
msg = "Ignored message {} - not releavant type".format(
|
||||
message)
|
||||
self._logger.debug(msg)
|
||||
return
|
||||
|
||||
if message.entity in self._devices:
|
||||
new_device_activated = False
|
||||
with self._devices_to_register_lock:
|
||||
if message.entity == self._device_being_registered:
|
||||
self._device_being_registered = None
|
||||
new_device_activated = True
|
||||
if new_device_activated:
|
||||
self._activate_next_device()
|
||||
|
||||
# pylint: disable=broad-except
|
||||
try:
|
||||
self._devices[message.entity].process_event(message)
|
||||
except Exception as exception:
|
||||
msg = "Exception while processing event: {}".format(
|
||||
exception)
|
||||
self._logger.error(msg)
|
||||
else:
|
||||
self._logger.info(
|
||||
"Ignoring state message for device {} because unknonw".format(
|
||||
message.entity))
|
||||
|
||||
@property
|
||||
def devices(self):
|
||||
"""
|
||||
Dictionary with known devices. Key is device ID, value is the device
|
||||
itself.
|
||||
"""
|
||||
return self._devices
|
||||
|
||||
def add_device(self, device):
|
||||
"""
|
||||
Adds the specified device to the list of the already registered ones.
|
||||
|
||||
Beware: this is not what you usually want to do, take a look at
|
||||
`add_devices_to_register`
|
||||
"""
|
||||
self._devices[device.scs_id] = device
|
||||
|
||||
def add_devices_to_register(self, devices):
|
||||
""" List of devices to be registered. """
|
||||
with self._devices_to_register_lock:
|
||||
for device in devices:
|
||||
self._devices_to_register[device.scs_id] = device
|
||||
self._activate_next_device()
|
||||
|
||||
def _activate_next_device(self):
|
||||
""" Starts the activation of the first device. """
|
||||
from scsgate.tasks import GetStatusTask
|
||||
|
||||
with self._devices_to_register_lock:
|
||||
if len(self._devices_to_register) == 0:
|
||||
return
|
||||
_, device = self._devices_to_register.popitem()
|
||||
self._devices[device.scs_id] = device
|
||||
self._device_being_registered = device.scs_id
|
||||
self._reactor.append_task(GetStatusTask(target=device.scs_id))
|
||||
|
||||
def is_device_registered(self, device_id):
|
||||
""" Checks whether a device is already registered or not. """
|
||||
with self._devices_to_register_lock:
|
||||
if device_id in self._devices_to_register.keys():
|
||||
return False
|
||||
|
||||
with self._device_being_registered_lock:
|
||||
if device_id == self._device_being_registered:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def start(self):
|
||||
""" Start the scsgate.Reactor. """
|
||||
self._reactor.start()
|
||||
|
||||
def stop(self):
|
||||
""" Stop the scsgate.Reactor. """
|
||||
self._reactor.stop()
|
||||
|
||||
def append_task(self, task):
|
||||
""" Registers a new task to be executed. """
|
||||
self._reactor.append_task(task)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Setup the SCSGate component. """
|
||||
device = config['scsgate']['device']
|
||||
global SCSGATE
|
||||
|
||||
# pylint: disable=broad-except
|
||||
try:
|
||||
SCSGATE = SCSGate(device=device, logger=_LOGGER)
|
||||
SCSGATE.start()
|
||||
except Exception as exception:
|
||||
_LOGGER.error("Cannot setup SCSGate component: %s", exception)
|
||||
return False
|
||||
|
||||
def stop_monitor(event):
|
||||
"""
|
||||
Invoked when home-assistant is exiting. Performs the necessary
|
||||
cleanups.
|
||||
"""
|
||||
_LOGGER.info("Stopping SCSGate monitor thread")
|
||||
SCSGATE.stop()
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_monitor)
|
||||
|
||||
return True
|
|
@ -0,0 +1,90 @@
|
|||
"""
|
||||
homeassistant.components.sensor.apcupsd
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Provides a sensor to track various status aspects of a UPS.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.apcupsd/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import TEMP_CELCIUS
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.components import apcupsd
|
||||
|
||||
DEPENDENCIES = [apcupsd.DOMAIN]
|
||||
DEFAULT_NAME = "UPS Status"
|
||||
SPECIFIC_UNITS = {
|
||||
"ITEMP": TEMP_CELCIUS
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""
|
||||
Ensure that the 'type' config value has been set and use a specific unit
|
||||
of measurement if required.
|
||||
"""
|
||||
typ = config.get(apcupsd.CONF_TYPE)
|
||||
if typ is None:
|
||||
_LOGGER.error(
|
||||
"You must include a '%s' when configuring an APCUPSd sensor.",
|
||||
apcupsd.CONF_TYPE)
|
||||
return False
|
||||
typ = typ.upper()
|
||||
|
||||
if typ not in apcupsd.DATA.status:
|
||||
_LOGGER.error(
|
||||
"Specified '%s' of '%s' does not appear in the APCUPSd status "
|
||||
"output.", apcupsd.CONF_TYPE, typ)
|
||||
return False
|
||||
|
||||
add_entities((
|
||||
Sensor(config, apcupsd.DATA, unit=SPECIFIC_UNITS.get(typ)),
|
||||
))
|
||||
|
||||
|
||||
def infer_unit(value):
|
||||
"""
|
||||
If the value ends with any of the units from ALL_UNITS, split the unit
|
||||
off the end of the value and return the value, unit tuple pair. Else return
|
||||
the original value and None as the unit.
|
||||
"""
|
||||
from apcaccess.status import ALL_UNITS
|
||||
for unit in ALL_UNITS:
|
||||
if value.endswith(unit):
|
||||
return value[:-len(unit)], unit
|
||||
return value, None
|
||||
|
||||
|
||||
class Sensor(Entity):
|
||||
""" Generic sensor entity for APCUPSd status values. """
|
||||
def __init__(self, config, data, unit=None):
|
||||
self._config = config
|
||||
self._unit = unit
|
||||
self._data = data
|
||||
self._inferred_unit = None
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" The name of the UPS sensor. """
|
||||
return self._config.get("name", DEFAULT_NAME)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" True if the UPS is online, else False. """
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Unit of measurement of this entity, if any. """
|
||||
if self._unit is None:
|
||||
return self._inferred_unit
|
||||
return self._unit
|
||||
|
||||
def update(self):
|
||||
""" Get the latest status and use it to update our sensor state. """
|
||||
key = self._config[apcupsd.CONF_TYPE].upper()
|
||||
self._state, self._inferred_unit = infer_unit(self._data.status[key])
|
|
@ -39,6 +39,7 @@ OPTION_TYPES = {
|
|||
'miners_revenue_btc': ['Miners revenue', 'BTC'],
|
||||
'market_price_usd': ['Market price', 'USD']
|
||||
}
|
||||
ICON = 'mdi:currency-btc'
|
||||
|
||||
# Return cached results if last scan was less then this time ago
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
|
||||
|
@ -108,6 +109,11 @@ class BitcoinSensor(Entity):
|
|||
def unit_of_measurement(self):
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
""" Icon to use in the frontend, if any. """
|
||||
return ICON
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def update(self):
|
||||
""" Gets the latest data and updates the states. """
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
"""
|
||||
homeassistant.components.sensor.bloomsky
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support the sensor of a BloomSky weather station.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.bloomsky/
|
||||
"""
|
||||
import logging
|
||||
import homeassistant.components.bloomsky as bloomsky
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
DEPENDENCIES = ["bloomsky"]
|
||||
|
||||
# These are the available sensors
|
||||
SENSOR_TYPES = ["Temperature",
|
||||
"Humidity",
|
||||
"Rain",
|
||||
"Pressure",
|
||||
"Luminance",
|
||||
"Night",
|
||||
"UVIndex"]
|
||||
|
||||
# Sensor units - these do not currently align with the API documentation
|
||||
SENSOR_UNITS = {"Temperature": "°F",
|
||||
"Humidity": "%",
|
||||
"Pressure": "inHg",
|
||||
"Luminance": "cd/m²"}
|
||||
|
||||
# Which sensors to format numerically
|
||||
FORMAT_NUMBERS = ["Temperature", "Pressure"]
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Set up the available BloomSky weather sensors. """
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
for device_key in bloomsky.BLOOMSKY.devices:
|
||||
device = bloomsky.BLOOMSKY.devices[device_key]
|
||||
for variable in config["monitored_conditions"]:
|
||||
if variable in SENSOR_TYPES:
|
||||
add_devices([BloomSkySensor(bloomsky.BLOOMSKY,
|
||||
device,
|
||||
variable)])
|
||||
else:
|
||||
logger.error("Cannot find definition for device: %s", variable)
|
||||
|
||||
|
||||
class BloomSkySensor(Entity):
|
||||
""" Represents a single sensor in a BloomSky device. """
|
||||
|
||||
def __init__(self, bs, device, sensor_name):
|
||||
self._bloomsky = bs
|
||||
self._device_id = device["DeviceID"]
|
||||
self._client_name = device["DeviceName"]
|
||||
self._sensor_name = sensor_name
|
||||
self._state = self.process_state(device)
|
||||
self._sensor_update = ""
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" The name of the BloomSky device and this sensor. """
|
||||
return "{} {}".format(self._client_name, self._sensor_name)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" The current state (i.e. value) of this sensor. """
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" This sensor's units. """
|
||||
return SENSOR_UNITS.get(self._sensor_name, None)
|
||||
|
||||
def update(self):
|
||||
""" Request an update from the BloomSky API. """
|
||||
self._bloomsky.refresh_devices()
|
||||
# TS is a Unix epoch timestamp for the last time the BloomSky servers
|
||||
# heard from this device. If that value hasn't changed, the value has
|
||||
# not been updated.
|
||||
last_ts = self._bloomsky.devices[self._device_id]["Data"]["TS"]
|
||||
if last_ts != self._sensor_update:
|
||||
self.process_state(self._bloomsky.devices[self._device_id])
|
||||
self._sensor_update = last_ts
|
||||
|
||||
def process_state(self, device):
|
||||
""" Handle the response from the BloomSky API for this sensor. """
|
||||
data = device["Data"][self._sensor_name]
|
||||
if self._sensor_name == "Rain":
|
||||
if data:
|
||||
self._state = "Raining"
|
||||
else:
|
||||
self._state = "Not raining"
|
||||
elif self._sensor_name == "Night":
|
||||
if data:
|
||||
self._state = "Nighttime"
|
||||
else:
|
||||
self._state = "Daytime"
|
||||
elif self._sensor_name in FORMAT_NUMBERS:
|
||||
self._state = "{0:.2f}".format(data)
|
||||
else:
|
||||
self._state = data
|
|
@ -19,6 +19,7 @@ DEFAULT_NAME = "CPU speed"
|
|||
ATTR_VENDOR = 'Vendor ID'
|
||||
ATTR_BRAND = 'Brand'
|
||||
ATTR_HZ = 'GHz Advertised'
|
||||
ICON = 'mdi:pulse'
|
||||
|
||||
|
||||
# pylint: disable=unused-variable
|
||||
|
@ -53,7 +54,7 @@ class CpuSpeedSensor(Entity):
|
|||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
def device_state_attributes(self):
|
||||
""" Returns the state attributes. """
|
||||
if self.info is not None:
|
||||
return {
|
||||
|
@ -62,6 +63,11 @@ class CpuSpeedSensor(Entity):
|
|||
ATTR_HZ: round(self.info['hz_advertised_raw'][0]/10**9, 2)
|
||||
}
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
""" Icon to use in the frontend, if any. """
|
||||
return ICON
|
||||
|
||||
def update(self):
|
||||
""" Gets the latest data and updates the state. """
|
||||
from cpuinfo import cpuinfo
|
||||
|
|
|
@ -46,7 +46,7 @@ class DemoSensor(Entity):
|
|||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
def device_state_attributes(self):
|
||||
""" Returns the state attributes. """
|
||||
if self._battery:
|
||||
return {
|
||||
|
|
|
@ -23,6 +23,7 @@ SENSOR_TYPES = {
|
|||
'temperature': ['Temperature', None],
|
||||
'humidity': ['Humidity', '%']
|
||||
}
|
||||
DEFAULT_NAME = "DHT Sensor"
|
||||
# Return cached results if last scan was less then this time ago
|
||||
# DHT11 is able to deliver data once per second, DHT22 once every two
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
|
||||
|
@ -53,12 +54,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
data = DHTClient(Adafruit_DHT, sensor, pin)
|
||||
dev = []
|
||||
name = config.get('name', DEFAULT_NAME)
|
||||
|
||||
try:
|
||||
for variable in config['monitored_conditions']:
|
||||
if variable not in SENSOR_TYPES:
|
||||
_LOGGER.error('Sensor type: "%s" does not exist', variable)
|
||||
else:
|
||||
dev.append(DHTSensor(data, variable, unit))
|
||||
dev.append(DHTSensor(data, variable, unit, name))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
@ -69,8 +72,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
class DHTSensor(Entity):
|
||||
""" Implements an DHT sensor. """
|
||||
|
||||
def __init__(self, dht_client, sensor_type, temp_unit):
|
||||
self.client_name = 'DHT sensor'
|
||||
def __init__(self, dht_client, sensor_type, temp_unit, name):
|
||||
self.client_name = name
|
||||
self._name = SENSOR_TYPES[sensor_type][0]
|
||||
self.dht_client = dht_client
|
||||
self.temp_unit = temp_unit
|
||||
|
|
|
@ -1,29 +1,10 @@
|
|||
"""
|
||||
homeassistant.components.sensor.ecobee
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sensor platform for Ecobee sensors.
|
||||
|
||||
Ecobee Thermostat Component
|
||||
|
||||
This component adds support for Ecobee3 Wireless Thermostats.
|
||||
You will need to setup developer access to your thermostat,
|
||||
and create and API key on the ecobee website.
|
||||
|
||||
The first time you run this component you will see a configuration
|
||||
component card in Home Assistant. This card will contain a PIN code
|
||||
that you will need to use to authorize access to your thermostat. You
|
||||
can do this at https://www.ecobee.com/consumerportal/index.html
|
||||
Click My Apps, Add application, Enter Pin and click Authorize.
|
||||
|
||||
After authorizing the application click the button in the configuration
|
||||
card. Now your thermostat and sensors should shown in home-assistant.
|
||||
|
||||
You can use the optional hold_temp parameter to set whether or not holds
|
||||
are set indefintely or until the next scheduled event.
|
||||
|
||||
ecobee:
|
||||
api_key: asdfasdfasdfasdfasdfaasdfasdfasdfasdf
|
||||
hold_temp: True
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.ecobee/
|
||||
"""
|
||||
import logging
|
||||
|
||||
|
@ -32,7 +13,6 @@ from homeassistant.components import ecobee
|
|||
from homeassistant.const import TEMP_FAHRENHEIT
|
||||
|
||||
DEPENDENCIES = ['ecobee']
|
||||
|
||||
SENSOR_TYPES = {
|
||||
'temperature': ['Temperature', TEMP_FAHRENHEIT],
|
||||
'humidity': ['Humidity', '%'],
|
||||
|
@ -40,12 +20,11 @@ SENSOR_TYPES = {
|
|||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ECOBEE_CONFIG_FILE = 'ecobee.conf'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the sensors. """
|
||||
""" Sets up the Ecobee sensors. """
|
||||
if discovery_info is None:
|
||||
return
|
||||
data = ecobee.NETWORK
|
||||
|
@ -63,7 +42,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
|
||||
class EcobeeSensor(Entity):
|
||||
""" An ecobee sensor. """
|
||||
""" An Ecobee sensor. """
|
||||
|
||||
def __init__(self, sensor_name, sensor_type, sensor_index):
|
||||
self._name = sensor_name + ' ' + SENSOR_TYPES[sensor_type][0]
|
||||
|
@ -76,6 +55,7 @@ class EcobeeSensor(Entity):
|
|||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the Ecobee sensor.. """
|
||||
return self._name.rstrip()
|
||||
|
||||
@property
|
||||
|
@ -83,11 +63,18 @@ class EcobeeSensor(Entity):
|
|||
""" Returns the state of the device. """
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Unique id of this sensor."""
|
||||
return "sensor_ecobee_{}_{}".format(self._name, self.index)
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Unit of measurement this sensor expresses itself in. """
|
||||
return self._unit_of_measurement
|
||||
|
||||
def update(self):
|
||||
""" Get the latest state of the sensor. """
|
||||
data = ecobee.NETWORK
|
||||
data.update()
|
||||
for sensor in data.ecobee.get_remote_sensors(self.index):
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
"""
|
||||
homeassistant.components.sensor.mfi
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for Ubiquiti mFi sensors.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.mfi/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD,
|
||||
TEMP_CELCIUS)
|
||||
from homeassistant.components.sensor import DOMAIN
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers import validate_config
|
||||
|
||||
REQUIREMENTS = ['mficlient==0.2.2']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STATE_ON = 'on'
|
||||
STATE_OFF = 'off'
|
||||
DIGITS = {
|
||||
'volts': 1,
|
||||
'amps': 1,
|
||||
'active_power': 0,
|
||||
'temperature': 1,
|
||||
}
|
||||
SENSOR_MODELS = [
|
||||
'Ubiquiti mFi-THS',
|
||||
'Ubiquiti mFi-CS',
|
||||
'Outlet',
|
||||
'Input Analog',
|
||||
'Input Digital',
|
||||
]
|
||||
|
||||
|
||||
# pylint: disable=unused-variable
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up mFi sensors. """
|
||||
|
||||
if not validate_config({DOMAIN: config},
|
||||
{DOMAIN: ['host',
|
||||
CONF_USERNAME,
|
||||
CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
_LOGGER.error('A host, username, and password are required')
|
||||
return False
|
||||
|
||||
host = config.get('host')
|
||||
port = int(config.get('port', 6443))
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
|
||||
from mficlient.client import MFiClient
|
||||
|
||||
try:
|
||||
client = MFiClient(host, username, password, port=port)
|
||||
except client.FailedToLogin as ex:
|
||||
_LOGGER.error('Unable to connect to mFi: %s', str(ex))
|
||||
return False
|
||||
|
||||
add_devices(MfiSensor(port, hass)
|
||||
for device in client.get_devices()
|
||||
for port in device.ports.values()
|
||||
if port.model in SENSOR_MODELS)
|
||||
|
||||
|
||||
class MfiSensor(Entity):
|
||||
""" An mFi sensor that exposes tag=value. """
|
||||
|
||||
def __init__(self, port, hass):
|
||||
self._port = port
|
||||
self._hass = hass
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._port.label
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
if self._port.model == 'Input Digital':
|
||||
return self._port.value > 0 and STATE_ON or STATE_OFF
|
||||
else:
|
||||
digits = DIGITS.get(self._port.tag, 0)
|
||||
return round(self._port.value, digits)
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
if self._port.tag == 'temperature':
|
||||
return TEMP_CELCIUS
|
||||
elif self._port.tag == 'active_pwr':
|
||||
return 'Watts'
|
||||
elif self._port.model == 'Input Digital':
|
||||
return 'State'
|
||||
return self._port.tag
|
||||
|
||||
def update(self):
|
||||
self._port.refresh()
|
|
@ -7,7 +7,7 @@ For more details about this platform, please refer to the documentation at
|
|||
https://home-assistant.io/components/sensor.mqtt/
|
||||
"""
|
||||
import logging
|
||||
from homeassistant.const import CONF_VALUE_TEMPLATE
|
||||
from homeassistant.const import CONF_VALUE_TEMPLATE, STATE_UNKNOWN
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import template
|
||||
import homeassistant.components.mqtt as mqtt
|
||||
|
@ -42,7 +42,7 @@ class MqttSensor(Entity):
|
|||
""" Represents a sensor that can be updated using MQTT. """
|
||||
def __init__(self, hass, name, state_topic, qos, unit_of_measurement,
|
||||
value_template):
|
||||
self._state = "-"
|
||||
self._state = STATE_UNKNOWN
|
||||
self._hass = hass
|
||||
self._name = name
|
||||
self._state_topic = state_topic
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""
|
||||
homeassistant.components.sensor.mysensors
|
||||
homeassistant.components.sensor.mysensors.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for MySensors sensors.
|
||||
|
||||
|
@ -30,7 +31,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
for gateway in mysensors.GATEWAYS.values():
|
||||
# Define the S_TYPES and V_TYPES that the platform should handle as
|
||||
# states. Map them in a defaultdict(list).
|
||||
# states. Map them in a dict of lists.
|
||||
pres = gateway.const.Presentation
|
||||
set_req = gateway.const.SetReq
|
||||
map_sv_types = {
|
||||
|
@ -151,48 +152,46 @@ class MySensorsSensor(Entity):
|
|||
self.gateway.const.SetReq.V_VOLTAGE: 'V',
|
||||
self.gateway.const.SetReq.V_CURRENT: 'A',
|
||||
}
|
||||
unit_map_v15 = {
|
||||
self.gateway.const.SetReq.V_PERCENTAGE: '%',
|
||||
}
|
||||
if float(self.gateway.version) >= 1.5:
|
||||
if self.gateway.const.SetReq.V_UNIT_PREFIX in self._values:
|
||||
return self._values[
|
||||
self.gateway.const.SetReq.V_UNIT_PREFIX]
|
||||
unit_map.update(unit_map_v15)
|
||||
unit_map.update({self.gateway.const.SetReq.V_PERCENTAGE: '%'})
|
||||
return unit_map.get(self.value_type)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
device_attr = {}
|
||||
for value_type, value in self._values.items():
|
||||
if value_type != self.value_type:
|
||||
device_attr[self.gateway.const.SetReq(value_type).name] = value
|
||||
return device_attr
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
data = {
|
||||
attr = {
|
||||
mysensors.ATTR_PORT: self.gateway.port,
|
||||
mysensors.ATTR_NODE_ID: self.node_id,
|
||||
mysensors.ATTR_CHILD_ID: self.child_id,
|
||||
ATTR_BATTERY_LEVEL: self.battery_level,
|
||||
}
|
||||
|
||||
device_attr = self.device_state_attributes
|
||||
set_req = self.gateway.const.SetReq
|
||||
|
||||
if device_attr is not None:
|
||||
data.update(device_attr)
|
||||
for value_type, value in self._values.items():
|
||||
if value_type != self.value_type:
|
||||
try:
|
||||
attr[set_req(value_type).name] = value
|
||||
except ValueError:
|
||||
_LOGGER.error('value_type %s is not valid for mysensors '
|
||||
'version %s', value_type,
|
||||
self.gateway.version)
|
||||
return attr
|
||||
|
||||
return data
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return self.value_type in self._values
|
||||
|
||||
def update(self):
|
||||
"""Update the controller with the latest values from a sensor."""
|
||||
node = self.gateway.sensors[self.node_id]
|
||||
child = node.children[self.child_id]
|
||||
for value_type, value in child.values.items():
|
||||
_LOGGER.info(
|
||||
_LOGGER.debug(
|
||||
"%s: value_type %s, value = %s", self._name, value_type, value)
|
||||
if value_type == self.gateway.const.SetReq.V_TRIPPED:
|
||||
self._values[value_type] = STATE_ON if int(
|
||||
|
|
|
@ -21,7 +21,7 @@ SENSOR_TYPES = ['humidity',
|
|||
'last_connection',
|
||||
'battery_level']
|
||||
|
||||
SENSOR_UNITS = {'humidity': '%', 'battery_level': '%'}
|
||||
SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V'}
|
||||
|
||||
SENSOR_TEMP_TYPES = ['temperature',
|
||||
'target',
|
||||
|
|
|
@ -96,5 +96,7 @@ class OneWire(Entity):
|
|||
equals_pos = lines[1].find('t=')
|
||||
if equals_pos != -1:
|
||||
temp_string = lines[1][equals_pos+2:]
|
||||
temp = float(temp_string) / 1000.0
|
||||
temp = round(float(temp_string) / 1000.0, 1)
|
||||
if temp < -55 or temp > 125:
|
||||
return
|
||||
self._state = temp
|
||||
|
|
|
@ -21,7 +21,9 @@ DATA_TYPES = OrderedDict([
|
|||
('Humidity', '%'),
|
||||
('Barometer', ''),
|
||||
('Wind direction', ''),
|
||||
('Rain rate', '')])
|
||||
('Rain rate', ''),
|
||||
('Energy usage', 'W'),
|
||||
('Total usage', 'W')])
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -87,7 +89,7 @@ class RfxtrxSensor(Entity):
|
|||
return self._name
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
def device_state_attributes(self):
|
||||
return self.event.values
|
||||
|
||||
@property
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
"""
|
||||
homeassistant.components.sensor.speedtest
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Speedtest.net sensor based on speedtest-cli.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.speedtest/
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
import re
|
||||
from datetime import timedelta
|
||||
from subprocess import check_output
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.event import track_time_change
|
||||
from homeassistant.components.sensor import DOMAIN
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
REQUIREMENTS = ['speedtest-cli==0.3.4']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_SPEEDTEST_REGEX = re.compile(r'Ping:\s(\d+\.\d+)\sms[\r\n]+'
|
||||
r'Download:\s(\d+\.\d+)\sMbit/s[\r\n]+'
|
||||
r'Upload:\s(\d+\.\d+)\sMbit/s[\r\n]+')
|
||||
|
||||
CONF_MONITORED_CONDITIONS = 'monitored_conditions'
|
||||
CONF_MINUTE = 'minute'
|
||||
CONF_HOUR = 'hour'
|
||||
CONF_DAY = 'day'
|
||||
SENSOR_TYPES = {
|
||||
'ping': ['Ping', 'ms'],
|
||||
'download': ['Download', 'Mbit/s'],
|
||||
'upload': ['Upload', 'Mbit/s'],
|
||||
}
|
||||
|
||||
# Return cached results if last scan was less then this time ago
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Setup the Speedtest sensor. """
|
||||
|
||||
data = SpeedtestData(hass, config)
|
||||
dev = []
|
||||
for sensor in config[CONF_MONITORED_CONDITIONS]:
|
||||
if sensor not in SENSOR_TYPES:
|
||||
_LOGGER.error('Sensor type: "%s" does not exist', sensor)
|
||||
else:
|
||||
dev.append(SpeedtestSensor(data, sensor))
|
||||
|
||||
add_devices(dev)
|
||||
|
||||
def update(call=None):
|
||||
""" Update service for manual updates. """
|
||||
data.update(dt_util.now())
|
||||
for sensor in dev:
|
||||
sensor.update()
|
||||
|
||||
hass.services.register(DOMAIN, 'update_speedtest', update)
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class SpeedtestSensor(Entity):
|
||||
""" Implements a speedtest.net sensor. """
|
||||
|
||||
def __init__(self, speedtest_data, sensor_type):
|
||||
self._name = SENSOR_TYPES[sensor_type][0]
|
||||
self.speedtest_client = speedtest_data
|
||||
self.type = sensor_type
|
||||
self._state = None
|
||||
self._unit_of_measurement = SENSOR_TYPES[self.type][1]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return '{} {}'.format('Speedtest', self._name)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Unit of measurement of this entity, if any. """
|
||||
return self._unit_of_measurement
|
||||
|
||||
def update(self):
|
||||
""" Gets the latest data from Forecast.io and updates the states. """
|
||||
data = self.speedtest_client.data
|
||||
if data is not None:
|
||||
if self.type == 'ping':
|
||||
self._state = data['ping']
|
||||
elif self.type == 'download':
|
||||
self._state = data['download']
|
||||
elif self.type == 'upload':
|
||||
self._state = data['upload']
|
||||
|
||||
|
||||
class SpeedtestData(object):
|
||||
""" Gets the latest data from speedtest.net. """
|
||||
|
||||
def __init__(self, hass, config):
|
||||
self.data = None
|
||||
self.hass = hass
|
||||
self.path = hass.config.path
|
||||
track_time_change(self.hass, self.update,
|
||||
minute=config.get(CONF_MINUTE, 0),
|
||||
hour=config.get(CONF_HOUR, None),
|
||||
day=config.get(CONF_DAY, None))
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self, now):
|
||||
""" Gets the latest data from speedtest.net. """
|
||||
_LOGGER.info('Executing speedtest')
|
||||
re_output = _SPEEDTEST_REGEX.split(
|
||||
check_output([sys.executable, self.path(
|
||||
'lib', 'speedtest_cli.py'), '--simple']).decode("utf-8"))
|
||||
self.data = {'ping': round(float(re_output[1]), 2),
|
||||
'download': round(float(re_output[2]), 2),
|
||||
'upload': round(float(re_output[3]), 2)}
|
|
@ -23,6 +23,7 @@ ATTR_DEPARTURE_TIME2 = 'Next on departure'
|
|||
ATTR_START = 'Start'
|
||||
ATTR_TARGET = 'Destination'
|
||||
ATTR_REMAINING_TIME = 'Remaining time'
|
||||
ICON = 'mdi:bus'
|
||||
|
||||
# Return cached results if last scan was less then this time ago
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
||||
|
@ -74,7 +75,7 @@ class SwissPublicTransportSensor(Entity):
|
|||
return self._state
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
def device_state_attributes(self):
|
||||
""" Returns the state attributes. """
|
||||
if self._times is not None:
|
||||
return {
|
||||
|
@ -86,6 +87,11 @@ class SwissPublicTransportSensor(Entity):
|
|||
':'.join(str(self._times[2]).split(':')[:2]))
|
||||
}
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
""" Icon to use in the frontend, if any. """
|
||||
return ICON
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def update(self):
|
||||
""" Gets the latest data from opendata.ch and updates the states. """
|
||||
|
|
|
@ -67,10 +67,12 @@ class SystemMonitorSensor(Entity):
|
|||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the sensor. """
|
||||
return self._name.rstrip()
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
""" Icon to use in the frontend, if any. """
|
||||
return SENSOR_TYPES[self.type][2]
|
||||
|
||||
@property
|
||||
|
@ -80,10 +82,12 @@ class SystemMonitorSensor(Entity):
|
|||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Unit of measurement of this entity, if any. """
|
||||
return self._unit_of_measurement
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def update(self):
|
||||
""" Get the latest system informations. """
|
||||
import psutil
|
||||
if self.type == 'disk_use_percent':
|
||||
self._state = psutil.disk_usage(self.argument).percent
|
||||
|
|
|
@ -11,7 +11,9 @@ import logging
|
|||
|
||||
from datetime import datetime
|
||||
|
||||
from homeassistant.const import TEMP_CELCIUS, ATTR_BATTERY_LEVEL
|
||||
from homeassistant.const import (TEMP_CELCIUS,
|
||||
ATTR_BATTERY_LEVEL,
|
||||
DEVICE_DEFAULT_NAME)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.components import tellduslive
|
||||
|
||||
|
@ -44,50 +46,86 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
""" Sets up Tellstick sensors. """
|
||||
if discovery_info is None:
|
||||
return
|
||||
sensors = tellduslive.NETWORK.get_sensors()
|
||||
devices = []
|
||||
|
||||
for component in sensors:
|
||||
for sensor in component["data"]:
|
||||
# one component can have more than one sensor
|
||||
# (e.g. both humidity and temperature)
|
||||
devices.append(TelldusLiveSensor(component["id"],
|
||||
component["name"],
|
||||
sensor["name"]))
|
||||
add_devices(devices)
|
||||
add_devices(TelldusLiveSensor(sensor) for sensor in discovery_info)
|
||||
|
||||
|
||||
class TelldusLiveSensor(Entity):
|
||||
""" Represents a Telldus Live sensor. """
|
||||
|
||||
def __init__(self, sensor_id, sensor_name, sensor_type):
|
||||
self._sensor_id = sensor_id
|
||||
self._sensor_type = sensor_type
|
||||
self._state = None
|
||||
self._name = sensor_name + ' ' + SENSOR_TYPES[sensor_type][0]
|
||||
self._last_update = None
|
||||
self._battery_level = None
|
||||
def __init__(self, sensor_id):
|
||||
self._id = sensor_id
|
||||
self.update()
|
||||
_LOGGER.debug("created sensor %s", self)
|
||||
|
||||
def update(self):
|
||||
""" update sensor values """
|
||||
tellduslive.NETWORK.update_sensors()
|
||||
self._sensor = tellduslive.NETWORK.get_sensor(self._id)
|
||||
|
||||
@property
|
||||
def _sensor_name(self):
|
||||
return self._sensor["name"]
|
||||
|
||||
@property
|
||||
def _sensor_value(self):
|
||||
return self._sensor["data"]["value"]
|
||||
|
||||
@property
|
||||
def _sensor_type(self):
|
||||
return self._sensor["data"]["name"]
|
||||
|
||||
@property
|
||||
def _battery_level(self):
|
||||
sensor_battery_level = self._sensor.get("battery")
|
||||
return round(sensor_battery_level * 100 / 255) \
|
||||
if sensor_battery_level else None
|
||||
|
||||
@property
|
||||
def _last_updated(self):
|
||||
sensor_last_updated = self._sensor.get("lastUpdated")
|
||||
return str(datetime.fromtimestamp(sensor_last_updated)) \
|
||||
if sensor_last_updated else None
|
||||
|
||||
@property
|
||||
def _value_as_temperature(self):
|
||||
return round(float(self._sensor_value), 1)
|
||||
|
||||
@property
|
||||
def _value_as_humidity(self):
|
||||
return int(round(float(self._sensor_value)))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return self._name
|
||||
return "{} {}".format(self._sensor_name or DEVICE_DEFAULT_NAME,
|
||||
self.quantity_name)
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
return not self._sensor.get("offline", False)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
return self._state
|
||||
if self._sensor_type == SENSOR_TYPE_TEMP:
|
||||
return self._value_as_temperature
|
||||
elif self._sensor_type == SENSOR_TYPE_HUMIDITY:
|
||||
return self._value_as_humidity
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
attrs = dict()
|
||||
def device_state_attributes(self):
|
||||
attrs = {}
|
||||
if self._battery_level is not None:
|
||||
attrs[ATTR_BATTERY_LEVEL] = self._battery_level
|
||||
if self._last_update is not None:
|
||||
attrs[ATTR_LAST_UPDATED] = self._last_update
|
||||
if self._last_updated is not None:
|
||||
attrs[ATTR_LAST_UPDATED] = self._last_updated
|
||||
return attrs
|
||||
|
||||
@property
|
||||
def quantity_name(self):
|
||||
""" name of quantity """
|
||||
return SENSOR_TYPES[self._sensor_type][0]
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
return SENSOR_TYPES[self._sensor_type][1]
|
||||
|
@ -95,18 +133,3 @@ class TelldusLiveSensor(Entity):
|
|||
@property
|
||||
def icon(self):
|
||||
return SENSOR_TYPES[self._sensor_type][2]
|
||||
|
||||
def update(self):
|
||||
values = tellduslive.NETWORK.get_sensor_value(self._sensor_id,
|
||||
self._sensor_type)
|
||||
self._state, self._battery_level, self._last_update = values
|
||||
|
||||
self._state = float(self._state)
|
||||
if self._sensor_type == SENSOR_TYPE_TEMP:
|
||||
self._state = round(self._state, 1)
|
||||
elif self._sensor_type == SENSOR_TYPE_HUMIDITY:
|
||||
self._state = int(round(self._state))
|
||||
|
||||
self._battery_level = round(self._battery_level * 100 / 255) # percent
|
||||
|
||||
self._last_update = str(datetime.fromtimestamp(self._last_update))
|
||||
|
|
|
@ -9,16 +9,20 @@ https://home-assistant.io/components/sensor.template/
|
|||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity import Entity, generate_entity_id
|
||||
from homeassistant.core import EVENT_STATE_CHANGED
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME,
|
||||
CONF_VALUE_TEMPLATE,
|
||||
ATTR_UNIT_OF_MEASUREMENT)
|
||||
|
||||
from homeassistant.util import template
|
||||
from homeassistant.util import template, slugify
|
||||
from homeassistant.exceptions import TemplateError
|
||||
|
||||
from homeassistant.components.sensor import DOMAIN
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CONF_SENSORS = 'sensors'
|
||||
STATE_ERROR = 'error'
|
||||
|
@ -34,9 +38,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
return False
|
||||
|
||||
for device, device_config in config[CONF_SENSORS].items():
|
||||
|
||||
if device != slugify(device):
|
||||
_LOGGER.error("Found invalid key for sensor.template: %s. "
|
||||
"Use %s instead", device, slugify(device))
|
||||
continue
|
||||
|
||||
if not isinstance(device_config, dict):
|
||||
_LOGGER.error("Missing configuration data for sensor %s", device)
|
||||
continue
|
||||
|
||||
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
||||
unit_of_measurement = device_config.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
state_template = device_config.get(CONF_VALUE_TEMPLATE)
|
||||
|
@ -44,14 +55,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
_LOGGER.error(
|
||||
"Missing %s for sensor %s", CONF_VALUE_TEMPLATE, device)
|
||||
continue
|
||||
|
||||
sensors.append(
|
||||
SensorTemplate(
|
||||
hass,
|
||||
device,
|
||||
friendly_name,
|
||||
unit_of_measurement,
|
||||
state_template)
|
||||
)
|
||||
if sensors is None:
|
||||
if not sensors:
|
||||
_LOGGER.error("No sensors added")
|
||||
return False
|
||||
add_devices(sensors)
|
||||
|
@ -64,10 +77,15 @@ class SensorTemplate(Entity):
|
|||
# pylint: disable=too-many-arguments
|
||||
def __init__(self,
|
||||
hass,
|
||||
device_id,
|
||||
friendly_name,
|
||||
unit_of_measurement,
|
||||
state_template):
|
||||
|
||||
self.entity_id = generate_entity_id(
|
||||
ENTITY_ID_FORMAT, device_id,
|
||||
hass=hass)
|
||||
|
||||
self.hass = hass
|
||||
self._name = friendly_name
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
|
@ -76,10 +94,7 @@ class SensorTemplate(Entity):
|
|||
|
||||
def _update_callback(_event):
|
||||
""" Called when the target device changes state. """
|
||||
# This can be called before the entity is properly
|
||||
# initialised, so check before updating state,
|
||||
if self.entity_id:
|
||||
self.update_ha_state(True)
|
||||
self.update_ha_state(True)
|
||||
|
||||
self.hass.bus.listen(EVENT_STATE_CHANGED, _update_callback)
|
||||
|
||||
|
@ -108,4 +123,9 @@ class SensorTemplate(Entity):
|
|||
self._state = template.render(self.hass, self._template)
|
||||
except TemplateError as ex:
|
||||
self._state = STATE_ERROR
|
||||
if ex.args and ex.args[0].startswith(
|
||||
"UndefinedError: 'None' has no attribute"):
|
||||
# Common during HA startup - so just a warning
|
||||
_LOGGER.warning(ex)
|
||||
return
|
||||
_LOGGER.error(ex)
|
||||
|
|
|
@ -59,6 +59,15 @@ class TimeDateSensor(Entity):
|
|||
""" Returns the state of the device. """
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
if "date" in self.type and "time" in self.type:
|
||||
return "mdi:calendar-clock"
|
||||
elif "date" in self.type:
|
||||
return "mdi:calendar"
|
||||
else:
|
||||
return "mdi:clock"
|
||||
|
||||
def update(self):
|
||||
""" Gets the latest data and updates the states. """
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ class TwitchSensor(Entity):
|
|||
self._state = STATE_OFFLINE
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
def device_state_attributes(self):
|
||||
""" Returns the state attributes. """
|
||||
if self._state == STATE_STREAMING:
|
||||
return {
|
||||
|
@ -78,4 +78,5 @@ class TwitchSensor(Entity):
|
|||
|
||||
@property
|
||||
def icon(self):
|
||||
""" Icon to use in the frontend, if any. """
|
||||
return ICON
|
||||
|
|
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
|||
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME,
|
||||
TEMP_CELCIUS, TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_STOP)
|
||||
|
||||
REQUIREMENTS = ['pyvera==0.2.7']
|
||||
REQUIREMENTS = ['pyvera==0.2.8']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -118,7 +118,7 @@ class VeraSensor(Entity):
|
|||
return '%'
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
def device_state_attributes(self):
|
||||
attr = {}
|
||||
if self.vera_device.has_battery:
|
||||
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
|
||||
|
|
|
@ -11,7 +11,7 @@ import logging
|
|||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
|
||||
|
||||
REQUIREMENTS = ['python-wink==0.4.2']
|
||||
REQUIREMENTS = ['python-wink==0.5.0']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
|
|
@ -14,6 +14,7 @@ from homeassistant.helpers.entity import Entity
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DEFAULT_NAME = "Worldclock Sensor"
|
||||
ICON = 'mdi:clock'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
@ -54,6 +55,11 @@ class WorldClockSensor(Entity):
|
|||
""" Returns the state of the device. """
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
""" Icon to use in the frontend, if any. """
|
||||
return ICON
|
||||
|
||||
def update(self):
|
||||
""" Gets the time and updates the states. """
|
||||
self._state = dt_util.datetime_to_time_str(
|
||||
|
|
|
@ -28,7 +28,7 @@ SENSOR_TYPES = {
|
|||
'temperature': ['Temperature', '°C'],
|
||||
'windSpeed': ['Wind speed', 'm/s'],
|
||||
'windGust': ['Wind gust', 'm/s'],
|
||||
'pressure': ['Pressure', 'mbar'],
|
||||
'pressure': ['Pressure', 'hPa'],
|
||||
'windDirection': ['Wind direction', '°'],
|
||||
'humidity': ['Humidity', '%'],
|
||||
'fog': ['Fog', '%'],
|
||||
|
@ -100,7 +100,7 @@ class YrSensor(Entity):
|
|||
return self._state
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
def device_state_attributes(self):
|
||||
""" Returns state attributes. """
|
||||
data = {
|
||||
'about': "Weather forecast from yr.no, delivered by the"
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
"""
|
||||
homeassistant.components.sensor.zigbee
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Contains functionality to use a ZigBee device as a sensor.
|
||||
"""
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.zigbee/
|
||||
|
||||
"""
|
||||
import logging
|
||||
from binascii import hexlify
|
||||
|
||||
|
@ -36,9 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
|
||||
|
||||
class ZigBeeTemperatureSensor(Entity):
|
||||
"""
|
||||
Allows usage of an XBee Pro as a temperature sensor.
|
||||
"""
|
||||
""" Allows usage of an XBee Pro as a temperature sensor. """
|
||||
def __init__(self, hass, config):
|
||||
self._config = config
|
||||
self._temp = None
|
||||
|
|
|
@ -22,14 +22,24 @@ from homeassistant.components.zwave import (
|
|||
from homeassistant.const import (
|
||||
STATE_ON, STATE_OFF, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||
|
||||
PHILIO = '013c'
|
||||
PHILIO_SLIM_SENSOR = '0002'
|
||||
PHILIO = '0x013c'
|
||||
PHILIO_SLIM_SENSOR = '0x0002'
|
||||
PHILIO_SLIM_SENSOR_MOTION = (PHILIO, PHILIO_SLIM_SENSOR, 0)
|
||||
|
||||
FIBARO = '0x010f'
|
||||
FIBARO_WALL_PLUG = '0x1000'
|
||||
FIBARO_WALL_PLUG_SENSOR_METER = (FIBARO, FIBARO_WALL_PLUG, 8)
|
||||
|
||||
WORKAROUND_NO_OFF_EVENT = 'trigger_no_off_event'
|
||||
WORKAROUND_IGNORE = 'ignore'
|
||||
|
||||
DEVICE_MAPPINGS = {
|
||||
PHILIO_SLIM_SENSOR_MOTION: WORKAROUND_NO_OFF_EVENT,
|
||||
|
||||
# For some reason Fibaro Wall Plug reports 2 power consumptions.
|
||||
# One value updates as the power consumption changes
|
||||
# and the other does not change.
|
||||
FIBARO_WALL_PLUG_SENSOR_METER: WORKAROUND_IGNORE,
|
||||
}
|
||||
|
||||
|
||||
|
@ -66,6 +76,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
add_devices([
|
||||
ZWaveTriggerSensor(value, hass, re_arm_multiplier * 8)
|
||||
])
|
||||
elif DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_IGNORE:
|
||||
return
|
||||
|
||||
# generic Device mappings
|
||||
elif value.command_class == COMMAND_CLASS_SENSOR_BINARY:
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
"""
|
||||
homeassistant.components.splunk
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Splunk component which allows you to send data to an Splunk instance
|
||||
utilizing the HTTP Event Collector.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/splunk/
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
import homeassistant.util as util
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.helpers import state as state_helper
|
||||
from homeassistant.const import EVENT_STATE_CHANGED
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "splunk"
|
||||
DEPENDENCIES = []
|
||||
|
||||
DEFAULT_HOST = 'localhost'
|
||||
DEFAULT_PORT = '8088'
|
||||
DEFAULT_SSL = False
|
||||
|
||||
CONF_HOST = 'host'
|
||||
CONF_PORT = 'port'
|
||||
CONF_TOKEN = 'token'
|
||||
CONF_SSL = 'SSL'
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
""" Setup the Splunk component. """
|
||||
|
||||
if not validate_config(config, {DOMAIN: ['token']}, _LOGGER):
|
||||
_LOGGER.error("You must include the token for your HTTP "
|
||||
"Event Collector input in Splunk.")
|
||||
return False
|
||||
|
||||
conf = config[DOMAIN]
|
||||
|
||||
host = conf[CONF_HOST]
|
||||
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
|
||||
token = util.convert(conf.get(CONF_TOKEN), str)
|
||||
use_ssl = util.convert(conf.get(CONF_SSL), bool, DEFAULT_SSL)
|
||||
if use_ssl:
|
||||
uri_scheme = "https://"
|
||||
else:
|
||||
uri_scheme = "http://"
|
||||
event_collector = uri_scheme + host + ":" + str(port) + \
|
||||
"/services/collector/event"
|
||||
headers = {'Authorization': 'Splunk ' + token}
|
||||
|
||||
def splunk_event_listener(event):
|
||||
""" Listen for new messages on the bus and sends them to Splunk. """
|
||||
|
||||
state = event.data.get('new_state')
|
||||
|
||||
if state is None:
|
||||
return
|
||||
|
||||
try:
|
||||
_state = state_helper.state_as_number(state)
|
||||
except ValueError:
|
||||
_state = state.state
|
||||
|
||||
json_body = [
|
||||
{
|
||||
'domain': state.domain,
|
||||
'entity_id': state.object_id,
|
||||
'attributes': state.attributes,
|
||||
'time': str(event.time_fired),
|
||||
'value': _state,
|
||||
}
|
||||
]
|
||||
|
||||
try:
|
||||
payload = {"host": event_collector,
|
||||
"event": json_body}
|
||||
requests.post(event_collector, data=json.dumps(payload),
|
||||
headers=headers)
|
||||
except requests.exceptions.RequestException as error:
|
||||
_LOGGER.exception('Error saving event to Splunk: %s', error)
|
||||
|
||||
hass.bus.listen(EVENT_STATE_CHANGED, splunk_event_listener)
|
||||
|
||||
return True
|
|
@ -8,10 +8,8 @@ https://home-assistant.io/components/statsd/
|
|||
"""
|
||||
import logging
|
||||
import homeassistant.util as util
|
||||
from homeassistant.const import (EVENT_STATE_CHANGED, STATE_ON, STATE_OFF,
|
||||
STATE_UNLOCKED, STATE_LOCKED, STATE_UNKNOWN)
|
||||
from homeassistant.components.sun import (STATE_ABOVE_HORIZON,
|
||||
STATE_BELOW_HORIZON)
|
||||
from homeassistant.const import EVENT_STATE_CHANGED
|
||||
from homeassistant.helpers import state as state_helper
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -61,19 +59,10 @@ def setup(hass, config):
|
|||
if state is None:
|
||||
return
|
||||
|
||||
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON):
|
||||
_state = 1
|
||||
elif state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
|
||||
STATE_BELOW_HORIZON):
|
||||
_state = 0
|
||||
else:
|
||||
_state = state.state
|
||||
if _state == '':
|
||||
return
|
||||
try:
|
||||
_state = float(_state)
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
_state = state_helper.state_as_number(state)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
if not isinstance(_state, NUM_TYPES):
|
||||
return
|
||||
|
|
|
@ -129,11 +129,6 @@ class SwitchDevice(ToggleEntity):
|
|||
""" Is the device in standby. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
""" Returns device specific state attributes. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Returns optional state attributes. """
|
||||
|
@ -144,9 +139,4 @@ class SwitchDevice(ToggleEntity):
|
|||
if value:
|
||||
data[attr] = value
|
||||
|
||||
device_attr = self.device_state_attributes
|
||||
|
||||
if device_attr is not None:
|
||||
data.update(device_attr)
|
||||
|
||||
return data
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
"""
|
||||
homeassistant.components.switch.mfi
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for Ubiquiti mFi switches.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/switch.mfi/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.switch import DOMAIN, SwitchDevice
|
||||
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
|
||||
from homeassistant.helpers import validate_config
|
||||
|
||||
REQUIREMENTS = ['mficlient==0.2.2']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SWITCH_MODELS = [
|
||||
'Outlet',
|
||||
'Output 5v',
|
||||
'Output 12v',
|
||||
'Output 24v',
|
||||
]
|
||||
|
||||
|
||||
# pylint: disable=unused-variable
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up mFi sensors. """
|
||||
|
||||
if not validate_config({DOMAIN: config},
|
||||
{DOMAIN: ['host',
|
||||
CONF_USERNAME,
|
||||
CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
_LOGGER.error('A host, username, and password are required')
|
||||
return False
|
||||
|
||||
host = config.get('host')
|
||||
port = int(config.get('port', 6443))
|
||||
username = config.get('username')
|
||||
password = config.get('password')
|
||||
|
||||
from mficlient.client import MFiClient
|
||||
|
||||
try:
|
||||
client = MFiClient(host, username, password, port=port)
|
||||
except client.FailedToLogin as ex:
|
||||
_LOGGER.error('Unable to connect to mFi: %s', str(ex))
|
||||
return False
|
||||
|
||||
add_devices(MfiSwitch(port)
|
||||
for device in client.get_devices()
|
||||
for port in device.ports.values()
|
||||
if port.model in SWITCH_MODELS)
|
||||
|
||||
|
||||
class MfiSwitch(SwitchDevice):
|
||||
""" An mFi switch-able device. """
|
||||
def __init__(self, port):
|
||||
self._port = port
|
||||
self._target_state = None
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
return self._port.ident
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._port.label
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
return self._port.output
|
||||
|
||||
def update(self):
|
||||
self._port.refresh()
|
||||
if self._target_state is not None:
|
||||
self._port.data['output'] = float(self._target_state)
|
||||
self._target_state = None
|
||||
|
||||
def turn_on(self):
|
||||
self._port.control(True)
|
||||
self._target_state = True
|
||||
|
||||
def turn_off(self):
|
||||
self._port.control(False)
|
||||
self._target_state = False
|
||||
|
||||
@property
|
||||
def current_power_mwh(self):
|
||||
return int(self._port.data.get('active_pwr', 0) * 1000)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
attr = {}
|
||||
attr['volts'] = round(self._port.data.get('v_rms', 0), 1)
|
||||
attr['amps'] = round(self._port.data.get('i_rms', 0), 1)
|
||||
return attr
|
|
@ -1,5 +1,6 @@
|
|||
"""
|
||||
homeassistant.components.switch.mysensors
|
||||
homeassistant.components.switch.mysensors.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Support for MySensors switches.
|
||||
|
||||
|
@ -29,14 +30,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
|
||||
for gateway in mysensors.GATEWAYS.values():
|
||||
# Define the S_TYPES and V_TYPES that the platform should handle as
|
||||
# states. Map them in a defaultdict(list).
|
||||
# states. Map them in a dict of lists.
|
||||
pres = gateway.const.Presentation
|
||||
set_req = gateway.const.SetReq
|
||||
map_sv_types = {
|
||||
pres.S_DOOR: [set_req.V_ARMED],
|
||||
pres.S_MOTION: [set_req.V_ARMED],
|
||||
pres.S_SMOKE: [set_req.V_ARMED],
|
||||
pres.S_LIGHT: [set_req.V_LIGHT],
|
||||
pres.S_LOCK: [set_req.V_LOCK_STATUS],
|
||||
}
|
||||
if float(gateway.version) >= 1.5:
|
||||
|
@ -48,7 +48,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
pres.S_VIBRATION: [set_req.V_ARMED],
|
||||
pres.S_MOISTURE: [set_req.V_ARMED],
|
||||
})
|
||||
map_sv_types[pres.S_LIGHT].append(set_req.V_STATUS)
|
||||
|
||||
devices = {}
|
||||
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
|
||||
|
@ -100,28 +99,24 @@ class MySensorsSwitch(SwitchDevice):
|
|||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
device_attr = {}
|
||||
for value_type, value in self._values.items():
|
||||
if value_type != self.value_type:
|
||||
device_attr[self.gateway.const.SetReq(value_type).name] = value
|
||||
return device_attr
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
data = {
|
||||
attr = {
|
||||
mysensors.ATTR_PORT: self.gateway.port,
|
||||
mysensors.ATTR_NODE_ID: self.node_id,
|
||||
mysensors.ATTR_CHILD_ID: self.child_id,
|
||||
ATTR_BATTERY_LEVEL: self.battery_level,
|
||||
}
|
||||
|
||||
device_attr = self.device_state_attributes
|
||||
set_req = self.gateway.const.SetReq
|
||||
|
||||
if device_attr is not None:
|
||||
data.update(device_attr)
|
||||
|
||||
return data
|
||||
for value_type, value in self._values.items():
|
||||
if value_type != self.value_type:
|
||||
try:
|
||||
attr[set_req(value_type).name] = value
|
||||
except ValueError:
|
||||
_LOGGER.error('value_type %s is not valid for mysensors '
|
||||
'version %s', value_type,
|
||||
self.gateway.version)
|
||||
return attr
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
@ -134,25 +129,33 @@ class MySensorsSwitch(SwitchDevice):
|
|||
"""Turn the switch on."""
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, self.value_type, 1)
|
||||
self._values[self.value_type] = STATE_ON
|
||||
self.update_ha_state()
|
||||
if self.gateway.optimistic:
|
||||
# optimistically assume that switch has changed state
|
||||
self._values[self.value_type] = STATE_ON
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn the switch off."""
|
||||
self.gateway.set_child_value(
|
||||
self.node_id, self.child_id, self.value_type, 0)
|
||||
self._values[self.value_type] = STATE_OFF
|
||||
self.update_ha_state()
|
||||
if self.gateway.optimistic:
|
||||
# optimistically assume that switch has changed state
|
||||
self._values[self.value_type] = STATE_OFF
|
||||
self.update_ha_state()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return self.value_type in self._values
|
||||
|
||||
def update(self):
|
||||
"""Update the controller with the latest value from a sensor."""
|
||||
node = self.gateway.sensors[self.node_id]
|
||||
child = node.children[self.child_id]
|
||||
for value_type, value in child.values.items():
|
||||
_LOGGER.info(
|
||||
_LOGGER.debug(
|
||||
"%s: value_type %s, value = %s", self._name, value_type, value)
|
||||
if value_type == self.gateway.const.SetReq.V_ARMED or \
|
||||
value_type == self.gateway.const.SetReq.V_STATUS or \
|
||||
value_type == self.gateway.const.SetReq.V_LIGHT or \
|
||||
value_type == self.gateway.const.SetReq.V_LOCK_STATUS:
|
||||
self._values[value_type] = (
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue