Merge pull request #1182 from balloob/dev

0.13
pull/1545/head 0.13
Paulus Schoutsen 2016-02-12 21:55:24 -08:00
commit e9fa1f1f83
169 changed files with 11702 additions and 4686 deletions

View File

@ -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 =

View File

@ -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())

View File

@ -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'))

View File

@ -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. """

View File

@ -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()

View File

@ -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()

View File

@ -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]

View File

@ -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

View File

@ -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',

View File

@ -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()

View File

@ -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))
])

View File

@ -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()
})

View File

@ -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. """

View File

@ -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

View File

@ -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):

View File

@ -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()

View File

@ -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)

View File

@ -21,6 +21,7 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
'binary_sensor',
'camera',
'device_tracker',
'garage_door',
'light',
'lock',
'media_player',

View File

@ -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

View File

@ -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)

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by update_mdi script """
VERSION = "a2605736c8d959d50c4bcbba1e6a6aa5"
VERSION = "a1a203680639ff1abcc7b68cdb29c57a"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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:

View File

@ -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())

View File

@ -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 = [
{

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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:

View File

@ -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
}
)

View File

@ -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

View File

@ -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()

View File

@ -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))
])

View File

@ -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

View File

@ -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()

View File

@ -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):

View File

@ -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

View File

@ -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):

View File

@ -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:

View File

@ -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")

View File

@ -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()

View File

@ -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()

View File

@ -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. """

View File

@ -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")

View File

@ -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)

View File

@ -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()

View File

@ -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}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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")

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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])

View File

@ -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. """

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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):

View File

@ -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()

View File

@ -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

View File

@ -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(

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -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)}

View File

@ -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. """

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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. """

View File

@ -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

View File

@ -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 + '%'

View File

@ -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):

View File

@ -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(

View File

@ -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"

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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