mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
Merge branch 'wink_garage_door_support' of https://github.com/xrolfex/home-assistant into wink_garage_door_support
This commit is contained in:
commit
06cb97adee
@ -6,10 +6,14 @@ omit =
|
|||||||
|
|
||||||
# omit pieces of code that rely on external devices being present
|
# omit pieces of code that rely on external devices being present
|
||||||
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
||||||
|
homeassistant/components/alarm_control_panel/nx584.py
|
||||||
|
|
||||||
homeassistant/components/arduino.py
|
homeassistant/components/arduino.py
|
||||||
homeassistant/components/*/arduino.py
|
homeassistant/components/*/arduino.py
|
||||||
|
|
||||||
|
homeassistant/components/bloomsky.py
|
||||||
|
homeassistant/components/*/bloomsky.py
|
||||||
|
|
||||||
homeassistant/components/insteon_hub.py
|
homeassistant/components/insteon_hub.py
|
||||||
homeassistant/components/*/insteon_hub.py
|
homeassistant/components/*/insteon_hub.py
|
||||||
|
|
||||||
@ -102,6 +106,7 @@ omit =
|
|||||||
homeassistant/components/notify/pushbullet.py
|
homeassistant/components/notify/pushbullet.py
|
||||||
homeassistant/components/notify/pushetta.py
|
homeassistant/components/notify/pushetta.py
|
||||||
homeassistant/components/notify/pushover.py
|
homeassistant/components/notify/pushover.py
|
||||||
|
homeassistant/components/notify/rest.py
|
||||||
homeassistant/components/notify/slack.py
|
homeassistant/components/notify/slack.py
|
||||||
homeassistant/components/notify/smtp.py
|
homeassistant/components/notify/smtp.py
|
||||||
homeassistant/components/notify/syslog.py
|
homeassistant/components/notify/syslog.py
|
||||||
|
@ -7,10 +7,12 @@ import sys
|
|||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
import argparse
|
import argparse
|
||||||
|
import time
|
||||||
|
|
||||||
from homeassistant import bootstrap
|
from homeassistant import bootstrap
|
||||||
import homeassistant.config as config_util
|
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():
|
def validate_python():
|
||||||
@ -76,6 +78,11 @@ def get_arguments():
|
|||||||
'--demo-mode',
|
'--demo-mode',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Start Home Assistant in demo mode')
|
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(
|
parser.add_argument(
|
||||||
'--open-ui',
|
'--open-ui',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
@ -207,8 +214,11 @@ def uninstall_osx():
|
|||||||
print("Home Assistant has been uninstalled.")
|
print("Home Assistant has been uninstalled.")
|
||||||
|
|
||||||
|
|
||||||
def setup_and_run_hass(config_dir, args):
|
def setup_and_run_hass(config_dir, args, top_process=False):
|
||||||
""" Setup HASS and run. Block until stopped. """
|
"""
|
||||||
|
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:
|
if args.demo_mode:
|
||||||
config = {
|
config = {
|
||||||
'frontend': {},
|
'frontend': {},
|
||||||
@ -235,7 +245,11 @@ def setup_and_run_hass(config_dir, args):
|
|||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser)
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser)
|
||||||
|
|
||||||
hass.start()
|
hass.start()
|
||||||
sys.exit(int(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):
|
def run_hass_process(hass_proc):
|
||||||
@ -243,8 +257,8 @@ def run_hass_process(hass_proc):
|
|||||||
requested_stop = threading.Event()
|
requested_stop = threading.Event()
|
||||||
hass_proc.daemon = True
|
hass_proc.daemon = True
|
||||||
|
|
||||||
def request_stop():
|
def request_stop(*args):
|
||||||
""" request hass stop """
|
""" request hass stop, *args is for signal handler callback """
|
||||||
requested_stop.set()
|
requested_stop.set()
|
||||||
hass_proc.terminate()
|
hass_proc.terminate()
|
||||||
|
|
||||||
@ -262,7 +276,10 @@ def run_hass_process(hass_proc):
|
|||||||
hass_proc.join()
|
hass_proc.join()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
return False
|
return False
|
||||||
return not requested_stop.isSet() and hass_proc.exitcode == 100
|
|
||||||
|
return (not requested_stop.isSet() and
|
||||||
|
hass_proc.exitcode == RESTART_EXIT_CODE,
|
||||||
|
hass_proc.exitcode)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -277,14 +294,16 @@ def main():
|
|||||||
# os x launchd functions
|
# os x launchd functions
|
||||||
if args.install_osx:
|
if args.install_osx:
|
||||||
install_osx()
|
install_osx()
|
||||||
return
|
return 0
|
||||||
if args.uninstall_osx:
|
if args.uninstall_osx:
|
||||||
uninstall_osx()
|
uninstall_osx()
|
||||||
return
|
return 0
|
||||||
if args.restart_osx:
|
if args.restart_osx:
|
||||||
uninstall_osx()
|
uninstall_osx()
|
||||||
|
# A small delay is needed on some systems to let the unload finish.
|
||||||
|
time.sleep(0.5)
|
||||||
install_osx()
|
install_osx()
|
||||||
return
|
return 0
|
||||||
|
|
||||||
# daemon functions
|
# daemon functions
|
||||||
if args.pid_file:
|
if args.pid_file:
|
||||||
@ -294,12 +313,23 @@ def main():
|
|||||||
if args.pid_file:
|
if args.pid_file:
|
||||||
write_pid(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.
|
# Run hass as child process. Restart if necessary.
|
||||||
keep_running = True
|
keep_running = True
|
||||||
while keep_running:
|
while keep_running:
|
||||||
hass_proc = Process(target=setup_and_run_hass, args=(config_dir, args))
|
hass_proc = Process(target=setup_and_run_hass, args=(config_dir, args))
|
||||||
keep_running = run_hass_process(hass_proc)
|
keep_running, exit_code = run_hass_process(hass_proc)
|
||||||
|
return exit_code
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
sys.exit(main())
|
||||||
|
@ -61,6 +61,8 @@ def setup(hass, config):
|
|||||||
|
|
||||||
for alarm in target_alarms:
|
for alarm in target_alarms:
|
||||||
getattr(alarm, method)(code)
|
getattr(alarm, method)(code)
|
||||||
|
if alarm.should_poll:
|
||||||
|
alarm.update_ha_state(True)
|
||||||
|
|
||||||
descriptions = load_yaml_config_file(
|
descriptions = load_yaml_config_file(
|
||||||
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
||||||
|
@ -90,7 +90,6 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
|||||||
# Open another session to alarm.com to fire off the command
|
# Open another session to alarm.com to fire off the command
|
||||||
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
|
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
|
||||||
_alarm.disarm()
|
_alarm.disarm()
|
||||||
self.update_ha_state()
|
|
||||||
|
|
||||||
def alarm_arm_home(self, code=None):
|
def alarm_arm_home(self, code=None):
|
||||||
""" Send arm home command. """
|
""" Send arm home command. """
|
||||||
@ -100,7 +99,6 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
|||||||
# Open another session to alarm.com to fire off the command
|
# Open another session to alarm.com to fire off the command
|
||||||
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
|
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
|
||||||
_alarm.arm_stay()
|
_alarm.arm_stay()
|
||||||
self.update_ha_state()
|
|
||||||
|
|
||||||
def alarm_arm_away(self, code=None):
|
def alarm_arm_away(self, code=None):
|
||||||
""" Send arm away command. """
|
""" Send arm away command. """
|
||||||
@ -110,7 +108,6 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
|||||||
# Open another session to alarm.com to fire off the command
|
# Open another session to alarm.com to fire off the command
|
||||||
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
|
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
|
||||||
_alarm.arm_away()
|
_alarm.arm_away()
|
||||||
self.update_ha_state()
|
|
||||||
|
|
||||||
def _validate_code(self, code, state):
|
def _validate_code(self, code, state):
|
||||||
""" Validate given code. """
|
""" Validate given code. """
|
||||||
|
105
homeassistant/components/alarm_control_panel/nx584.py
Normal file
105
homeassistant/components/alarm_control_panel/nx584.py
Normal 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()
|
77
homeassistant/components/bloomsky.py
Normal file
77
homeassistant/components/bloomsky.py
Normal 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()
|
||||||
|
})
|
@ -33,8 +33,6 @@ SWITCH_ACTION_SNAPSHOT = 'snapshot'
|
|||||||
|
|
||||||
SERVICE_CAMERA = 'camera_service'
|
SERVICE_CAMERA = 'camera_service'
|
||||||
|
|
||||||
STATE_RECORDING = 'recording'
|
|
||||||
|
|
||||||
DEFAULT_RECORDING_SECONDS = 30
|
DEFAULT_RECORDING_SECONDS = 30
|
||||||
|
|
||||||
# Maps discovered services to their platforms
|
# Maps discovered services to their platforms
|
||||||
@ -46,6 +44,7 @@ DIR_DATETIME_FORMAT = '%Y-%m-%d_%H-%M-%S'
|
|||||||
REC_DIR_PREFIX = 'recording-'
|
REC_DIR_PREFIX = 'recording-'
|
||||||
REC_IMG_PREFIX = 'recording_image-'
|
REC_IMG_PREFIX = 'recording_image-'
|
||||||
|
|
||||||
|
STATE_RECORDING = 'recording'
|
||||||
STATE_STREAMING = 'streaming'
|
STATE_STREAMING = 'streaming'
|
||||||
STATE_IDLE = 'idle'
|
STATE_IDLE = 'idle'
|
||||||
|
|
||||||
@ -121,33 +120,7 @@ def setup(hass, config):
|
|||||||
try:
|
try:
|
||||||
camera.is_streaming = True
|
camera.is_streaming = True
|
||||||
camera.update_ha_state()
|
camera.update_ha_state()
|
||||||
|
camera.mjpeg_stream(handler)
|
||||||
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)
|
|
||||||
|
|
||||||
except (requests.RequestException, IOError):
|
except (requests.RequestException, IOError):
|
||||||
camera.is_streaming = False
|
camera.is_streaming = False
|
||||||
@ -190,6 +163,34 @@ class Camera(Entity):
|
|||||||
""" Return bytes of camera image. """
|
""" Return bytes of camera image. """
|
||||||
raise NotImplementedError()
|
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
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
""" Returns the state of the entity. """
|
""" Returns the state of the entity. """
|
||||||
|
60
homeassistant/components/camera/bloomsky.py
Normal file
60
homeassistant/components/camera/bloomsky.py
Normal 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
|
@ -14,6 +14,9 @@ from requests.auth import HTTPBasicAuth
|
|||||||
|
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
from homeassistant.components.camera import DOMAIN, Camera
|
from homeassistant.components.camera import DOMAIN, Camera
|
||||||
|
from homeassistant.const import HTTP_OK
|
||||||
|
|
||||||
|
CONTENT_TYPE_HEADER = 'Content-Type'
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -41,6 +44,17 @@ class MjpegCamera(Camera):
|
|||||||
self._password = device_info.get('password')
|
self._password = device_info.get('password')
|
||||||
self._mjpeg_url = device_info['mjpeg_url']
|
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):
|
def camera_image(self):
|
||||||
""" Return a still image response from the camera. """
|
""" Return a still image response from the camera. """
|
||||||
|
|
||||||
@ -55,16 +69,22 @@ class MjpegCamera(Camera):
|
|||||||
jpg = data[jpg_start:jpg_end + 2]
|
jpg = data[jpg_start:jpg_end + 2]
|
||||||
return jpg
|
return jpg
|
||||||
|
|
||||||
if self._username and self._password:
|
with closing(self.camera_stream()) as response:
|
||||||
with closing(requests.get(self._mjpeg_url,
|
return process_response(response)
|
||||||
auth=HTTPBasicAuth(self._username,
|
|
||||||
self._password),
|
def mjpeg_stream(self, handler):
|
||||||
stream=True)) as response:
|
""" Generate an HTTP MJPEG stream from the camera. """
|
||||||
return process_response(response)
|
response = self.camera_stream()
|
||||||
else:
|
content_type = response.headers[CONTENT_TYPE_HEADER]
|
||||||
with closing(requests.get(self._mjpeg_url,
|
|
||||||
stream=True)) as response:
|
handler.send_response(HTTP_OK)
|
||||||
return process_response(response)
|
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
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
|
91
homeassistant/components/camera/uvc.py
Normal file
91
homeassistant/components/camera/uvc.py
Normal 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()
|
@ -141,7 +141,7 @@ class Configurator(object):
|
|||||||
|
|
||||||
state = self.hass.states.get(entity_id)
|
state = self.hass.states.get(entity_id)
|
||||||
|
|
||||||
new_data = state.attributes
|
new_data = dict(state.attributes)
|
||||||
new_data[ATTR_ERRORS] = error
|
new_data[ATTR_ERRORS] = error
|
||||||
|
|
||||||
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
|
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "b5daaa4815050f90f6c996a429bfeae1"
|
VERSION = "e310ed31e0c6d96def74b44c90ff5878"
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
Subproject commit 8050e0861586a65317de27dbfd0ff50ffe209731
|
Subproject commit 31fb734c5b080797e81b9143e2db530c70390f5a
|
@ -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)}([/*!*************************************!*\
|
!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 ***!
|
!*** ./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
|
//# sourceMappingURL=service_worker.js.map
|
@ -71,7 +71,7 @@ def expand_entity_ids(hass, entity_ids):
|
|||||||
if domain == DOMAIN:
|
if domain == DOMAIN:
|
||||||
found_ids.extend(
|
found_ids.extend(
|
||||||
ent_id for ent_id
|
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)
|
if ent_id not in found_ids)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -25,7 +25,7 @@ DEFAULT_DATABASE = 'home_assistant'
|
|||||||
DEFAULT_SSL = False
|
DEFAULT_SSL = False
|
||||||
DEFAULT_VERIFY_SSL = False
|
DEFAULT_VERIFY_SSL = False
|
||||||
|
|
||||||
REQUIREMENTS = ['influxdb==2.11.0']
|
REQUIREMENTS = ['influxdb==2.12.0']
|
||||||
|
|
||||||
CONF_HOST = 'host'
|
CONF_HOST = 'host'
|
||||||
CONF_PORT = 'port'
|
CONF_PORT = 'port'
|
||||||
@ -70,25 +70,22 @@ def setup(hass, config):
|
|||||||
""" Listen for new messages on the bus and sends them to Influx. """
|
""" Listen for new messages on the bus and sends them to Influx. """
|
||||||
|
|
||||||
state = event.data.get('new_state')
|
state = event.data.get('new_state')
|
||||||
|
if state is None or state.state in (STATE_UNKNOWN, ''):
|
||||||
if state is None:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON):
|
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON):
|
||||||
_state = 1
|
_state = 1
|
||||||
elif state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
|
elif state.state in (STATE_OFF, STATE_UNLOCKED, STATE_BELOW_HORIZON):
|
||||||
STATE_BELOW_HORIZON):
|
|
||||||
_state = 0
|
_state = 0
|
||||||
else:
|
else:
|
||||||
_state = state.state
|
|
||||||
if _state == '':
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
_state = float(_state)
|
_state = float(state.state)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
_state = state.state
|
||||||
|
|
||||||
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 = [
|
json_body = [
|
||||||
{
|
{
|
||||||
|
@ -300,11 +300,6 @@ class Light(ToggleEntity):
|
|||||||
""" CT color value in mirads. """
|
""" CT color value in mirads. """
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
""" Returns device specific state attributes. """
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
""" Returns optional state attributes. """
|
""" Returns optional state attributes. """
|
||||||
@ -322,9 +317,4 @@ class Light(ToggleEntity):
|
|||||||
data[ATTR_XY_COLOR][0], data[ATTR_XY_COLOR][1],
|
data[ATTR_XY_COLOR][0], data[ATTR_XY_COLOR][1],
|
||||||
data[ATTR_BRIGHTNESS])
|
data[ATTR_BRIGHTNESS])
|
||||||
|
|
||||||
device_attr = self.device_state_attributes
|
|
||||||
|
|
||||||
if device_attr is not None:
|
|
||||||
data.update(device_attr)
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
@ -9,13 +9,20 @@ https://home-assistant.io/components/light.vera/
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from requests.exceptions import RequestException
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -67,17 +74,35 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
add_devices_callback(lights)
|
add_devices_callback(lights)
|
||||||
|
|
||||||
|
|
||||||
class VeraLight(VeraSwitch):
|
class VeraLight(Light):
|
||||||
""" Represents a Vera Light, including dimmable. """
|
""" 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
|
@property
|
||||||
def state_attributes(self):
|
def name(self):
|
||||||
attr = super().state_attributes or {}
|
""" Get the mame of the switch. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def brightness(self):
|
||||||
|
"""Brightness of the light."""
|
||||||
if self.vera_device.is_dimmable:
|
if self.vera_device.is_dimmable:
|
||||||
attr[ATTR_BRIGHTNESS] = self.vera_device.get_brightness()
|
return self.vera_device.get_brightness()
|
||||||
|
|
||||||
return attr
|
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
if ATTR_BRIGHTNESS in kwargs and self.vera_device.is_dimmable:
|
if ATTR_BRIGHTNESS in kwargs and self.vera_device.is_dimmable:
|
||||||
@ -87,3 +112,49 @@ class VeraLight(VeraSwitch):
|
|||||||
|
|
||||||
self._state = STATE_ON
|
self._state = STATE_ON
|
||||||
self.update_ha_state(True)
|
self.update_ha_state(True)
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs):
|
||||||
|
self.vera_device.switch_off()
|
||||||
|
self._state = STATE_OFF
|
||||||
|
self.update_ha_state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
attr = {}
|
||||||
|
|
||||||
|
if self.vera_device.has_battery:
|
||||||
|
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
|
||||||
|
|
||||||
|
if self.vera_device.is_armable:
|
||||||
|
armed = self.vera_device.is_armed
|
||||||
|
attr[ATTR_ARMED] = 'True' if armed else 'False'
|
||||||
|
|
||||||
|
if self.vera_device.is_trippable:
|
||||||
|
last_tripped = self.vera_device.last_trip
|
||||||
|
if last_tripped is not None:
|
||||||
|
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
|
||||||
|
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
|
||||||
|
utc_time)
|
||||||
|
else:
|
||||||
|
attr[ATTR_LAST_TRIP_TIME] = None
|
||||||
|
tripped = self.vera_device.is_tripped
|
||||||
|
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
|
||||||
|
|
||||||
|
attr['Vera Device Id'] = self.vera_device.vera_device_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" Tells Home Assistant not to poll this entity. """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if device is on. """
|
||||||
|
return self._state == STATE_ON
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
""" Called by the vera device callback to update state. """
|
||||||
|
if self.vera_device.is_switched_on():
|
||||||
|
self._state = STATE_ON
|
||||||
|
else:
|
||||||
|
self._state = STATE_OFF
|
||||||
|
@ -8,11 +8,10 @@ https://home-assistant.io/components/light.wink/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.light import ATTR_BRIGHTNESS
|
from homeassistant.components.light import ATTR_BRIGHTNESS, Light
|
||||||
from homeassistant.components.wink import WinkToggleDevice
|
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
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):
|
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())
|
WinkLight(light) for light in pywink.get_bulbs())
|
||||||
|
|
||||||
|
|
||||||
class WinkLight(WinkToggleDevice):
|
class WinkLight(Light):
|
||||||
""" Represents a Wink 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
|
# pylint: disable=too-few-public-methods
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
""" Turns the switch on. """
|
""" Turns the switch on. """
|
||||||
@ -48,14 +70,10 @@ class WinkLight(WinkToggleDevice):
|
|||||||
else:
|
else:
|
||||||
self.wink.set_state(True)
|
self.wink.set_state(True)
|
||||||
|
|
||||||
@property
|
def turn_off(self):
|
||||||
def state_attributes(self):
|
""" Turns the switch off. """
|
||||||
attr = super().state_attributes
|
self.wink.set_state(False)
|
||||||
|
|
||||||
if self.is_on:
|
def update(self):
|
||||||
brightness = self.wink.brightness()
|
""" Update state of the light. """
|
||||||
|
self.wink.update_state()
|
||||||
if brightness is not None:
|
|
||||||
attr[ATTR_BRIGHTNESS] = int(brightness * 255)
|
|
||||||
|
|
||||||
return attr
|
|
||||||
|
@ -17,7 +17,7 @@ from homeassistant.helpers.entity import Entity
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK,
|
STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK,
|
||||||
ATTR_ENTITY_ID)
|
ATTR_ENTITY_ID)
|
||||||
from homeassistant.components import (group, wink)
|
from homeassistant.components import (group, verisure, wink)
|
||||||
|
|
||||||
DOMAIN = 'lock'
|
DOMAIN = 'lock'
|
||||||
SCAN_INTERVAL = 30
|
SCAN_INTERVAL = 30
|
||||||
@ -28,12 +28,15 @@ ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format('all_locks')
|
|||||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
|
|
||||||
ATTR_LOCKED = "locked"
|
ATTR_LOCKED = "locked"
|
||||||
|
ATTR_CODE = 'code'
|
||||||
|
ATTR_CODE_FORMAT = 'code_format'
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
|
|
||||||
# Maps discovered services to their platforms
|
# Maps discovered services to their platforms
|
||||||
DISCOVERY_PLATFORMS = {
|
DISCOVERY_PLATFORMS = {
|
||||||
wink.DISCOVER_LOCKS: 'wink'
|
wink.DISCOVER_LOCKS: 'wink',
|
||||||
|
verisure.DISCOVER_LOCKS: 'verisure'
|
||||||
}
|
}
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -45,15 +48,25 @@ def is_locked(hass, entity_id=None):
|
|||||||
return hass.states.is_state(entity_id, STATE_LOCKED)
|
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. """
|
""" 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)
|
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. """
|
""" 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)
|
hass.services.call(DOMAIN, SERVICE_UNLOCK, data)
|
||||||
|
|
||||||
|
|
||||||
@ -68,11 +81,16 @@ def setup(hass, config):
|
|||||||
""" Handles calls to the lock services. """
|
""" Handles calls to the lock services. """
|
||||||
target_locks = component.extract_from_service(service)
|
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:
|
for item in target_locks:
|
||||||
if service.service == SERVICE_LOCK:
|
if service.service == SERVICE_LOCK:
|
||||||
item.lock()
|
item.lock(code=code)
|
||||||
else:
|
else:
|
||||||
item.unlock()
|
item.unlock(code=code)
|
||||||
|
|
||||||
if item.should_poll:
|
if item.should_poll:
|
||||||
item.update_ha_state(True)
|
item.update_ha_state(True)
|
||||||
@ -91,19 +109,34 @@ class LockDevice(Entity):
|
|||||||
""" Represents a lock within Home Assistant. """
|
""" Represents a lock within Home Assistant. """
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
|
|
||||||
|
@property
|
||||||
|
def code_format(self):
|
||||||
|
""" regex for code format or None if no code is required. """
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_locked(self):
|
def is_locked(self):
|
||||||
""" Is the lock locked or unlocked. """
|
""" Is the lock locked or unlocked. """
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def lock(self):
|
def lock(self, **kwargs):
|
||||||
""" Locks the lock. """
|
""" Locks the lock. """
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def unlock(self):
|
def unlock(self, **kwargs):
|
||||||
""" Unlocks the lock. """
|
""" Unlocks the lock. """
|
||||||
raise NotImplementedError()
|
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
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
locked = self.is_locked
|
locked = self.is_locked
|
||||||
|
92
homeassistant/components/lock/verisure.py
Normal file
92
homeassistant/components/lock/verisure.py
Normal 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()
|
@ -11,7 +11,7 @@ import logging
|
|||||||
from homeassistant.components.lock import LockDevice
|
from homeassistant.components.lock import LockDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
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):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
@ -425,11 +425,6 @@ class MediaPlayerDevice(Entity):
|
|||||||
""" Flags of media commands that are supported. """
|
""" Flags of media commands that are supported. """
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
""" Extra attributes a device wants to expose. """
|
|
||||||
return None
|
|
||||||
|
|
||||||
def turn_on(self):
|
def turn_on(self):
|
||||||
""" turn the media player on. """
|
""" turn the media player on. """
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@ -546,9 +541,4 @@ class MediaPlayerDevice(Entity):
|
|||||||
if self.media_image_url:
|
if self.media_image_url:
|
||||||
state_attr[ATTR_ENTITY_PICTURE] = 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
|
return state_attr
|
||||||
|
@ -56,8 +56,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
hosts = [(config[CONF_HOST], DEFAULT_PORT)]
|
hosts = [(config[CONF_HOST], DEFAULT_PORT)]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
hosts = [host for host in pychromecast.discover_chromecasts()
|
hosts = [tuple(dev[:2]) for dev in pychromecast.discover_chromecasts()
|
||||||
if host not in KNOWN_HOSTS]
|
if tuple(dev[:2]) not in KNOWN_HOSTS]
|
||||||
|
|
||||||
casts = []
|
casts = []
|
||||||
|
|
||||||
|
@ -12,8 +12,10 @@ import socket
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
from homeassistant.config import load_yaml_config_file
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
|
from homeassistant.util import template
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||||
@ -49,24 +51,34 @@ DEFAULT_PROTOCOL = PROTOCOL_311
|
|||||||
|
|
||||||
ATTR_TOPIC = 'topic'
|
ATTR_TOPIC = 'topic'
|
||||||
ATTR_PAYLOAD = 'payload'
|
ATTR_PAYLOAD = 'payload'
|
||||||
|
ATTR_PAYLOAD_TEMPLATE = 'payload_template'
|
||||||
ATTR_QOS = 'qos'
|
ATTR_QOS = 'qos'
|
||||||
ATTR_RETAIN = 'retain'
|
ATTR_RETAIN = 'retain'
|
||||||
|
|
||||||
MAX_RECONNECT_WAIT = 300 # seconds
|
MAX_RECONNECT_WAIT = 300 # seconds
|
||||||
|
|
||||||
|
|
||||||
def publish(hass, topic, payload, qos=None, retain=None):
|
def _build_publish_data(topic, qos, retain):
|
||||||
"""Publish message to an MQTT topic."""
|
"""Build the arguments for the publish service without the payload."""
|
||||||
data = {
|
data = {ATTR_TOPIC: topic}
|
||||||
ATTR_TOPIC: topic,
|
|
||||||
ATTR_PAYLOAD: payload,
|
|
||||||
}
|
|
||||||
if qos is not None:
|
if qos is not None:
|
||||||
data[ATTR_QOS] = qos
|
data[ATTR_QOS] = qos
|
||||||
|
|
||||||
if retain is not None:
|
if retain is not None:
|
||||||
data[ATTR_RETAIN] = retain
|
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)
|
hass.services.call(DOMAIN, SERVICE_PUBLISH, data)
|
||||||
|
|
||||||
|
|
||||||
@ -132,15 +144,34 @@ def setup(hass, config):
|
|||||||
"""Handle MQTT publish service calls."""
|
"""Handle MQTT publish service calls."""
|
||||||
msg_topic = call.data.get(ATTR_TOPIC)
|
msg_topic = call.data.get(ATTR_TOPIC)
|
||||||
payload = call.data.get(ATTR_PAYLOAD)
|
payload = call.data.get(ATTR_PAYLOAD)
|
||||||
|
payload_template = call.data.get(ATTR_PAYLOAD_TEMPLATE)
|
||||||
qos = call.data.get(ATTR_QOS, DEFAULT_QOS)
|
qos = call.data.get(ATTR_QOS, DEFAULT_QOS)
|
||||||
retain = call.data.get(ATTR_RETAIN, DEFAULT_RETAIN)
|
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:
|
if msg_topic is None or payload is None:
|
||||||
return
|
return
|
||||||
MQTT_CLIENT.publish(msg_topic, payload, qos, retain)
|
MQTT_CLIENT.publish(msg_topic, payload, qos, retain)
|
||||||
|
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mqtt)
|
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
|
return True
|
||||||
|
|
||||||
|
29
homeassistant/components/mqtt/services.yaml
Normal file
29
homeassistant/components/mqtt/services.yaml
Normal 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
|
@ -24,7 +24,9 @@ CONF_DEBUG = 'debug'
|
|||||||
CONF_PERSISTENCE = 'persistence'
|
CONF_PERSISTENCE = 'persistence'
|
||||||
CONF_PERSISTENCE_FILE = 'persistence_file'
|
CONF_PERSISTENCE_FILE = 'persistence_file'
|
||||||
CONF_VERSION = 'version'
|
CONF_VERSION = 'version'
|
||||||
|
CONF_BAUD_RATE = 'baud_rate'
|
||||||
DEFAULT_VERSION = '1.4'
|
DEFAULT_VERSION = '1.4'
|
||||||
|
DEFAULT_BAUD_RATE = 115200
|
||||||
|
|
||||||
DOMAIN = 'mysensors'
|
DOMAIN = 'mysensors'
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
@ -60,12 +62,13 @@ def setup(hass, config):
|
|||||||
version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION))
|
version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION))
|
||||||
is_metric = (hass.config.temperature_unit == TEMP_CELCIUS)
|
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."""
|
"""Return gateway after setup of the gateway."""
|
||||||
gateway = mysensors.SerialGateway(port, event_callback=None,
|
gateway = mysensors.SerialGateway(port, event_callback=None,
|
||||||
persistence=persistence,
|
persistence=persistence,
|
||||||
persistence_file=persistence_file,
|
persistence_file=persistence_file,
|
||||||
protocol_version=version)
|
protocol_version=version,
|
||||||
|
baud=baud_rate)
|
||||||
gateway.metric = is_metric
|
gateway.metric = is_metric
|
||||||
gateway.debug = config[DOMAIN].get(CONF_DEBUG, False)
|
gateway.debug = config[DOMAIN].get(CONF_DEBUG, False)
|
||||||
gateway = GatewayWrapper(gateway, version)
|
gateway = GatewayWrapper(gateway, version)
|
||||||
@ -98,8 +101,9 @@ def setup(hass, config):
|
|||||||
persistence_file = gway.get(
|
persistence_file = gway.get(
|
||||||
CONF_PERSISTENCE_FILE,
|
CONF_PERSISTENCE_FILE,
|
||||||
hass.config.path('mysensors{}.pickle'.format(index + 1)))
|
hass.config.path('mysensors{}.pickle'.format(index + 1)))
|
||||||
|
baud_rate = gway.get(CONF_BAUD_RATE, DEFAULT_BAUD_RATE)
|
||||||
GATEWAYS[port] = setup_gateway(
|
GATEWAYS[port] = setup_gateway(
|
||||||
port, persistence, persistence_file, version)
|
port, persistence, persistence_file, version, baud_rate)
|
||||||
|
|
||||||
for (component, discovery_service) in DISCOVERY_COMPONENTS:
|
for (component, discovery_service) in DISCOVERY_COMPONENTS:
|
||||||
# Ensure component is loaded
|
# Ensure component is loaded
|
||||||
|
80
homeassistant/components/notify/rest.py
Normal file
80
homeassistant/components/notify/rest.py
Normal 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)
|
238
homeassistant/components/proximity.py
Normal file
238
homeassistant/components/proximity.py
Normal 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)
|
@ -232,7 +232,7 @@ class Recorder(threading.Thread):
|
|||||||
else:
|
else:
|
||||||
state_domain = state.domain
|
state_domain = state.domain
|
||||||
state_state = state.state
|
state_state = state.state
|
||||||
state_attr = json.dumps(state.attributes)
|
state_attr = json.dumps(dict(state.attributes))
|
||||||
last_changed = state.last_changed
|
last_changed = state.last_changed
|
||||||
last_updated = state.last_updated
|
last_updated = state.last_updated
|
||||||
|
|
||||||
@ -285,7 +285,8 @@ class Recorder(threading.Thread):
|
|||||||
else:
|
else:
|
||||||
return cur.fetchall()
|
return cur.fetchall()
|
||||||
|
|
||||||
except sqlite3.IntegrityError:
|
except (sqlite3.IntegrityError, sqlite3.OperationalError,
|
||||||
|
sqlite3.ProgrammingError):
|
||||||
_LOGGER.exception(
|
_LOGGER.exception(
|
||||||
"Error querying the database using: %s", sql_query)
|
"Error querying the database using: %s", sql_query)
|
||||||
return []
|
return []
|
||||||
@ -329,7 +330,7 @@ class Recorder(threading.Thread):
|
|||||||
migration_id = 0
|
migration_id = 0
|
||||||
|
|
||||||
if migration_id < 1:
|
if migration_id < 1:
|
||||||
self.query("""
|
cur.execute("""
|
||||||
CREATE TABLE recorder_runs (
|
CREATE TABLE recorder_runs (
|
||||||
run_id integer primary key,
|
run_id integer primary key,
|
||||||
start integer,
|
start integer,
|
||||||
@ -338,7 +339,7 @@ class Recorder(threading.Thread):
|
|||||||
created integer)
|
created integer)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.query("""
|
cur.execute("""
|
||||||
CREATE TABLE events (
|
CREATE TABLE events (
|
||||||
event_id integer primary key,
|
event_id integer primary key,
|
||||||
event_type text,
|
event_type text,
|
||||||
@ -346,10 +347,10 @@ class Recorder(threading.Thread):
|
|||||||
origin text,
|
origin text,
|
||||||
created integer)
|
created integer)
|
||||||
""")
|
""")
|
||||||
self.query(
|
cur.execute(
|
||||||
'CREATE INDEX events__event_type ON events(event_type)')
|
'CREATE INDEX events__event_type ON events(event_type)')
|
||||||
|
|
||||||
self.query("""
|
cur.execute("""
|
||||||
CREATE TABLE states (
|
CREATE TABLE states (
|
||||||
state_id integer primary key,
|
state_id integer primary key,
|
||||||
entity_id text,
|
entity_id text,
|
||||||
@ -359,51 +360,51 @@ class Recorder(threading.Thread):
|
|||||||
last_updated integer,
|
last_updated integer,
|
||||||
created 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)
|
save_migration(1)
|
||||||
|
|
||||||
if migration_id < 2:
|
if migration_id < 2:
|
||||||
self.query("""
|
cur.execute("""
|
||||||
ALTER TABLE events
|
ALTER TABLE events
|
||||||
ADD COLUMN time_fired integer
|
ADD COLUMN time_fired integer
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.query('UPDATE events SET time_fired=created')
|
cur.execute('UPDATE events SET time_fired=created')
|
||||||
|
|
||||||
save_migration(2)
|
save_migration(2)
|
||||||
|
|
||||||
if migration_id < 3:
|
if migration_id < 3:
|
||||||
utc_offset = self.utc_offset
|
utc_offset = self.utc_offset
|
||||||
|
|
||||||
self.query("""
|
cur.execute("""
|
||||||
ALTER TABLE recorder_runs
|
ALTER TABLE recorder_runs
|
||||||
ADD COLUMN utc_offset integer
|
ADD COLUMN utc_offset integer
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.query("""
|
cur.execute("""
|
||||||
ALTER TABLE events
|
ALTER TABLE events
|
||||||
ADD COLUMN utc_offset integer
|
ADD COLUMN utc_offset integer
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.query("""
|
cur.execute("""
|
||||||
ALTER TABLE states
|
ALTER TABLE states
|
||||||
ADD COLUMN utc_offset integer
|
ADD COLUMN utc_offset integer
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.query("UPDATE recorder_runs SET utc_offset=?", [utc_offset])
|
cur.execute("UPDATE recorder_runs SET utc_offset=?", [utc_offset])
|
||||||
self.query("UPDATE events SET utc_offset=?", [utc_offset])
|
cur.execute("UPDATE events SET utc_offset=?", [utc_offset])
|
||||||
self.query("UPDATE states SET utc_offset=?", [utc_offset])
|
cur.execute("UPDATE states SET utc_offset=?", [utc_offset])
|
||||||
|
|
||||||
save_migration(3)
|
save_migration(3)
|
||||||
|
|
||||||
if migration_id < 4:
|
if migration_id < 4:
|
||||||
# We had a bug where we did not save utc offset for recorder runs
|
# We had a bug where we did not save utc offset for recorder runs
|
||||||
self.query(
|
cur.execute(
|
||||||
"""UPDATE recorder_runs SET utc_offset=?
|
"""UPDATE recorder_runs SET utc_offset=?
|
||||||
WHERE utc_offset IS NULL""", [self.utc_offset])
|
WHERE utc_offset IS NULL""", [self.utc_offset])
|
||||||
|
|
||||||
self.query("""
|
cur.execute("""
|
||||||
ALTER TABLE states
|
ALTER TABLE states
|
||||||
ADD COLUMN event_id integer
|
ADD COLUMN event_id integer
|
||||||
""")
|
""")
|
||||||
@ -412,25 +413,33 @@ class Recorder(threading.Thread):
|
|||||||
|
|
||||||
if migration_id < 5:
|
if migration_id < 5:
|
||||||
# Add domain so that thermostat graphs look right
|
# Add domain so that thermostat graphs look right
|
||||||
self.query("""
|
try:
|
||||||
ALTER TABLE states
|
cur.execute("""
|
||||||
ADD COLUMN domain text
|
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
|
# populate domain with defaults
|
||||||
rows = self.query("select distinct entity_id from states")
|
cur.execute("""
|
||||||
for row in rows:
|
UPDATE states
|
||||||
entity_id = row[0]
|
set domain=substr(entity_id, 0, instr(entity_id, '.'))
|
||||||
domain = entity_id.split(".")[0]
|
""")
|
||||||
self.query(
|
|
||||||
"UPDATE states set domain=? where entity_id=?",
|
|
||||||
domain, entity_id)
|
|
||||||
|
|
||||||
# add indexes we are going to use a lot on selects
|
# add indexes we are going to use a lot on selects
|
||||||
self.query("""
|
cur.execute("""
|
||||||
CREATE INDEX states__state_changes ON
|
CREATE INDEX states__state_changes ON
|
||||||
states (last_changed, last_updated, entity_id)""")
|
states (last_changed, last_updated, entity_id)""")
|
||||||
self.query("""
|
cur.execute("""
|
||||||
CREATE INDEX states__significant_changes ON
|
CREATE INDEX states__significant_changes ON
|
||||||
states (domain, last_updated, entity_id)""")
|
states (domain, last_updated, entity_id)""")
|
||||||
save_migration(5)
|
save_migration(5)
|
||||||
|
@ -39,6 +39,7 @@ OPTION_TYPES = {
|
|||||||
'miners_revenue_btc': ['Miners revenue', 'BTC'],
|
'miners_revenue_btc': ['Miners revenue', 'BTC'],
|
||||||
'market_price_usd': ['Market price', 'USD']
|
'market_price_usd': ['Market price', 'USD']
|
||||||
}
|
}
|
||||||
|
ICON = 'mdi:currency-btc'
|
||||||
|
|
||||||
# Return cached results if last scan was less then this time ago
|
# Return cached results if last scan was less then this time ago
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
|
||||||
@ -108,6 +109,11 @@ class BitcoinSensor(Entity):
|
|||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
return self._unit_of_measurement
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
""" Icon to use in the frontend, if any. """
|
||||||
|
return ICON
|
||||||
|
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
def update(self):
|
def update(self):
|
||||||
""" Gets the latest data and updates the states. """
|
""" Gets the latest data and updates the states. """
|
||||||
|
104
homeassistant/components/sensor/bloomsky.py
Normal file
104
homeassistant/components/sensor/bloomsky.py
Normal 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
|
@ -54,7 +54,7 @@ class CpuSpeedSensor(Entity):
|
|||||||
return self._unit_of_measurement
|
return self._unit_of_measurement
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def device_state_attributes(self):
|
||||||
""" Returns the state attributes. """
|
""" Returns the state attributes. """
|
||||||
if self.info is not None:
|
if self.info is not None:
|
||||||
return {
|
return {
|
||||||
|
@ -46,7 +46,7 @@ class DemoSensor(Entity):
|
|||||||
return self._unit_of_measurement
|
return self._unit_of_measurement
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def device_state_attributes(self):
|
||||||
""" Returns the state attributes. """
|
""" Returns the state attributes. """
|
||||||
if self._battery:
|
if self._battery:
|
||||||
return {
|
return {
|
||||||
|
@ -23,6 +23,7 @@ SENSOR_TYPES = {
|
|||||||
'temperature': ['Temperature', None],
|
'temperature': ['Temperature', None],
|
||||||
'humidity': ['Humidity', '%']
|
'humidity': ['Humidity', '%']
|
||||||
}
|
}
|
||||||
|
DEFAULT_NAME = "DHT Sensor"
|
||||||
# Return cached results if last scan was less then this time ago
|
# 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
|
# DHT11 is able to deliver data once per second, DHT22 once every two
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
|
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)
|
data = DHTClient(Adafruit_DHT, sensor, pin)
|
||||||
dev = []
|
dev = []
|
||||||
|
name = config.get('name', DEFAULT_NAME)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for variable in config['monitored_conditions']:
|
for variable in config['monitored_conditions']:
|
||||||
if variable not in SENSOR_TYPES:
|
if variable not in SENSOR_TYPES:
|
||||||
_LOGGER.error('Sensor type: "%s" does not exist', variable)
|
_LOGGER.error('Sensor type: "%s" does not exist', variable)
|
||||||
else:
|
else:
|
||||||
dev.append(DHTSensor(data, variable, unit))
|
dev.append(DHTSensor(data, variable, unit, name))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -69,8 +72,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
class DHTSensor(Entity):
|
class DHTSensor(Entity):
|
||||||
""" Implements an DHT sensor. """
|
""" Implements an DHT sensor. """
|
||||||
|
|
||||||
def __init__(self, dht_client, sensor_type, temp_unit):
|
def __init__(self, dht_client, sensor_type, temp_unit, name):
|
||||||
self.client_name = 'DHT sensor'
|
self.client_name = name
|
||||||
self._name = SENSOR_TYPES[sensor_type][0]
|
self._name = SENSOR_TYPES[sensor_type][0]
|
||||||
self.dht_client = dht_client
|
self.dht_client = dht_client
|
||||||
self.temp_unit = temp_unit
|
self.temp_unit = temp_unit
|
||||||
|
@ -63,6 +63,11 @@ class EcobeeSensor(Entity):
|
|||||||
""" Returns the state of the device. """
|
""" Returns the state of the device. """
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Unique id of this sensor."""
|
||||||
|
return "sensor_ecobee_{}_{}".format(self.type, self.index)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
""" Unit of measurement this sensor expresses itself in. """
|
""" Unit of measurement this sensor expresses itself in. """
|
||||||
|
99
homeassistant/components/sensor/mfi.py
Normal file
99
homeassistant/components/sensor/mfi.py
Normal 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()
|
@ -7,7 +7,7 @@ For more details about this platform, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/sensor.mqtt/
|
https://home-assistant.io/components/sensor.mqtt/
|
||||||
"""
|
"""
|
||||||
import logging
|
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.helpers.entity import Entity
|
||||||
from homeassistant.util import template
|
from homeassistant.util import template
|
||||||
import homeassistant.components.mqtt as mqtt
|
import homeassistant.components.mqtt as mqtt
|
||||||
@ -42,7 +42,7 @@ class MqttSensor(Entity):
|
|||||||
""" Represents a sensor that can be updated using MQTT. """
|
""" Represents a sensor that can be updated using MQTT. """
|
||||||
def __init__(self, hass, name, state_topic, qos, unit_of_measurement,
|
def __init__(self, hass, name, state_topic, qos, unit_of_measurement,
|
||||||
value_template):
|
value_template):
|
||||||
self._state = "-"
|
self._state = STATE_UNKNOWN
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._name = name
|
self._name = name
|
||||||
self._state_topic = state_topic
|
self._state_topic = state_topic
|
||||||
|
@ -151,41 +151,39 @@ class MySensorsSensor(Entity):
|
|||||||
self.gateway.const.SetReq.V_VOLTAGE: 'V',
|
self.gateway.const.SetReq.V_VOLTAGE: 'V',
|
||||||
self.gateway.const.SetReq.V_CURRENT: 'A',
|
self.gateway.const.SetReq.V_CURRENT: 'A',
|
||||||
}
|
}
|
||||||
unit_map_v15 = {
|
|
||||||
self.gateway.const.SetReq.V_PERCENTAGE: '%',
|
|
||||||
}
|
|
||||||
if float(self.gateway.version) >= 1.5:
|
if float(self.gateway.version) >= 1.5:
|
||||||
if self.gateway.const.SetReq.V_UNIT_PREFIX in self._values:
|
if self.gateway.const.SetReq.V_UNIT_PREFIX in self._values:
|
||||||
return self._values[
|
return self._values[
|
||||||
self.gateway.const.SetReq.V_UNIT_PREFIX]
|
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)
|
return unit_map.get(self.value_type)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return device specific state attributes."""
|
"""Return device specific state attributes."""
|
||||||
device_attr = {}
|
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 = {
|
|
||||||
mysensors.ATTR_PORT: self.gateway.port,
|
mysensors.ATTR_PORT: self.gateway.port,
|
||||||
mysensors.ATTR_NODE_ID: self.node_id,
|
mysensors.ATTR_NODE_ID: self.node_id,
|
||||||
mysensors.ATTR_CHILD_ID: self.child_id,
|
mysensors.ATTR_CHILD_ID: self.child_id,
|
||||||
ATTR_BATTERY_LEVEL: self.battery_level,
|
ATTR_BATTERY_LEVEL: self.battery_level,
|
||||||
}
|
}
|
||||||
|
|
||||||
device_attr = self.device_state_attributes
|
set_req = self.gateway.const.SetReq
|
||||||
|
|
||||||
if device_attr is not None:
|
for value_type, value in self._values.items():
|
||||||
data.update(device_attr)
|
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):
|
def update(self):
|
||||||
"""Update the controller with the latest values from a sensor."""
|
"""Update the controller with the latest values from a sensor."""
|
||||||
|
@ -21,7 +21,7 @@ SENSOR_TYPES = ['humidity',
|
|||||||
'last_connection',
|
'last_connection',
|
||||||
'battery_level']
|
'battery_level']
|
||||||
|
|
||||||
SENSOR_UNITS = {'humidity': '%', 'battery_level': '%'}
|
SENSOR_UNITS = {'humidity': '%', 'battery_level': 'V'}
|
||||||
|
|
||||||
SENSOR_TEMP_TYPES = ['temperature',
|
SENSOR_TEMP_TYPES = ['temperature',
|
||||||
'target',
|
'target',
|
||||||
|
@ -87,7 +87,7 @@ class RfxtrxSensor(Entity):
|
|||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def device_state_attributes(self):
|
||||||
return self.event.values
|
return self.event.values
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -75,7 +75,7 @@ class SwissPublicTransportSensor(Entity):
|
|||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def device_state_attributes(self):
|
||||||
""" Returns the state attributes. """
|
""" Returns the state attributes. """
|
||||||
if self._times is not None:
|
if self._times is not None:
|
||||||
return {
|
return {
|
||||||
|
@ -113,8 +113,8 @@ class TelldusLiveSensor(Entity):
|
|||||||
return self._value_as_humidity
|
return self._value_as_humidity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def device_state_attributes(self):
|
||||||
attrs = dict()
|
attrs = {}
|
||||||
if self._battery_level is not None:
|
if self._battery_level is not None:
|
||||||
attrs[ATTR_BATTERY_LEVEL] = self._battery_level
|
attrs[ATTR_BATTERY_LEVEL] = self._battery_level
|
||||||
if self._last_updated is not None:
|
if self._last_updated is not None:
|
||||||
|
@ -94,10 +94,7 @@ class SensorTemplate(Entity):
|
|||||||
|
|
||||||
def _update_callback(_event):
|
def _update_callback(_event):
|
||||||
""" Called when the target device changes state. """
|
""" Called when the target device changes state. """
|
||||||
# This can be called before the entity is properly
|
self.update_ha_state(True)
|
||||||
# initialised, so check before updating state,
|
|
||||||
if self.entity_id:
|
|
||||||
self.update_ha_state(True)
|
|
||||||
|
|
||||||
self.hass.bus.listen(EVENT_STATE_CHANGED, _update_callback)
|
self.hass.bus.listen(EVENT_STATE_CHANGED, _update_callback)
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ class TwitchSensor(Entity):
|
|||||||
self._state = STATE_OFFLINE
|
self._state = STATE_OFFLINE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def device_state_attributes(self):
|
||||||
""" Returns the state attributes. """
|
""" Returns the state attributes. """
|
||||||
if self._state == STATE_STREAMING:
|
if self._state == STATE_STREAMING:
|
||||||
return {
|
return {
|
||||||
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
|||||||
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME,
|
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME,
|
||||||
TEMP_CELCIUS, TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_STOP)
|
TEMP_CELCIUS, TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_STOP)
|
||||||
|
|
||||||
REQUIREMENTS = ['pyvera==0.2.7']
|
REQUIREMENTS = ['pyvera==0.2.8']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ class VeraSensor(Entity):
|
|||||||
return '%'
|
return '%'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def device_state_attributes(self):
|
||||||
attr = {}
|
attr = {}
|
||||||
if self.vera_device.has_battery:
|
if self.vera_device.has_battery:
|
||||||
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
|
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
|
||||||
|
@ -11,7 +11,7 @@ import logging
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_OPEN, STATE_CLOSED
|
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):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
@ -100,7 +100,7 @@ class YrSensor(Entity):
|
|||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def device_state_attributes(self):
|
||||||
""" Returns state attributes. """
|
""" Returns state attributes. """
|
||||||
data = {
|
data = {
|
||||||
'about': "Weather forecast from yr.no, delivered by the"
|
'about': "Weather forecast from yr.no, delivered by the"
|
||||||
|
@ -111,11 +111,6 @@ class ZWaveSensor(ZWaveDeviceEntity, Entity):
|
|||||||
""" Returns the state of the sensor. """
|
""" Returns the state of the sensor. """
|
||||||
return self._value.data
|
return self._value.data
|
||||||
|
|
||||||
@property
|
|
||||||
def state_attributes(self):
|
|
||||||
""" Returns optional state attributes. """
|
|
||||||
return self.device_state_attributes
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
return self._value.units
|
return self._value.units
|
||||||
|
@ -3,6 +3,9 @@ homeassistant.components.splunk
|
|||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Splunk component which allows you to send data to an Splunk instance
|
Splunk component which allows you to send data to an Splunk instance
|
||||||
utilizing the HTTP Event Collector.
|
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 json
|
||||||
import logging
|
import logging
|
||||||
|
@ -129,11 +129,6 @@ class SwitchDevice(ToggleEntity):
|
|||||||
""" Is the device in standby. """
|
""" Is the device in standby. """
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
""" Returns device specific state attributes. """
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
""" Returns optional state attributes. """
|
""" Returns optional state attributes. """
|
||||||
@ -144,9 +139,4 @@ class SwitchDevice(ToggleEntity):
|
|||||||
if value:
|
if value:
|
||||||
data[attr] = value
|
data[attr] = value
|
||||||
|
|
||||||
device_attr = self.device_state_attributes
|
|
||||||
|
|
||||||
if device_attr is not None:
|
|
||||||
data.update(device_attr)
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
103
homeassistant/components/switch/mfi.py
Normal file
103
homeassistant/components/switch/mfi.py
Normal 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
|
@ -100,28 +100,24 @@ class MySensorsSwitch(SwitchDevice):
|
|||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return device specific state attributes."""
|
"""Return device specific state attributes."""
|
||||||
device_attr = {}
|
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 = {
|
|
||||||
mysensors.ATTR_PORT: self.gateway.port,
|
mysensors.ATTR_PORT: self.gateway.port,
|
||||||
mysensors.ATTR_NODE_ID: self.node_id,
|
mysensors.ATTR_NODE_ID: self.node_id,
|
||||||
mysensors.ATTR_CHILD_ID: self.child_id,
|
mysensors.ATTR_CHILD_ID: self.child_id,
|
||||||
ATTR_BATTERY_LEVEL: self.battery_level,
|
ATTR_BATTERY_LEVEL: self.battery_level,
|
||||||
}
|
}
|
||||||
|
|
||||||
device_attr = self.device_state_attributes
|
set_req = self.gateway.const.SetReq
|
||||||
|
|
||||||
if device_attr is not None:
|
for value_type, value in self._values.items():
|
||||||
data.update(device_attr)
|
if value_type != self.value_type:
|
||||||
|
try:
|
||||||
return data
|
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
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
@ -144,6 +140,11 @@ class MySensorsSwitch(SwitchDevice):
|
|||||||
self._values[self.value_type] = STATE_OFF
|
self._values[self.value_type] = STATE_OFF
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
return self.value_type in self._values
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update the controller with the latest value from a sensor."""
|
"""Update the controller with the latest value from a sensor."""
|
||||||
node = self.gateway.sensors[self.node_id]
|
node = self.gateway.sensors[self.node_id]
|
||||||
|
@ -8,8 +8,7 @@ https://home-assistant.io/components/switch.tellstick/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP,
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
ATTR_FRIENDLY_NAME)
|
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
|
|
||||||
SIGNAL_REPETITIONS = 1
|
SIGNAL_REPETITIONS = 1
|
||||||
@ -63,7 +62,6 @@ class TellstickSwitchDevice(ToggleEntity):
|
|||||||
import tellcore.constants as tellcore_constants
|
import tellcore.constants as tellcore_constants
|
||||||
|
|
||||||
self.tellstick_device = tellstick_device
|
self.tellstick_device = tellstick_device
|
||||||
self.state_attr = {ATTR_FRIENDLY_NAME: tellstick_device.name}
|
|
||||||
self.signal_repetitions = signal_repetitions
|
self.signal_repetitions = signal_repetitions
|
||||||
|
|
||||||
self.last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
|
self.last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
|
||||||
@ -79,11 +77,6 @@ class TellstickSwitchDevice(ToggleEntity):
|
|||||||
""" Returns the name of the switch if any. """
|
""" Returns the name of the switch if any. """
|
||||||
return self.tellstick_device.name
|
return self.tellstick_device.name
|
||||||
|
|
||||||
@property
|
|
||||||
def state_attributes(self):
|
|
||||||
""" Returns optional state attributes. """
|
|
||||||
return self.state_attr
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
""" True if switch is on. """
|
""" True if switch is on. """
|
||||||
|
162
homeassistant/components/switch/template.py
Normal file
162
homeassistant/components/switch/template.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.switch.template
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Allows the creation of a switch that integrates other components together
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/switch.template/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.helpers.entity import generate_entity_id
|
||||||
|
|
||||||
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
|
||||||
|
from homeassistant.core import EVENT_STATE_CHANGED
|
||||||
|
from homeassistant.const import (
|
||||||
|
STATE_ON,
|
||||||
|
STATE_OFF,
|
||||||
|
ATTR_FRIENDLY_NAME,
|
||||||
|
CONF_VALUE_TEMPLATE)
|
||||||
|
|
||||||
|
from homeassistant.helpers.service import call_from_config
|
||||||
|
from homeassistant.util import template, slugify
|
||||||
|
from homeassistant.exceptions import TemplateError
|
||||||
|
from homeassistant.components.switch import DOMAIN
|
||||||
|
|
||||||
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CONF_SWITCHES = 'switches'
|
||||||
|
|
||||||
|
STATE_ERROR = 'error'
|
||||||
|
|
||||||
|
ON_ACTION = 'turn_on'
|
||||||
|
OFF_ACTION = 'turn_off'
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the switches. """
|
||||||
|
|
||||||
|
switches = []
|
||||||
|
if config.get(CONF_SWITCHES) is None:
|
||||||
|
_LOGGER.error("Missing configuration data for switch platform")
|
||||||
|
return False
|
||||||
|
|
||||||
|
for device, device_config in config[CONF_SWITCHES].items():
|
||||||
|
|
||||||
|
if device != slugify(device):
|
||||||
|
_LOGGER.error("Found invalid key for switch.template: %s. "
|
||||||
|
"Use %s instead", device, slugify(device))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not isinstance(device_config, dict):
|
||||||
|
_LOGGER.error("Missing configuration data for switch %s", device)
|
||||||
|
continue
|
||||||
|
|
||||||
|
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
||||||
|
state_template = device_config.get(CONF_VALUE_TEMPLATE)
|
||||||
|
on_action = device_config.get(ON_ACTION)
|
||||||
|
off_action = device_config.get(OFF_ACTION)
|
||||||
|
if state_template is None:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Missing %s for switch %s", CONF_VALUE_TEMPLATE, device)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if on_action is None or off_action is None:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Missing action for switch %s", device)
|
||||||
|
continue
|
||||||
|
|
||||||
|
switches.append(
|
||||||
|
SwitchTemplate(
|
||||||
|
hass,
|
||||||
|
device,
|
||||||
|
friendly_name,
|
||||||
|
state_template,
|
||||||
|
on_action,
|
||||||
|
off_action)
|
||||||
|
)
|
||||||
|
if not switches:
|
||||||
|
_LOGGER.error("No switches added")
|
||||||
|
return False
|
||||||
|
add_devices(switches)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class SwitchTemplate(SwitchDevice):
|
||||||
|
""" Represents a Template Switch. """
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
def __init__(self,
|
||||||
|
hass,
|
||||||
|
device_id,
|
||||||
|
friendly_name,
|
||||||
|
state_template,
|
||||||
|
on_action,
|
||||||
|
off_action):
|
||||||
|
|
||||||
|
self.entity_id = generate_entity_id(
|
||||||
|
ENTITY_ID_FORMAT, device_id,
|
||||||
|
hass=hass)
|
||||||
|
|
||||||
|
self.hass = hass
|
||||||
|
self._name = friendly_name
|
||||||
|
self._template = state_template
|
||||||
|
self._on_action = on_action
|
||||||
|
self._off_action = off_action
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def _update_callback(_event):
|
||||||
|
""" Called when the target device changes state. """
|
||||||
|
self.update_ha_state(True)
|
||||||
|
|
||||||
|
self.hass.bus.listen(EVENT_STATE_CHANGED, _update_callback)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the device. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" Tells Home Assistant not to poll this entity. """
|
||||||
|
return False
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
""" Fires the on action. """
|
||||||
|
call_from_config(self.hass, self._on_action, True)
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs):
|
||||||
|
""" Fires the off action. """
|
||||||
|
call_from_config(self.hass, self._off_action, True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if device is on. """
|
||||||
|
return self._value.lower() == 'true' or self._value == STATE_ON
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_off(self):
|
||||||
|
""" True if device is off. """
|
||||||
|
return self._value.lower() == 'false' or self._value == STATE_OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
return self.is_on or self.is_off
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
""" Updates the state from the template. """
|
||||||
|
try:
|
||||||
|
self._value = template.render(self.hass, self._template)
|
||||||
|
if not self.available:
|
||||||
|
_LOGGER.error(
|
||||||
|
"`%s` is not a switch state, setting %s to unavailable",
|
||||||
|
self._value, self.entity_id)
|
||||||
|
|
||||||
|
except TemplateError as ex:
|
||||||
|
self._value = STATE_ERROR
|
||||||
|
_LOGGER.error(ex)
|
@ -21,7 +21,7 @@ from homeassistant.const import (
|
|||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_OFF)
|
STATE_OFF)
|
||||||
|
|
||||||
REQUIREMENTS = ['pyvera==0.2.7']
|
REQUIREMENTS = ['pyvera==0.2.8']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -102,8 +102,8 @@ class VeraSwitch(SwitchDevice):
|
|||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def device_state_attributes(self):
|
||||||
attr = super().state_attributes or {}
|
attr = {}
|
||||||
|
|
||||||
if self.vera_device.has_battery:
|
if self.vera_device.has_battery:
|
||||||
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
|
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
|
||||||
|
@ -12,7 +12,7 @@ from homeassistant.components.switch import SwitchDevice
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, STATE_OFF, STATE_STANDBY, EVENT_HOMEASSISTANT_STOP)
|
STATE_ON, STATE_OFF, STATE_STANDBY, EVENT_HOMEASSISTANT_STOP)
|
||||||
|
|
||||||
REQUIREMENTS = ['pywemo==0.3.8']
|
REQUIREMENTS = ['pywemo==0.3.9']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
_WEMO_SUBSCRIPTION_REGISTRY = None
|
_WEMO_SUBSCRIPTION_REGISTRY = None
|
||||||
@ -95,8 +95,8 @@ class WemoSwitch(SwitchDevice):
|
|||||||
return self.wemo.name
|
return self.wemo.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def device_state_attributes(self):
|
||||||
attr = super().state_attributes or {}
|
attr = {}
|
||||||
if self.maker_params:
|
if self.maker_params:
|
||||||
# Is the maker sensor on or off.
|
# Is the maker sensor on or off.
|
||||||
if self.maker_params['hassensor']:
|
if self.maker_params['hassensor']:
|
||||||
@ -153,6 +153,18 @@ class WemoSwitch(SwitchDevice):
|
|||||||
""" True if switch is on. """
|
""" True if switch is on. """
|
||||||
return self.wemo.get_state()
|
return self.wemo.get_state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
""" True if switch is available. """
|
||||||
|
if (self.wemo.model_name == 'Insight' and
|
||||||
|
self.insight_params is None):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (self.wemo.model_name == 'Maker' and
|
||||||
|
self.maker_params is None):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
""" Turns the switch on. """
|
""" Turns the switch on. """
|
||||||
self.wemo.on()
|
self.wemo.on()
|
||||||
|
@ -11,7 +11,7 @@ import logging
|
|||||||
from homeassistant.components.wink import WinkToggleDevice
|
from homeassistant.components.wink import WinkToggleDevice
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
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):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
@ -178,11 +178,6 @@ class ThermostatDevice(Entity):
|
|||||||
""" Returns the current state. """
|
""" Returns the current state. """
|
||||||
return self.target_temperature or STATE_UNKNOWN
|
return self.target_temperature or STATE_UNKNOWN
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
""" Returns device specific state attributes. """
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
""" Returns optional state attributes. """
|
""" Returns optional state attributes. """
|
||||||
@ -211,11 +206,6 @@ class ThermostatDevice(Entity):
|
|||||||
if is_fan_on is not None:
|
if is_fan_on is not None:
|
||||||
data[ATTR_FAN] = STATE_ON if is_fan_on else STATE_OFF
|
data[ATTR_FAN] = STATE_ON if is_fan_on else STATE_OFF
|
||||||
|
|
||||||
device_attr = self.device_state_attributes
|
|
||||||
|
|
||||||
if device_attr is not None:
|
|
||||||
data.update(device_attr)
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -9,33 +9,32 @@ https://home-assistant.io/components/thermostat.honeywell/
|
|||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
from homeassistant.components.thermostat import ThermostatDevice
|
from homeassistant.components.thermostat import ThermostatDevice
|
||||||
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS)
|
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS,
|
||||||
|
TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
REQUIREMENTS = ['evohomeclient==0.2.4']
|
REQUIREMENTS = ['evohomeclient==0.2.4']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_AWAY_TEMP = "away_temperature"
|
CONF_AWAY_TEMP = "away_temperature"
|
||||||
|
US_SYSTEM_SWITCH_POSITIONS = {1: 'Heat',
|
||||||
|
2: 'Off',
|
||||||
|
3: 'Cool'}
|
||||||
|
US_BASEURL = 'https://mytotalconnectcomfort.com/portal'
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
def _setup_round(username, password, config, add_devices):
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
|
||||||
""" Sets up the honeywel thermostat. """
|
|
||||||
from evohomeclient import EvohomeClient
|
from evohomeclient import EvohomeClient
|
||||||
|
|
||||||
username = config.get(CONF_USERNAME)
|
|
||||||
password = config.get(CONF_PASSWORD)
|
|
||||||
try:
|
try:
|
||||||
away_temp = float(config.get(CONF_AWAY_TEMP, 16))
|
away_temp = float(config.get(CONF_AWAY_TEMP, 16))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
_LOGGER.error("value entered for item %s should convert to a number",
|
_LOGGER.error("value entered for item %s should convert to a number",
|
||||||
CONF_AWAY_TEMP)
|
CONF_AWAY_TEMP)
|
||||||
return False
|
return False
|
||||||
if username is None or password is None:
|
|
||||||
_LOGGER.error("Missing required configuration items %s or %s",
|
|
||||||
CONF_USERNAME, CONF_PASSWORD)
|
|
||||||
return False
|
|
||||||
|
|
||||||
evo_api = EvohomeClient(username, password)
|
evo_api = EvohomeClient(username, password)
|
||||||
|
|
||||||
@ -53,6 +52,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Sets up the honeywel thermostat. """
|
||||||
|
username = config.get(CONF_USERNAME)
|
||||||
|
password = config.get(CONF_PASSWORD)
|
||||||
|
thermostat_id = config.get('id')
|
||||||
|
|
||||||
|
if username is None or password is None:
|
||||||
|
_LOGGER.error("Missing required configuration items %s or %s",
|
||||||
|
CONF_USERNAME, CONF_PASSWORD)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if thermostat_id:
|
||||||
|
add_devices([HoneywellUSThermostat(thermostat_id, username, password)])
|
||||||
|
else:
|
||||||
|
return _setup_round(username, password, config, add_devices)
|
||||||
|
|
||||||
|
|
||||||
class RoundThermostat(ThermostatDevice):
|
class RoundThermostat(ThermostatDevice):
|
||||||
""" Represents a Honeywell Round Connected thermostat. """
|
""" Represents a Honeywell Round Connected thermostat. """
|
||||||
|
|
||||||
@ -135,3 +152,117 @@ class RoundThermostat(ThermostatDevice):
|
|||||||
else:
|
else:
|
||||||
self._name = data['name']
|
self._name = data['name']
|
||||||
self._is_dhw = False
|
self._is_dhw = False
|
||||||
|
|
||||||
|
|
||||||
|
class HoneywellUSThermostat(ThermostatDevice):
|
||||||
|
""" Represents a Honeywell US Thermostat. """
|
||||||
|
|
||||||
|
def __init__(self, ident, username, password):
|
||||||
|
self._ident = ident
|
||||||
|
self._username = username
|
||||||
|
self._password = password
|
||||||
|
self._session = requests.Session()
|
||||||
|
# Maybe this should be configurable?
|
||||||
|
self._timeout = 30
|
||||||
|
# Yeah, really.
|
||||||
|
self._session.headers['X-Requested-With'] = 'XMLHttpRequest'
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
self._session.get(US_BASEURL, timeout=self._timeout)
|
||||||
|
params = {'UserName': self._username,
|
||||||
|
'Password': self._password,
|
||||||
|
'RememberMe': 'false',
|
||||||
|
'timeOffset': 480}
|
||||||
|
resp = self._session.post(US_BASEURL, params=params,
|
||||||
|
timeout=self._timeout)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
_LOGGER('Login failed for user %(user)s',
|
||||||
|
dict(user=self._username))
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _get_data(self):
|
||||||
|
if not self._login():
|
||||||
|
return
|
||||||
|
url = '%s/Device/CheckDataSession/%s' % (US_BASEURL, self._ident)
|
||||||
|
resp = self._session.get(url, timeout=self._timeout)
|
||||||
|
if resp.status_code < 300:
|
||||||
|
return resp.json()
|
||||||
|
else:
|
||||||
|
return {'error': resp.status_code}
|
||||||
|
|
||||||
|
def _set_data(self, data):
|
||||||
|
if not self._login():
|
||||||
|
return
|
||||||
|
url = '%s/Device/SubmitControlScreenChanges' % US_BASEURL
|
||||||
|
data['DeviceID'] = self._ident
|
||||||
|
resp = self._session.post(url, data=data, timeout=self._timeout)
|
||||||
|
if resp.status_code < 300:
|
||||||
|
return resp.json()
|
||||||
|
else:
|
||||||
|
return {'error': resp.status_code}
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
self._data = self._get_data()['latestData']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_fan_on(self):
|
||||||
|
return self._data['fanData']['fanIsRunning']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return 'honeywell'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
unit = self._data['uiData']['DisplayUnits']
|
||||||
|
if unit == 'F':
|
||||||
|
return TEMP_FAHRENHEIT
|
||||||
|
else:
|
||||||
|
return TEMP_CELCIUS
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temperature(self):
|
||||||
|
self._update()
|
||||||
|
return self._data['uiData']['DispTemperature']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature(self):
|
||||||
|
setpoint = US_SYSTEM_SWITCH_POSITIONS.get(
|
||||||
|
self._data['uiData']['SystemSwitchPosition'],
|
||||||
|
'Off')
|
||||||
|
return self._data['uiData']['%sSetpoint' % setpoint]
|
||||||
|
|
||||||
|
def set_temperature(self, temperature):
|
||||||
|
""" Set target temperature. """
|
||||||
|
data = {'SystemSwitch': None,
|
||||||
|
'HeatSetpoint': None,
|
||||||
|
'CoolSetpoint': None,
|
||||||
|
'HeatNextPeriod': None,
|
||||||
|
'CoolNextPeriod': None,
|
||||||
|
'StatusHeat': None,
|
||||||
|
'StatusCool': None,
|
||||||
|
'FanMode': None}
|
||||||
|
setpoint = US_SYSTEM_SWITCH_POSITIONS.get(
|
||||||
|
self._data['uiData']['SystemSwitchPosition'],
|
||||||
|
'Off')
|
||||||
|
data['%sSetpoint' % setpoint] = temperature
|
||||||
|
self._set_data(data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
""" Return device specific state attributes. """
|
||||||
|
fanmodes = {0: "auto",
|
||||||
|
1: "on",
|
||||||
|
2: "circulate"}
|
||||||
|
return {"fan": (self._data['fanData']['fanIsRunning'] and
|
||||||
|
'running' or 'idle'),
|
||||||
|
"fanmode": fanmodes[self._data['fanData']['fanMode']]}
|
||||||
|
|
||||||
|
def turn_away_mode_on(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def turn_away_mode_off(self):
|
||||||
|
pass
|
||||||
|
@ -26,6 +26,7 @@ DOMAIN = "verisure"
|
|||||||
DISCOVER_SENSORS = 'verisure.sensors'
|
DISCOVER_SENSORS = 'verisure.sensors'
|
||||||
DISCOVER_SWITCHES = 'verisure.switches'
|
DISCOVER_SWITCHES = 'verisure.switches'
|
||||||
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
|
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
|
||||||
|
DISCOVER_LOCKS = 'verisure.lock'
|
||||||
|
|
||||||
DEPENDENCIES = ['alarm_control_panel']
|
DEPENDENCIES = ['alarm_control_panel']
|
||||||
REQUIREMENTS = ['vsure==0.5.0']
|
REQUIREMENTS = ['vsure==0.5.0']
|
||||||
@ -36,6 +37,7 @@ MY_PAGES = None
|
|||||||
ALARM_STATUS = {}
|
ALARM_STATUS = {}
|
||||||
SMARTPLUG_STATUS = {}
|
SMARTPLUG_STATUS = {}
|
||||||
CLIMATE_STATUS = {}
|
CLIMATE_STATUS = {}
|
||||||
|
LOCK_STATUS = {}
|
||||||
|
|
||||||
VERISURE_LOGIN_ERROR = None
|
VERISURE_LOGIN_ERROR = None
|
||||||
VERISURE_ERROR = None
|
VERISURE_ERROR = None
|
||||||
@ -44,6 +46,7 @@ SHOW_THERMOMETERS = True
|
|||||||
SHOW_HYGROMETERS = True
|
SHOW_HYGROMETERS = True
|
||||||
SHOW_ALARM = True
|
SHOW_ALARM = True
|
||||||
SHOW_SMARTPLUGS = True
|
SHOW_SMARTPLUGS = True
|
||||||
|
SHOW_LOCKS = True
|
||||||
CODE_DIGITS = 4
|
CODE_DIGITS = 4
|
||||||
|
|
||||||
# if wrong password was given don't try again
|
# if wrong password was given don't try again
|
||||||
@ -63,11 +66,12 @@ def setup(hass, config):
|
|||||||
from verisure import MyPages, LoginError, Error
|
from verisure import MyPages, LoginError, Error
|
||||||
|
|
||||||
global SHOW_THERMOMETERS, SHOW_HYGROMETERS,\
|
global SHOW_THERMOMETERS, SHOW_HYGROMETERS,\
|
||||||
SHOW_ALARM, SHOW_SMARTPLUGS, CODE_DIGITS
|
SHOW_ALARM, SHOW_SMARTPLUGS, SHOW_LOCKS, CODE_DIGITS
|
||||||
SHOW_THERMOMETERS = int(config[DOMAIN].get('thermometers', '1'))
|
SHOW_THERMOMETERS = int(config[DOMAIN].get('thermometers', '1'))
|
||||||
SHOW_HYGROMETERS = int(config[DOMAIN].get('hygrometers', '1'))
|
SHOW_HYGROMETERS = int(config[DOMAIN].get('hygrometers', '1'))
|
||||||
SHOW_ALARM = int(config[DOMAIN].get('alarm', '1'))
|
SHOW_ALARM = int(config[DOMAIN].get('alarm', '1'))
|
||||||
SHOW_SMARTPLUGS = int(config[DOMAIN].get('smartplugs', '1'))
|
SHOW_SMARTPLUGS = int(config[DOMAIN].get('smartplugs', '1'))
|
||||||
|
SHOW_LOCKS = int(config[DOMAIN].get('locks', '1'))
|
||||||
CODE_DIGITS = int(config[DOMAIN].get('code_digits', '4'))
|
CODE_DIGITS = int(config[DOMAIN].get('code_digits', '4'))
|
||||||
|
|
||||||
global MY_PAGES
|
global MY_PAGES
|
||||||
@ -87,11 +91,13 @@ def setup(hass, config):
|
|||||||
update_alarm()
|
update_alarm()
|
||||||
update_climate()
|
update_climate()
|
||||||
update_smartplug()
|
update_smartplug()
|
||||||
|
update_lock()
|
||||||
|
|
||||||
# Load components for the devices in the ISY controller that we support
|
# Load components for the devices in the ISY controller that we support
|
||||||
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
|
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
|
||||||
('switch', DISCOVER_SWITCHES),
|
('switch', DISCOVER_SWITCHES),
|
||||||
('alarm_control_panel', DISCOVER_ALARMS))):
|
('alarm_control_panel', DISCOVER_ALARMS),
|
||||||
|
('lock', DISCOVER_LOCKS))):
|
||||||
component = get_component(comp_name)
|
component = get_component(comp_name)
|
||||||
_LOGGER.info(config[DOMAIN])
|
_LOGGER.info(config[DOMAIN])
|
||||||
bootstrap.setup_component(hass, component.DOMAIN, config)
|
bootstrap.setup_component(hass, component.DOMAIN, config)
|
||||||
@ -134,6 +140,11 @@ def update_smartplug():
|
|||||||
update_component(MY_PAGES.smartplug.get, SMARTPLUG_STATUS)
|
update_component(MY_PAGES.smartplug.get, SMARTPLUG_STATUS)
|
||||||
|
|
||||||
|
|
||||||
|
def update_lock():
|
||||||
|
""" Updates the status of alarms. """
|
||||||
|
update_component(MY_PAGES.lock.get, LOCK_STATUS)
|
||||||
|
|
||||||
|
|
||||||
def update_component(get_function, status):
|
def update_component(get_function, status):
|
||||||
""" Updates the status of verisure components. """
|
""" Updates the status of verisure components. """
|
||||||
if WRONG_PASSWORD_GIVEN:
|
if WRONG_PASSWORD_GIVEN:
|
||||||
|
@ -13,10 +13,10 @@ from homeassistant.helpers import validate_config
|
|||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_PLATFORM_DISCOVERED, CONF_ACCESS_TOKEN,
|
EVENT_PLATFORM_DISCOVERED, CONF_ACCESS_TOKEN,
|
||||||
ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME)
|
ATTR_SERVICE, ATTR_DISCOVERED)
|
||||||
|
|
||||||
DOMAIN = "wink"
|
DOMAIN = "wink"
|
||||||
REQUIREMENTS = ['python-wink==0.4.2']
|
REQUIREMENTS = ['python-wink==0.5.0']
|
||||||
|
|
||||||
DISCOVER_LIGHTS = "wink.lights"
|
DISCOVER_LIGHTS = "wink.lights"
|
||||||
DISCOVER_SWITCHES = "wink.switches"
|
DISCOVER_SWITCHES = "wink.switches"
|
||||||
@ -82,13 +82,6 @@ class WinkToggleDevice(ToggleEntity):
|
|||||||
""" True if light is on. """
|
""" True if light is on. """
|
||||||
return self.wink.state()
|
return self.wink.state()
|
||||||
|
|
||||||
@property
|
|
||||||
def state_attributes(self):
|
|
||||||
""" Returns optional state attributes. """
|
|
||||||
return {
|
|
||||||
ATTR_FRIENDLY_NAME: self.wink.name()
|
|
||||||
}
|
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
""" Turns the switch on. """
|
""" Turns the switch on. """
|
||||||
self.wink.set_state(True)
|
self.wink.set_state(True)
|
||||||
|
@ -200,3 +200,6 @@ HTTP_HEADER_EXPIRES = "Expires"
|
|||||||
CONTENT_TYPE_JSON = "application/json"
|
CONTENT_TYPE_JSON = "application/json"
|
||||||
CONTENT_TYPE_MULTIPART = 'multipart/x-mixed-replace; boundary={}'
|
CONTENT_TYPE_MULTIPART = 'multipart/x-mixed-replace; boundary={}'
|
||||||
CONTENT_TYPE_TEXT_PLAIN = 'text/plain'
|
CONTENT_TYPE_TEXT_PLAIN = 'text/plain'
|
||||||
|
|
||||||
|
# The exit code to send to request a restart
|
||||||
|
RESTART_EXIT_CODE = 100
|
||||||
|
@ -10,9 +10,9 @@ import time
|
|||||||
import logging
|
import logging
|
||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
|
from types import MappingProxyType
|
||||||
import enum
|
import enum
|
||||||
import functools as ft
|
import functools as ft
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
__version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
__version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
||||||
@ -20,7 +20,8 @@ from homeassistant.const import (
|
|||||||
EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
|
||||||
EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL,
|
EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL,
|
||||||
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
|
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
|
||||||
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME, ATTR_SERVICE_DATA)
|
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME, ATTR_SERVICE_DATA,
|
||||||
|
RESTART_EXIT_CODE)
|
||||||
from homeassistant.exceptions import (
|
from homeassistant.exceptions import (
|
||||||
HomeAssistantError, InvalidEntityFormatError)
|
HomeAssistantError, InvalidEntityFormatError)
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
@ -45,12 +46,6 @@ MIN_WORKER_THREAD = 2
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Temporary to support deprecated methods
|
|
||||||
_MockHA = namedtuple("MockHomeAssistant", ['bus'])
|
|
||||||
|
|
||||||
# The exit code to send to request a restart
|
|
||||||
RESTART_EXIT_CODE = 100
|
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistant(object):
|
class HomeAssistant(object):
|
||||||
"""Root object of the Home Assistant home automation."""
|
"""Root object of the Home Assistant home automation."""
|
||||||
@ -97,7 +92,10 @@ class HomeAssistant(object):
|
|||||||
'Could not bind to SIGTERM. Are you running in a thread?')
|
'Could not bind to SIGTERM. Are you running in a thread?')
|
||||||
|
|
||||||
while not request_shutdown.isSet():
|
while not request_shutdown.isSet():
|
||||||
time.sleep(1)
|
try:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
break
|
||||||
|
|
||||||
self.stop()
|
self.stop()
|
||||||
return RESTART_EXIT_CODE if request_restart.isSet() else 0
|
return RESTART_EXIT_CODE if request_restart.isSet() else 0
|
||||||
@ -113,46 +111,6 @@ class HomeAssistant(object):
|
|||||||
|
|
||||||
self.pool.stop()
|
self.pool.stop()
|
||||||
|
|
||||||
def track_point_in_time(self, action, point_in_time):
|
|
||||||
"""Deprecated method as of 8/4/2015 to track point in time."""
|
|
||||||
_LOGGER.warning(
|
|
||||||
'hass.track_point_in_time is deprecated. '
|
|
||||||
'Please use homeassistant.helpers.event.track_point_in_time')
|
|
||||||
import homeassistant.helpers.event as helper
|
|
||||||
helper.track_point_in_time(self, action, point_in_time)
|
|
||||||
|
|
||||||
def track_point_in_utc_time(self, action, point_in_time):
|
|
||||||
"""Deprecated method as of 8/4/2015 to track point in UTC time."""
|
|
||||||
_LOGGER.warning(
|
|
||||||
'hass.track_point_in_utc_time is deprecated. '
|
|
||||||
'Please use homeassistant.helpers.event.track_point_in_utc_time')
|
|
||||||
import homeassistant.helpers.event as helper
|
|
||||||
helper.track_point_in_utc_time(self, action, point_in_time)
|
|
||||||
|
|
||||||
def track_utc_time_change(self, action,
|
|
||||||
year=None, month=None, day=None,
|
|
||||||
hour=None, minute=None, second=None):
|
|
||||||
"""Deprecated method as of 8/4/2015 to track UTC time change."""
|
|
||||||
# pylint: disable=too-many-arguments
|
|
||||||
_LOGGER.warning(
|
|
||||||
'hass.track_utc_time_change is deprecated. '
|
|
||||||
'Please use homeassistant.helpers.event.track_utc_time_change')
|
|
||||||
import homeassistant.helpers.event as helper
|
|
||||||
helper.track_utc_time_change(self, action, year, month, day, hour,
|
|
||||||
minute, second)
|
|
||||||
|
|
||||||
def track_time_change(self, action,
|
|
||||||
year=None, month=None, day=None,
|
|
||||||
hour=None, minute=None, second=None, utc=False):
|
|
||||||
"""Deprecated method as of 8/4/2015 to track time change."""
|
|
||||||
# pylint: disable=too-many-arguments
|
|
||||||
_LOGGER.warning(
|
|
||||||
'hass.track_time_change is deprecated. '
|
|
||||||
'Please use homeassistant.helpers.event.track_time_change')
|
|
||||||
import homeassistant.helpers.event as helper
|
|
||||||
helper.track_time_change(self, action, year, month, day, hour,
|
|
||||||
minute, second)
|
|
||||||
|
|
||||||
|
|
||||||
class JobPriority(util.OrderedEnum):
|
class JobPriority(util.OrderedEnum):
|
||||||
"""Provides job priorities for event bus jobs."""
|
"""Provides job priorities for event bus jobs."""
|
||||||
@ -352,7 +310,7 @@ class State(object):
|
|||||||
|
|
||||||
self.entity_id = entity_id.lower()
|
self.entity_id = entity_id.lower()
|
||||||
self.state = state
|
self.state = state
|
||||||
self.attributes = attributes or {}
|
self.attributes = MappingProxyType(attributes or {})
|
||||||
self.last_updated = dt_util.strip_microseconds(
|
self.last_updated = dt_util.strip_microseconds(
|
||||||
last_updated or dt_util.utcnow())
|
last_updated or dt_util.utcnow())
|
||||||
|
|
||||||
@ -380,12 +338,6 @@ class State(object):
|
|||||||
self.attributes.get(ATTR_FRIENDLY_NAME) or
|
self.attributes.get(ATTR_FRIENDLY_NAME) or
|
||||||
self.object_id.replace('_', ' '))
|
self.object_id.replace('_', ' '))
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
"""Return a copy of the state."""
|
|
||||||
return State(self.entity_id, self.state,
|
|
||||||
dict(self.attributes), self.last_changed,
|
|
||||||
self.last_updated)
|
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
"""Return a dict representation of the State.
|
"""Return a dict representation of the State.
|
||||||
|
|
||||||
@ -394,7 +346,7 @@ class State(object):
|
|||||||
"""
|
"""
|
||||||
return {'entity_id': self.entity_id,
|
return {'entity_id': self.entity_id,
|
||||||
'state': self.state,
|
'state': self.state,
|
||||||
'attributes': self.attributes,
|
'attributes': dict(self.attributes),
|
||||||
'last_changed': dt_util.datetime_to_str(self.last_changed),
|
'last_changed': dt_util.datetime_to_str(self.last_changed),
|
||||||
'last_updated': dt_util.datetime_to_str(self.last_updated)}
|
'last_updated': dt_util.datetime_to_str(self.last_updated)}
|
||||||
|
|
||||||
@ -458,14 +410,11 @@ class StateMachine(object):
|
|||||||
def all(self):
|
def all(self):
|
||||||
"""Create a list of all states."""
|
"""Create a list of all states."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
return [state.copy() for state in self._states.values()]
|
return list(self._states.values())
|
||||||
|
|
||||||
def get(self, entity_id):
|
def get(self, entity_id):
|
||||||
"""Retrieve state of entity_id or None if not found."""
|
"""Retrieve state of entity_id or None if not found."""
|
||||||
state = self._states.get(entity_id.lower())
|
return self._states.get(entity_id.lower())
|
||||||
|
|
||||||
# Make a copy so people won't mutate the state
|
|
||||||
return state.copy() if state else None
|
|
||||||
|
|
||||||
def is_state(self, entity_id, state):
|
def is_state(self, entity_id, state):
|
||||||
"""Test if entity exists and is specified state."""
|
"""Test if entity exists and is specified state."""
|
||||||
@ -526,15 +475,6 @@ class StateMachine(object):
|
|||||||
|
|
||||||
self._bus.fire(EVENT_STATE_CHANGED, event_data)
|
self._bus.fire(EVENT_STATE_CHANGED, event_data)
|
||||||
|
|
||||||
def track_change(self, entity_ids, action, from_state=None, to_state=None):
|
|
||||||
"""DEPRECATED AS OF 8/4/2015."""
|
|
||||||
_LOGGER.warning(
|
|
||||||
'hass.states.track_change is deprecated. '
|
|
||||||
'Use homeassistant.helpers.event.track_state_change instead.')
|
|
||||||
import homeassistant.helpers.event as helper
|
|
||||||
helper.track_state_change(_MockHA(self._bus), entity_ids, action,
|
|
||||||
from_state, to_state)
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
class Service(object):
|
class Service(object):
|
||||||
|
@ -80,7 +80,20 @@ class Entity(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
"""Return the state attributes."""
|
"""
|
||||||
|
Return the state attributes.
|
||||||
|
|
||||||
|
Implemented by component base class.
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""
|
||||||
|
Return device specific state attributes.
|
||||||
|
|
||||||
|
Implemented by platform classes.
|
||||||
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -135,6 +148,11 @@ class Entity(object):
|
|||||||
state = str(self.state)
|
state = str(self.state)
|
||||||
attr = self.state_attributes or {}
|
attr = self.state_attributes or {}
|
||||||
|
|
||||||
|
device_attr = self.device_state_attributes
|
||||||
|
|
||||||
|
if device_attr is not None:
|
||||||
|
attr.update(device_attr)
|
||||||
|
|
||||||
if ATTR_UNIT_OF_MEASUREMENT not in attr and \
|
if ATTR_UNIT_OF_MEASUREMENT not in attr and \
|
||||||
self.unit_of_measurement is not None:
|
self.unit_of_measurement is not None:
|
||||||
attr[ATTR_UNIT_OF_MEASUREMENT] = str(self.unit_of_measurement)
|
attr[ATTR_UNIT_OF_MEASUREMENT] = str(self.unit_of_measurement)
|
||||||
|
@ -85,7 +85,7 @@ def reproduce_state(hass, states, blocking=False):
|
|||||||
# We group service calls for entities by service call
|
# We group service calls for entities by service call
|
||||||
# json used to create a hashable version of dict with maybe lists in it
|
# json used to create a hashable version of dict with maybe lists in it
|
||||||
key = (service_domain, service,
|
key = (service_domain, service,
|
||||||
json.dumps(state.attributes, sort_keys=True))
|
json.dumps(dict(state.attributes), sort_keys=True))
|
||||||
to_call[key].append(state.entity_id)
|
to_call[key].append(state.entity_id)
|
||||||
|
|
||||||
for (service_domain, service, service_data), entity_ids in to_call.items():
|
for (service_domain, service, service_data), entity_ids in to_call.items():
|
||||||
|
@ -15,6 +15,7 @@ import socket
|
|||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from types import MappingProxyType
|
||||||
|
|
||||||
from .dt import datetime_to_local_str, utcnow
|
from .dt import datetime_to_local_str, utcnow
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ def slugify(text):
|
|||||||
|
|
||||||
def repr_helper(inp):
|
def repr_helper(inp):
|
||||||
""" Helps creating a more readable string representation of objects. """
|
""" Helps creating a more readable string representation of objects. """
|
||||||
if isinstance(inp, dict):
|
if isinstance(inp, (dict, MappingProxyType)):
|
||||||
return ", ".join(
|
return ", ".join(
|
||||||
repr_helper(key)+"="+repr_helper(item) for key, item
|
repr_helper(key)+"="+repr_helper(item) for key, item
|
||||||
in inp.items())
|
in inp.items())
|
||||||
|
@ -48,7 +48,7 @@ def as_utc(dattim):
|
|||||||
if dattim.tzinfo == UTC:
|
if dattim.tzinfo == UTC:
|
||||||
return dattim
|
return dattim
|
||||||
elif dattim.tzinfo is None:
|
elif dattim.tzinfo is None:
|
||||||
dattim = dattim.replace(tzinfo=DEFAULT_TIME_ZONE)
|
dattim = DEFAULT_TIME_ZONE.localize(dattim)
|
||||||
|
|
||||||
return dattim.astimezone(UTC)
|
return dattim.astimezone(UTC)
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ def as_local(dattim):
|
|||||||
if dattim.tzinfo == DEFAULT_TIME_ZONE:
|
if dattim.tzinfo == DEFAULT_TIME_ZONE:
|
||||||
return dattim
|
return dattim
|
||||||
elif dattim.tzinfo is None:
|
elif dattim.tzinfo is None:
|
||||||
dattim = dattim.replace(tzinfo=UTC)
|
dattim = UTC.localize(dattim)
|
||||||
|
|
||||||
return dattim.astimezone(DEFAULT_TIME_ZONE)
|
return dattim.astimezone(DEFAULT_TIME_ZONE)
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ https://github.com/theolind/pymysensors/archive/005bff4c5ca7a56acd30e816bc3bcdb5
|
|||||||
https://github.com/w1ll1am23/pygooglevoice-sms/archive/7c5ee9969b97a7992fc86a753fe9f20e3ffa3f7c.zip#pygooglevoice-sms==0.0.1
|
https://github.com/w1ll1am23/pygooglevoice-sms/archive/7c5ee9969b97a7992fc86a753fe9f20e3ffa3f7c.zip#pygooglevoice-sms==0.0.1
|
||||||
|
|
||||||
# homeassistant.components.influxdb
|
# homeassistant.components.influxdb
|
||||||
influxdb==2.11.0
|
influxdb==2.12.0
|
||||||
|
|
||||||
# homeassistant.components.insteon_hub
|
# homeassistant.components.insteon_hub
|
||||||
insteon_hub==0.4.5
|
insteon_hub==0.4.5
|
||||||
@ -105,6 +105,10 @@ liffylights==0.9.4
|
|||||||
# homeassistant.components.light.limitlessled
|
# homeassistant.components.light.limitlessled
|
||||||
limitlessled==1.0.0
|
limitlessled==1.0.0
|
||||||
|
|
||||||
|
# homeassistant.components.sensor.mfi
|
||||||
|
# homeassistant.components.switch.mfi
|
||||||
|
mficlient==0.2.2
|
||||||
|
|
||||||
# homeassistant.components.discovery
|
# homeassistant.components.discovery
|
||||||
netdisco==0.5.2
|
netdisco==0.5.2
|
||||||
|
|
||||||
@ -153,6 +157,9 @@ pyicloud==0.7.2
|
|||||||
# homeassistant.components.device_tracker.netgear
|
# homeassistant.components.device_tracker.netgear
|
||||||
pynetgear==0.3.2
|
pynetgear==0.3.2
|
||||||
|
|
||||||
|
# homeassistant.components.alarm_control_panel.nx584
|
||||||
|
pynx584==0.1
|
||||||
|
|
||||||
# homeassistant.components.sensor.openweathermap
|
# homeassistant.components.sensor.openweathermap
|
||||||
pyowm==2.3.0
|
pyowm==2.3.0
|
||||||
|
|
||||||
@ -183,13 +190,16 @@ python-telegram-bot==3.2.0
|
|||||||
# homeassistant.components.sensor.twitch
|
# homeassistant.components.sensor.twitch
|
||||||
python-twitch==1.2.0
|
python-twitch==1.2.0
|
||||||
|
|
||||||
|
# homeassistant.components.garage_door.wink
|
||||||
|
python-wink==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.wink
|
# homeassistant.components.wink
|
||||||
# homeassistant.components.garage_door.wink
|
# homeassistant.components.garage_door.wink
|
||||||
# homeassistant.components.light.wink
|
# homeassistant.components.light.wink
|
||||||
# homeassistant.components.lock.wink
|
# homeassistant.components.lock.wink
|
||||||
# homeassistant.components.sensor.wink
|
# homeassistant.components.sensor.wink
|
||||||
# homeassistant.components.switch.wink
|
# homeassistant.components.switch.wink
|
||||||
python-wink==0.4.2
|
python-wink==0.5.0
|
||||||
|
|
||||||
# homeassistant.components.keyboard
|
# homeassistant.components.keyboard
|
||||||
pyuserinput==0.1.9
|
pyuserinput==0.1.9
|
||||||
@ -197,10 +207,10 @@ pyuserinput==0.1.9
|
|||||||
# homeassistant.components.light.vera
|
# homeassistant.components.light.vera
|
||||||
# homeassistant.components.sensor.vera
|
# homeassistant.components.sensor.vera
|
||||||
# homeassistant.components.switch.vera
|
# homeassistant.components.switch.vera
|
||||||
pyvera==0.2.7
|
pyvera==0.2.8
|
||||||
|
|
||||||
# homeassistant.components.switch.wemo
|
# homeassistant.components.switch.wemo
|
||||||
pywemo==0.3.8
|
pywemo==0.3.9
|
||||||
|
|
||||||
# homeassistant.components.thermostat.radiotherm
|
# homeassistant.components.thermostat.radiotherm
|
||||||
radiotherm==1.2
|
radiotherm==1.2
|
||||||
@ -229,6 +239,9 @@ tellive-py==0.5.2
|
|||||||
# homeassistant.components.switch.transmission
|
# homeassistant.components.switch.transmission
|
||||||
transmissionrpc==0.11
|
transmissionrpc==0.11
|
||||||
|
|
||||||
|
# homeassistant.components.camera.uvc
|
||||||
|
uvcclient==0.5
|
||||||
|
|
||||||
# homeassistant.components.verisure
|
# homeassistant.components.verisure
|
||||||
vsure==0.5.0
|
vsure==0.5.0
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# script/test: Run test suite for application. Optionallly pass in a path to an
|
# script/test: Run test suite for application. Optionally pass in a path to an
|
||||||
# individual test file to run a single test.
|
# individual test file to run a single test.
|
||||||
|
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
138
tests/components/sensor/test_mfi.py
Normal file
138
tests/components/sensor/test_mfi.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
"""
|
||||||
|
tests.components.sensor.test_mfi
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests mFi sensor.
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
import unittest.mock as mock
|
||||||
|
|
||||||
|
import homeassistant.core as ha
|
||||||
|
import homeassistant.components.sensor as sensor
|
||||||
|
import homeassistant.components.sensor.mfi as mfi
|
||||||
|
from homeassistant.const import TEMP_CELCIUS
|
||||||
|
|
||||||
|
|
||||||
|
class TestMfiSensorSetup(unittest.TestCase):
|
||||||
|
PLATFORM = mfi
|
||||||
|
COMPONENT = sensor
|
||||||
|
THING = 'sensor'
|
||||||
|
GOOD_CONFIG = {
|
||||||
|
'sensor': {
|
||||||
|
'platform': 'mfi',
|
||||||
|
'host': 'foo',
|
||||||
|
'port': 6123,
|
||||||
|
'username': 'user',
|
||||||
|
'password': 'pass',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def setup_method(self, method):
|
||||||
|
self.hass = ha.HomeAssistant()
|
||||||
|
self.hass.config.latitude = 32.87336
|
||||||
|
self.hass.config.longitude = 117.22743
|
||||||
|
|
||||||
|
def teardown_method(self, method):
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_setup_missing_config(self):
|
||||||
|
config = {
|
||||||
|
'sensor': {
|
||||||
|
'platform': 'mfi',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.assertFalse(self.PLATFORM.setup_platform(self.hass, config, None))
|
||||||
|
|
||||||
|
@mock.patch('mficlient.client')
|
||||||
|
def test_setup_failed_login(self, mock_client):
|
||||||
|
mock_client.FailedToLogin = Exception()
|
||||||
|
mock_client.MFiClient.side_effect = mock_client.FailedToLogin
|
||||||
|
self.assertFalse(
|
||||||
|
self.PLATFORM.setup_platform(self.hass,
|
||||||
|
dict(self.GOOD_CONFIG),
|
||||||
|
None))
|
||||||
|
|
||||||
|
@mock.patch('mficlient.client.MFiClient')
|
||||||
|
def test_setup_minimum(self, mock_client):
|
||||||
|
config = dict(self.GOOD_CONFIG)
|
||||||
|
del config[self.THING]['port']
|
||||||
|
assert self.COMPONENT.setup(self.hass, config)
|
||||||
|
mock_client.assert_called_once_with('foo', 'user', 'pass',
|
||||||
|
port=6443)
|
||||||
|
|
||||||
|
@mock.patch('mficlient.client.MFiClient')
|
||||||
|
def test_setup_with_port(self, mock_client):
|
||||||
|
config = dict(self.GOOD_CONFIG)
|
||||||
|
config[self.THING]['port'] = 6123
|
||||||
|
assert self.COMPONENT.setup(self.hass, config)
|
||||||
|
mock_client.assert_called_once_with('foo', 'user', 'pass',
|
||||||
|
port=6123)
|
||||||
|
|
||||||
|
@mock.patch('mficlient.client.MFiClient')
|
||||||
|
@mock.patch('homeassistant.components.sensor.mfi.MfiSensor')
|
||||||
|
def test_setup_adds_proper_devices(self, mock_sensor, mock_client):
|
||||||
|
ports = {i: mock.MagicMock(model=model)
|
||||||
|
for i, model in enumerate(mfi.SENSOR_MODELS)}
|
||||||
|
ports['bad'] = mock.MagicMock(model='notasensor')
|
||||||
|
print(ports['bad'].model)
|
||||||
|
mock_client.return_value.get_devices.return_value = \
|
||||||
|
[mock.MagicMock(ports=ports)]
|
||||||
|
assert sensor.setup(self.hass, self.GOOD_CONFIG)
|
||||||
|
for ident, port in ports.items():
|
||||||
|
if ident != 'bad':
|
||||||
|
mock_sensor.assert_any_call(port, self.hass)
|
||||||
|
assert mock.call(ports['bad'], self.hass) not in mock_sensor.mock_calls
|
||||||
|
|
||||||
|
|
||||||
|
class TestMfiSensor(unittest.TestCase):
|
||||||
|
def setup_method(self, method):
|
||||||
|
self.hass = ha.HomeAssistant()
|
||||||
|
self.hass.config.latitude = 32.87336
|
||||||
|
self.hass.config.longitude = 117.22743
|
||||||
|
self.port = mock.MagicMock()
|
||||||
|
self.sensor = mfi.MfiSensor(self.port, self.hass)
|
||||||
|
|
||||||
|
def teardown_method(self, method):
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_name(self):
|
||||||
|
self.assertEqual(self.port.label, self.sensor.name)
|
||||||
|
|
||||||
|
def test_uom_temp(self):
|
||||||
|
self.port.tag = 'temperature'
|
||||||
|
self.assertEqual(TEMP_CELCIUS, self.sensor.unit_of_measurement)
|
||||||
|
|
||||||
|
def test_uom_power(self):
|
||||||
|
self.port.tag = 'active_pwr'
|
||||||
|
self.assertEqual('Watts', self.sensor.unit_of_measurement)
|
||||||
|
|
||||||
|
def test_uom_digital(self):
|
||||||
|
self.port.model = 'Input Digital'
|
||||||
|
self.assertEqual('State', self.sensor.unit_of_measurement)
|
||||||
|
|
||||||
|
def test_uom_unknown(self):
|
||||||
|
self.port.tag = 'balloons'
|
||||||
|
self.assertEqual('balloons', self.sensor.unit_of_measurement)
|
||||||
|
|
||||||
|
def test_state_digital(self):
|
||||||
|
self.port.model = 'Input Digital'
|
||||||
|
self.port.value = 0
|
||||||
|
self.assertEqual(mfi.STATE_OFF, self.sensor.state)
|
||||||
|
self.port.value = 1
|
||||||
|
self.assertEqual(mfi.STATE_ON, self.sensor.state)
|
||||||
|
self.port.value = 2
|
||||||
|
self.assertEqual(mfi.STATE_ON, self.sensor.state)
|
||||||
|
|
||||||
|
def test_state_digits(self):
|
||||||
|
self.port.tag = 'didyoucheckthedict?'
|
||||||
|
self.port.value = 1.25
|
||||||
|
with mock.patch.dict(mfi.DIGITS, {'didyoucheckthedict?': 1}):
|
||||||
|
self.assertEqual(1.2, self.sensor.state)
|
||||||
|
with mock.patch.dict(mfi.DIGITS, {}):
|
||||||
|
self.assertEqual(1.0, self.sensor.state)
|
||||||
|
|
||||||
|
def test_update(self):
|
||||||
|
self.sensor.update()
|
||||||
|
self.port.refresh.assert_called_once_with()
|
98
tests/components/switch/test_mfi.py
Normal file
98
tests/components/switch/test_mfi.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
"""
|
||||||
|
tests.components.switch.test_mfi
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests mFi switch.
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
import unittest.mock as mock
|
||||||
|
|
||||||
|
import homeassistant.core as ha
|
||||||
|
import homeassistant.components.switch as switch
|
||||||
|
import homeassistant.components.switch.mfi as mfi
|
||||||
|
from tests.components.sensor import test_mfi as test_mfi_sensor
|
||||||
|
|
||||||
|
|
||||||
|
class TestMfiSwitchSetup(test_mfi_sensor.TestMfiSensorSetup):
|
||||||
|
PLATFORM = mfi
|
||||||
|
COMPONENT = switch
|
||||||
|
THING = 'switch'
|
||||||
|
GOOD_CONFIG = {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'mfi',
|
||||||
|
'host': 'foo',
|
||||||
|
'port': 6123,
|
||||||
|
'username': 'user',
|
||||||
|
'password': 'pass',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mock.patch('mficlient.client.MFiClient')
|
||||||
|
@mock.patch('homeassistant.components.switch.mfi.MfiSwitch')
|
||||||
|
def test_setup_adds_proper_devices(self, mock_switch, mock_client):
|
||||||
|
ports = {i: mock.MagicMock(model=model)
|
||||||
|
for i, model in enumerate(mfi.SWITCH_MODELS)}
|
||||||
|
ports['bad'] = mock.MagicMock(model='notaswitch')
|
||||||
|
print(ports['bad'].model)
|
||||||
|
mock_client.return_value.get_devices.return_value = \
|
||||||
|
[mock.MagicMock(ports=ports)]
|
||||||
|
assert self.COMPONENT.setup(self.hass, self.GOOD_CONFIG)
|
||||||
|
for ident, port in ports.items():
|
||||||
|
if ident != 'bad':
|
||||||
|
mock_switch.assert_any_call(port)
|
||||||
|
assert mock.call(ports['bad'], self.hass) not in mock_switch.mock_calls
|
||||||
|
|
||||||
|
|
||||||
|
class TestMfiSwitch(unittest.TestCase):
|
||||||
|
def setup_method(self, method):
|
||||||
|
self.hass = ha.HomeAssistant()
|
||||||
|
self.hass.config.latitude = 32.87336
|
||||||
|
self.hass.config.longitude = 117.22743
|
||||||
|
self.port = mock.MagicMock()
|
||||||
|
self.switch = mfi.MfiSwitch(self.port)
|
||||||
|
|
||||||
|
def teardown_method(self, method):
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_name(self):
|
||||||
|
self.assertEqual(self.port.label, self.switch.name)
|
||||||
|
|
||||||
|
def test_update(self):
|
||||||
|
self.switch.update()
|
||||||
|
self.port.refresh.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_update_with_target_state(self):
|
||||||
|
self.switch._target_state = True
|
||||||
|
self.port.data = {}
|
||||||
|
self.port.data['output'] = 'stale'
|
||||||
|
self.switch.update()
|
||||||
|
self.assertEqual(1.0, self.port.data['output'])
|
||||||
|
self.assertEqual(None, self.switch._target_state)
|
||||||
|
self.port.data['output'] = 'untouched'
|
||||||
|
self.switch.update()
|
||||||
|
self.assertEqual('untouched', self.port.data['output'])
|
||||||
|
|
||||||
|
def test_turn_on(self):
|
||||||
|
self.switch.turn_on()
|
||||||
|
self.port.control.assert_called_once_with(True)
|
||||||
|
self.assertTrue(self.switch._target_state)
|
||||||
|
|
||||||
|
def test_turn_off(self):
|
||||||
|
self.switch.turn_off()
|
||||||
|
self.port.control.assert_called_once_with(False)
|
||||||
|
self.assertFalse(self.switch._target_state)
|
||||||
|
|
||||||
|
def test_current_power_mwh(self):
|
||||||
|
self.port.data = {'active_pwr': 1}
|
||||||
|
self.assertEqual(1000, self.switch.current_power_mwh)
|
||||||
|
|
||||||
|
def test_current_power_mwh_no_data(self):
|
||||||
|
self.port.data = {'notpower': 123}
|
||||||
|
self.assertEqual(0, self.switch.current_power_mwh)
|
||||||
|
|
||||||
|
def test_device_state_attributes(self):
|
||||||
|
self.port.data = {'v_rms': 1.25,
|
||||||
|
'i_rms': 2.75}
|
||||||
|
self.assertEqual({'volts': 1.2, 'amps': 2.8},
|
||||||
|
self.switch.device_state_attributes)
|
311
tests/components/switch/test_template.py
Normal file
311
tests/components/switch/test_template.py
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
"""
|
||||||
|
tests.components.switch.template
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests template switch.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import homeassistant.core as ha
|
||||||
|
import homeassistant.components as core
|
||||||
|
import homeassistant.components.switch as switch
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
STATE_ON,
|
||||||
|
STATE_OFF)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTemplateSwitch:
|
||||||
|
""" Test the Template switch. """
|
||||||
|
|
||||||
|
def setup_method(self, method):
|
||||||
|
self.hass = ha.HomeAssistant()
|
||||||
|
|
||||||
|
self.calls = []
|
||||||
|
|
||||||
|
def record_call(service):
|
||||||
|
self.calls.append(service)
|
||||||
|
|
||||||
|
self.hass.services.register('test', 'automation', record_call)
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_method(self, method):
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_template_state_text(self):
|
||||||
|
assert switch.setup(self.hass, {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'template',
|
||||||
|
'switches': {
|
||||||
|
'test_template_switch': {
|
||||||
|
'value_template':
|
||||||
|
"{{ states.switch.test_state.state }}",
|
||||||
|
'turn_on': {
|
||||||
|
'service': 'switch.turn_on',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
'turn_off': {
|
||||||
|
'service': 'switch.turn_off',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
state = self.hass.states.set('switch.test_state', STATE_ON)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
state = self.hass.states.get('switch.test_template_switch')
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
state = self.hass.states.set('switch.test_state', STATE_OFF)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
state = self.hass.states.get('switch.test_template_switch')
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
|
def test_template_state_boolean_on(self):
|
||||||
|
assert switch.setup(self.hass, {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'template',
|
||||||
|
'switches': {
|
||||||
|
'test_template_switch': {
|
||||||
|
'value_template':
|
||||||
|
"{{ 1 == 1 }}",
|
||||||
|
'turn_on': {
|
||||||
|
'service': 'switch.turn_on',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
'turn_off': {
|
||||||
|
'service': 'switch.turn_off',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
state = self.hass.states.get('switch.test_template_switch')
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
def test_template_state_boolean_off(self):
|
||||||
|
assert switch.setup(self.hass, {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'template',
|
||||||
|
'switches': {
|
||||||
|
'test_template_switch': {
|
||||||
|
'value_template':
|
||||||
|
"{{ 1 == 2 }}",
|
||||||
|
'turn_on': {
|
||||||
|
'service': 'switch.turn_on',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
'turn_off': {
|
||||||
|
'service': 'switch.turn_off',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
state = self.hass.states.get('switch.test_template_switch')
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
def test_template_syntax_error(self):
|
||||||
|
assert switch.setup(self.hass, {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'template',
|
||||||
|
'switches': {
|
||||||
|
'test_template_switch': {
|
||||||
|
'value_template':
|
||||||
|
"{% if rubbish %}",
|
||||||
|
'turn_on': {
|
||||||
|
'service': 'switch.turn_on',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
'turn_off': {
|
||||||
|
'service': 'switch.turn_off',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
state = self.hass.states.set('switch.test_state', STATE_ON)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('switch.test_template_switch')
|
||||||
|
assert state.state == 'unavailable'
|
||||||
|
|
||||||
|
def test_invalid_name_does_not_create(self):
|
||||||
|
assert switch.setup(self.hass, {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'template',
|
||||||
|
'switches': {
|
||||||
|
'test INVALID switch': {
|
||||||
|
'value_template':
|
||||||
|
"{{ rubbish }",
|
||||||
|
'turn_on': {
|
||||||
|
'service': 'switch.turn_on',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
'turn_off': {
|
||||||
|
'service': 'switch.turn_off',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert self.hass.states.all() == []
|
||||||
|
|
||||||
|
def test_invalid_switch_does_not_create(self):
|
||||||
|
assert switch.setup(self.hass, {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'template',
|
||||||
|
'switches': {
|
||||||
|
'test_template_switch': 'Invalid'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert self.hass.states.all() == []
|
||||||
|
|
||||||
|
def test_no_switches_does_not_create(self):
|
||||||
|
assert switch.setup(self.hass, {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'template'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert self.hass.states.all() == []
|
||||||
|
|
||||||
|
def test_missing_template_does_not_create(self):
|
||||||
|
assert switch.setup(self.hass, {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'template',
|
||||||
|
'switches': {
|
||||||
|
'test_template_switch': {
|
||||||
|
'not_value_template':
|
||||||
|
"{{ states.switch.test_state.state }}",
|
||||||
|
'turn_on': {
|
||||||
|
'service': 'switch.turn_on',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
'turn_off': {
|
||||||
|
'service': 'switch.turn_off',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert self.hass.states.all() == []
|
||||||
|
|
||||||
|
def test_missing_on_does_not_create(self):
|
||||||
|
assert switch.setup(self.hass, {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'template',
|
||||||
|
'switches': {
|
||||||
|
'test_template_switch': {
|
||||||
|
'value_template':
|
||||||
|
"{{ states.switch.test_state.state }}",
|
||||||
|
'not_on': {
|
||||||
|
'service': 'switch.turn_on',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
'turn_off': {
|
||||||
|
'service': 'switch.turn_off',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert self.hass.states.all() == []
|
||||||
|
|
||||||
|
def test_missing_off_does_not_create(self):
|
||||||
|
assert switch.setup(self.hass, {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'template',
|
||||||
|
'switches': {
|
||||||
|
'test_template_switch': {
|
||||||
|
'value_template':
|
||||||
|
"{{ states.switch.test_state.state }}",
|
||||||
|
'turn_on': {
|
||||||
|
'service': 'switch.turn_on',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
'not_off': {
|
||||||
|
'service': 'switch.turn_off',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert self.hass.states.all() == []
|
||||||
|
|
||||||
|
def test_on_action(self):
|
||||||
|
assert switch.setup(self.hass, {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'template',
|
||||||
|
'switches': {
|
||||||
|
'test_template_switch': {
|
||||||
|
'value_template':
|
||||||
|
"{{ states.switch.test_state.state }}",
|
||||||
|
'turn_on': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
},
|
||||||
|
'turn_off': {
|
||||||
|
'service': 'switch.turn_off',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.hass.states.set('switch.test_state', STATE_OFF)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
state = self.hass.states.get('switch.test_template_switch')
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
core.switch.turn_on(self.hass, 'switch.test_template_switch')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
assert 1 == len(self.calls)
|
||||||
|
|
||||||
|
|
||||||
|
def test_off_action(self):
|
||||||
|
assert switch.setup(self.hass, {
|
||||||
|
'switch': {
|
||||||
|
'platform': 'template',
|
||||||
|
'switches': {
|
||||||
|
'test_template_switch': {
|
||||||
|
'value_template':
|
||||||
|
"{{ states.switch.test_state.state }}",
|
||||||
|
'turn_on': {
|
||||||
|
'service': 'switch.turn_on',
|
||||||
|
'entity_id': 'switch.test_state'
|
||||||
|
|
||||||
|
},
|
||||||
|
'turn_off': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.hass.states.set('switch.test_state', STATE_ON)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
state = self.hass.states.get('switch.test_template_switch')
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
core.switch.turn_off(self.hass, 'switch.test_template_switch')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
assert 1 == len(self.calls)
|
@ -236,3 +236,14 @@ class TestComponentsGroup(unittest.TestCase):
|
|||||||
grp2 = group.Group(self.hass, 'Je suis Charlie')
|
grp2 = group.Group(self.hass, 'Je suis Charlie')
|
||||||
|
|
||||||
self.assertNotEqual(grp1.entity_id, grp2.entity_id)
|
self.assertNotEqual(grp1.entity_id, grp2.entity_id)
|
||||||
|
|
||||||
|
def test_expand_entity_ids_expands_nested_groups(self):
|
||||||
|
group.Group(self.hass, 'light', ['light.test_1', 'light.test_2'])
|
||||||
|
group.Group(self.hass, 'switch', ['switch.test_1', 'switch.test_2'])
|
||||||
|
group.Group(self.hass, 'group_of_groups', ['group.light',
|
||||||
|
'group.switch'])
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
['light.test_1', 'light.test_2', 'switch.test_1', 'switch.test_2'],
|
||||||
|
sorted(group.expand_entity_ids(self.hass,
|
||||||
|
['group.group_of_groups'])))
|
||||||
|
@ -68,7 +68,7 @@ class TestMQTT(unittest.TestCase):
|
|||||||
self.assertEqual('test-payload',
|
self.assertEqual('test-payload',
|
||||||
self.calls[0][0].data['service_data'][mqtt.ATTR_PAYLOAD])
|
self.calls[0][0].data['service_data'][mqtt.ATTR_PAYLOAD])
|
||||||
|
|
||||||
def test_service_call_without_topic_does_not_publush(self):
|
def test_service_call_without_topic_does_not_publish(self):
|
||||||
self.hass.bus.fire(EVENT_CALL_SERVICE, {
|
self.hass.bus.fire(EVENT_CALL_SERVICE, {
|
||||||
ATTR_DOMAIN: mqtt.DOMAIN,
|
ATTR_DOMAIN: mqtt.DOMAIN,
|
||||||
ATTR_SERVICE: mqtt.SERVICE_PUBLISH
|
ATTR_SERVICE: mqtt.SERVICE_PUBLISH
|
||||||
@ -76,6 +76,42 @@ class TestMQTT(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertTrue(not mqtt.MQTT_CLIENT.publish.called)
|
self.assertTrue(not mqtt.MQTT_CLIENT.publish.called)
|
||||||
|
|
||||||
|
def test_service_call_with_template_payload_renders_template(self):
|
||||||
|
"""
|
||||||
|
If 'payload_template' is provided and 'payload' is not, then render it.
|
||||||
|
"""
|
||||||
|
mqtt.publish_template(self.hass, "test/topic", "{{ 1+1 }}")
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertTrue(mqtt.MQTT_CLIENT.publish.called)
|
||||||
|
self.assertEqual(mqtt.MQTT_CLIENT.publish.call_args[0][1], "2")
|
||||||
|
|
||||||
|
def test_service_call_with_payload_doesnt_render_template(self):
|
||||||
|
"""
|
||||||
|
If a 'payload' is provided then use that instead of 'payload_template'.
|
||||||
|
"""
|
||||||
|
payload = "not a template"
|
||||||
|
payload_template = "a template"
|
||||||
|
# Call the service directly because the helper functions don't allow
|
||||||
|
# you to provide payload AND payload_template.
|
||||||
|
self.hass.services.call(mqtt.DOMAIN, mqtt.SERVICE_PUBLISH, {
|
||||||
|
mqtt.ATTR_TOPIC: "test/topic",
|
||||||
|
mqtt.ATTR_PAYLOAD: payload,
|
||||||
|
mqtt.ATTR_PAYLOAD_TEMPLATE: payload_template
|
||||||
|
}, blocking=True)
|
||||||
|
self.assertTrue(mqtt.MQTT_CLIENT.publish.called)
|
||||||
|
self.assertEqual(mqtt.MQTT_CLIENT.publish.call_args[0][1], payload)
|
||||||
|
|
||||||
|
def test_service_call_without_payload_or_payload_template(self):
|
||||||
|
"""
|
||||||
|
If neither 'payload' or 'payload_template' is provided then fail.
|
||||||
|
"""
|
||||||
|
# Call the service directly because the helper functions require you to
|
||||||
|
# provide a payload.
|
||||||
|
self.hass.services.call(mqtt.DOMAIN, mqtt.SERVICE_PUBLISH, {
|
||||||
|
mqtt.ATTR_TOPIC: "test/topic"
|
||||||
|
}, blocking=True)
|
||||||
|
self.assertFalse(mqtt.MQTT_CLIENT.publish.called)
|
||||||
|
|
||||||
def test_subscribe_topic(self):
|
def test_subscribe_topic(self):
|
||||||
mqtt.subscribe(self.hass, 'test-topic', self.record_calls)
|
mqtt.subscribe(self.hass, 'test-topic', self.record_calls)
|
||||||
|
|
||||||
|
616
tests/components/test_proximity.py
Normal file
616
tests/components/test_proximity.py
Normal file
@ -0,0 +1,616 @@
|
|||||||
|
"""
|
||||||
|
tests.components.proximity
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests proximity component.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import homeassistant.core as ha
|
||||||
|
from homeassistant.components import proximity
|
||||||
|
|
||||||
|
class TestProximity:
|
||||||
|
""" Test the Proximity component. """
|
||||||
|
|
||||||
|
def setup_method(self, method):
|
||||||
|
self.hass = ha.HomeAssistant()
|
||||||
|
self.hass.states.set(
|
||||||
|
'zone.home', 'zoning',
|
||||||
|
{
|
||||||
|
'name': 'home',
|
||||||
|
'latitude': 2.1,
|
||||||
|
'longitude': 1.1,
|
||||||
|
'radius': 10
|
||||||
|
})
|
||||||
|
|
||||||
|
def teardown_method(self, method):
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_proximity(self):
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1',
|
||||||
|
'device_tracker.test2'
|
||||||
|
},
|
||||||
|
'tolerance': '1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.state == 'not set'
|
||||||
|
assert state.attributes.get('nearest') == 'not set'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'not set'
|
||||||
|
|
||||||
|
self.hass.states.set('proximity.home', '0')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.state == '0'
|
||||||
|
|
||||||
|
def test_no_devices_in_config(self):
|
||||||
|
assert not proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'tolerance': '1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_no_tolerance_in_config(self):
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1',
|
||||||
|
'device_tracker.test2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_no_ignored_zones_in_config(self):
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1',
|
||||||
|
'device_tracker.test2'
|
||||||
|
},
|
||||||
|
'tolerance': '1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_no_zone_in_config(self):
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1',
|
||||||
|
'device_tracker.test2'
|
||||||
|
},
|
||||||
|
'tolerance': '1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_device_tracker_test1_in_zone(self):
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1'
|
||||||
|
},
|
||||||
|
'tolerance': '1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 2.1,
|
||||||
|
'longitude': 1.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.state == '0'
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'arrived'
|
||||||
|
|
||||||
|
def test_device_trackers_in_zone(self):
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1',
|
||||||
|
'device_tracker.test2'
|
||||||
|
},
|
||||||
|
'tolerance': '1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 2.1,
|
||||||
|
'longitude': 1.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test2', 'home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test2',
|
||||||
|
'latitude': 2.1,
|
||||||
|
'longitude': 1.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.state == '0'
|
||||||
|
assert (state.attributes.get('nearest') == 'test1, test2') or (state.attributes.get('nearest') == 'test2, test1')
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'arrived'
|
||||||
|
|
||||||
|
def test_device_tracker_test1_away(self):
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1'
|
||||||
|
},
|
||||||
|
'tolerance': '1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 20.1,
|
||||||
|
'longitude': 10.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
||||||
|
|
||||||
|
def test_device_tracker_test1_awayfurther(self):
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 20.1,
|
||||||
|
'longitude': 10.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 40.1,
|
||||||
|
'longitude': 20.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'away_from'
|
||||||
|
|
||||||
|
def test_device_tracker_test1_awaycloser(self):
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 40.1,
|
||||||
|
'longitude': 20.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 20.1,
|
||||||
|
'longitude': 10.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'towards'
|
||||||
|
|
||||||
|
def test_all_device_trackers_in_ignored_zone(self):
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'work',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.state == 'not set'
|
||||||
|
assert state.attributes.get('nearest') == 'not set'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'not set'
|
||||||
|
|
||||||
|
def test_device_tracker_test1_no_coordinates(self):
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1'
|
||||||
|
},
|
||||||
|
'tolerance': '1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'not set'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'not set'
|
||||||
|
|
||||||
|
def test_device_tracker_test1_awayfurther_than_test2_first_test1(self):
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test2', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test2'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1',
|
||||||
|
'device_tracker.test2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 20.1,
|
||||||
|
'longitude': 10.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test2', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test2',
|
||||||
|
'latitude': 40.1,
|
||||||
|
'longitude': 20.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
||||||
|
|
||||||
|
def test_device_tracker_test1_awayfurther_than_test2_first_test2(self):
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test2', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test2'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1',
|
||||||
|
'device_tracker.test2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test2', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test2',
|
||||||
|
'latitude': 40.1,
|
||||||
|
'longitude': 20.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test2'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 20.1,
|
||||||
|
'longitude': 10.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
||||||
|
|
||||||
|
def test_device_tracker_test1_awayfurther_test2_in_ignored_zone(self):
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test2', 'work',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test2'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1',
|
||||||
|
'device_tracker.test2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 20.1,
|
||||||
|
'longitude': 10.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
||||||
|
|
||||||
|
def test_device_tracker_test1_awayfurther_than_test2_first_test1_than_test2_than_test1(self):
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test2', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test2'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1',
|
||||||
|
'device_tracker.test2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 10.1,
|
||||||
|
'longitude': 5.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test2', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test2',
|
||||||
|
'latitude': 20.1,
|
||||||
|
'longitude': 10.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 40.1,
|
||||||
|
'longitude': 20.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 35.1,
|
||||||
|
'longitude': 15.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'work',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test2'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
||||||
|
|
||||||
|
def test_device_tracker_test1_awayfurther_a_bit(self):
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1'
|
||||||
|
},
|
||||||
|
'tolerance': 1000
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 20.1000001,
|
||||||
|
'longitude': 10.1000001
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 20.1000002,
|
||||||
|
'longitude': 10.1000002
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'stationary'
|
||||||
|
|
||||||
|
def test_device_tracker_test1_nearest_after_test2_in_ignored_zone(self):
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test2', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test2'
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
assert proximity.setup(self.hass, {
|
||||||
|
'proximity': {
|
||||||
|
'zone': 'home',
|
||||||
|
'ignored_zones': {
|
||||||
|
'work'
|
||||||
|
},
|
||||||
|
'devices': {
|
||||||
|
'device_tracker.test1',
|
||||||
|
'device_tracker.test2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test1', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test1',
|
||||||
|
'latitude': 20.1,
|
||||||
|
'longitude': 10.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test2', 'not_home',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test2',
|
||||||
|
'latitude': 10.1,
|
||||||
|
'longitude': 5.1
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test2'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
||||||
|
|
||||||
|
self.hass.states.set(
|
||||||
|
'device_tracker.test2', 'work',
|
||||||
|
{
|
||||||
|
'friendly_name': 'test2',
|
||||||
|
'latitude': 12.6,
|
||||||
|
'longitude': 7.6
|
||||||
|
})
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
state = self.hass.states.get('proximity.home')
|
||||||
|
assert state.attributes.get('nearest') == 'test1'
|
||||||
|
assert state.attributes.get('dir_of_travel') == 'unknown'
|
@ -23,7 +23,7 @@ import homeassistant.util.dt as dt_util
|
|||||||
from homeassistant.helpers.event import track_state_change
|
from homeassistant.helpers.event import track_state_change
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
__version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
__version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
||||||
ATTR_FRIENDLY_NAME, TEMP_CELCIUS,
|
EVENT_STATE_CHANGED, ATTR_FRIENDLY_NAME, TEMP_CELCIUS,
|
||||||
TEMP_FAHRENHEIT)
|
TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
PST = pytz.timezone('America/Los_Angeles')
|
PST = pytz.timezone('America/Los_Angeles')
|
||||||
@ -93,65 +93,6 @@ class TestHomeAssistant(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(1, len(calls))
|
self.assertEqual(1, len(calls))
|
||||||
|
|
||||||
def test_track_point_in_time(self):
|
|
||||||
""" Test track point in time. """
|
|
||||||
before_birthday = datetime(1985, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC)
|
|
||||||
birthday_paulus = datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC)
|
|
||||||
after_birthday = datetime(1987, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC)
|
|
||||||
|
|
||||||
runs = []
|
|
||||||
|
|
||||||
self.hass.track_point_in_utc_time(
|
|
||||||
lambda x: runs.append(1), birthday_paulus)
|
|
||||||
|
|
||||||
self._send_time_changed(before_birthday)
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(0, len(runs))
|
|
||||||
|
|
||||||
self._send_time_changed(birthday_paulus)
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(runs))
|
|
||||||
|
|
||||||
# A point in time tracker will only fire once, this should do nothing
|
|
||||||
self._send_time_changed(birthday_paulus)
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(runs))
|
|
||||||
|
|
||||||
self.hass.track_point_in_time(
|
|
||||||
lambda x: runs.append(1), birthday_paulus)
|
|
||||||
|
|
||||||
self._send_time_changed(after_birthday)
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(2, len(runs))
|
|
||||||
|
|
||||||
def test_track_time_change(self):
|
|
||||||
""" Test tracking time change. """
|
|
||||||
wildcard_runs = []
|
|
||||||
specific_runs = []
|
|
||||||
|
|
||||||
self.hass.track_time_change(lambda x: wildcard_runs.append(1))
|
|
||||||
self.hass.track_utc_time_change(
|
|
||||||
lambda x: specific_runs.append(1), second=[0, 30])
|
|
||||||
|
|
||||||
self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0))
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(specific_runs))
|
|
||||||
self.assertEqual(1, len(wildcard_runs))
|
|
||||||
|
|
||||||
self._send_time_changed(datetime(2014, 5, 24, 12, 0, 15))
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(specific_runs))
|
|
||||||
self.assertEqual(2, len(wildcard_runs))
|
|
||||||
|
|
||||||
self._send_time_changed(datetime(2014, 5, 24, 12, 0, 30))
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
self.assertEqual(2, len(specific_runs))
|
|
||||||
self.assertEqual(3, len(wildcard_runs))
|
|
||||||
|
|
||||||
def _send_time_changed(self, now):
|
|
||||||
""" Send a time changed event. """
|
|
||||||
self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now})
|
|
||||||
|
|
||||||
|
|
||||||
class TestEvent(unittest.TestCase):
|
class TestEvent(unittest.TestCase):
|
||||||
""" Test Event class. """
|
""" Test Event class. """
|
||||||
@ -267,18 +208,6 @@ class TestState(unittest.TestCase):
|
|||||||
{ATTR_FRIENDLY_NAME: name})
|
{ATTR_FRIENDLY_NAME: name})
|
||||||
self.assertEqual(name, state.name)
|
self.assertEqual(name, state.name)
|
||||||
|
|
||||||
def test_copy(self):
|
|
||||||
state = ha.State('domain.hello', 'world', {'some': 'attr'})
|
|
||||||
# Patch dt_util.utcnow() so we know last_updated got copied too
|
|
||||||
with patch('homeassistant.core.dt_util.utcnow',
|
|
||||||
return_value=dt_util.utcnow() + timedelta(seconds=10)):
|
|
||||||
copy = state.copy()
|
|
||||||
self.assertEqual(state.entity_id, copy.entity_id)
|
|
||||||
self.assertEqual(state.state, copy.state)
|
|
||||||
self.assertEqual(state.attributes, copy.attributes)
|
|
||||||
self.assertEqual(state.last_changed, copy.last_changed)
|
|
||||||
self.assertEqual(state.last_updated, copy.last_updated)
|
|
||||||
|
|
||||||
def test_dict_conversion(self):
|
def test_dict_conversion(self):
|
||||||
state = ha.State('domain.hello', 'world', {'some': 'attr'})
|
state = ha.State('domain.hello', 'world', {'some': 'attr'})
|
||||||
self.assertEqual(state, ha.State.from_dict(state.as_dict()))
|
self.assertEqual(state, ha.State.from_dict(state.as_dict()))
|
||||||
@ -358,52 +287,11 @@ class TestStateMachine(unittest.TestCase):
|
|||||||
# If it does not exist, we should get False
|
# If it does not exist, we should get False
|
||||||
self.assertFalse(self.states.remove('light.Bowl'))
|
self.assertFalse(self.states.remove('light.Bowl'))
|
||||||
|
|
||||||
def test_track_change(self):
|
|
||||||
""" Test states.track_change. """
|
|
||||||
self.pool.add_worker()
|
|
||||||
|
|
||||||
# 2 lists to track how often our callbacks got called
|
|
||||||
specific_runs = []
|
|
||||||
wildcard_runs = []
|
|
||||||
|
|
||||||
self.states.track_change(
|
|
||||||
'light.Bowl', lambda a, b, c: specific_runs.append(1), 'on', 'off')
|
|
||||||
|
|
||||||
self.states.track_change(
|
|
||||||
'light.Bowl', lambda a, b, c: wildcard_runs.append(1),
|
|
||||||
ha.MATCH_ALL, ha.MATCH_ALL)
|
|
||||||
|
|
||||||
# Set same state should not trigger a state change/listener
|
|
||||||
self.states.set('light.Bowl', 'on')
|
|
||||||
self.bus._pool.block_till_done()
|
|
||||||
self.assertEqual(0, len(specific_runs))
|
|
||||||
self.assertEqual(0, len(wildcard_runs))
|
|
||||||
|
|
||||||
# State change off -> on
|
|
||||||
self.states.set('light.Bowl', 'off')
|
|
||||||
self.bus._pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(specific_runs))
|
|
||||||
self.assertEqual(1, len(wildcard_runs))
|
|
||||||
|
|
||||||
# State change off -> off
|
|
||||||
self.states.set('light.Bowl', 'off', {"some_attr": 1})
|
|
||||||
self.bus._pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(specific_runs))
|
|
||||||
self.assertEqual(2, len(wildcard_runs))
|
|
||||||
|
|
||||||
# State change off -> on
|
|
||||||
self.states.set('light.Bowl', 'on')
|
|
||||||
self.bus._pool.block_till_done()
|
|
||||||
self.assertEqual(1, len(specific_runs))
|
|
||||||
self.assertEqual(3, len(wildcard_runs))
|
|
||||||
|
|
||||||
def test_case_insensitivty(self):
|
def test_case_insensitivty(self):
|
||||||
self.pool.add_worker()
|
self.pool.add_worker()
|
||||||
runs = []
|
runs = []
|
||||||
|
|
||||||
track_state_change(
|
self.bus.listen(EVENT_STATE_CHANGED, lambda event: runs.append(event))
|
||||||
ha._MockHA(self.bus), 'light.BoWl', lambda a, b, c: runs.append(1),
|
|
||||||
ha.MATCH_ALL, ha.MATCH_ALL)
|
|
||||||
|
|
||||||
self.states.set('light.BOWL', 'off')
|
self.states.set('light.BOWL', 'off')
|
||||||
self.bus._pool.block_till_done()
|
self.bus._pool.block_till_done()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user