mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Merge branch 'dev' of https://github.com/balloob/home-assistant into dev
This commit is contained in:
commit
94eb54ff00
@ -49,6 +49,7 @@ omit =
|
||||
homeassistant/components/media_player/itunes.py
|
||||
homeassistant/components/media_player/kodi.py
|
||||
homeassistant/components/media_player/mpd.py
|
||||
homeassistant/components/media_player/plex.py
|
||||
homeassistant/components/media_player/squeezebox.py
|
||||
homeassistant/components/media_player/sonos.py
|
||||
homeassistant/components/notify/file.py
|
||||
@ -69,6 +70,7 @@ omit =
|
||||
homeassistant/components/sensor/glances.py
|
||||
homeassistant/components/sensor/mysensors.py
|
||||
homeassistant/components/sensor/openweathermap.py
|
||||
homeassistant/components/sensor/rest.py
|
||||
homeassistant/components/sensor/rfxtrx.py
|
||||
homeassistant/components/sensor/rpi_gpio.py
|
||||
homeassistant/components/sensor/sabnzbd.py
|
||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -15,10 +15,6 @@ tests/config/home-assistant.log
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# Hide code validator output
|
||||
pep8.txt
|
||||
pylint.txt
|
||||
|
||||
# Hide some OS X stuff
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
@ -30,6 +26,9 @@ Icon
|
||||
|
||||
.idea
|
||||
|
||||
# pytest
|
||||
.cache
|
||||
|
||||
# GITHUB Proposed Python stuff:
|
||||
*.py[cod]
|
||||
|
||||
|
@ -18,7 +18,7 @@ For help on building your component, please see the [developer documentation](ht
|
||||
After you finish adding support for your device:
|
||||
|
||||
- Update the supported devices in the `README.md` file.
|
||||
- Add any new dependencies to `requirements.txt`.
|
||||
- Add any new dependencies to `requirements_all.txt`. There is no ordering right now, so just add it to the end.
|
||||
- Update the `.coveragerc` file.
|
||||
- Provide some documentation for [home-assistant.io](https://home-assistant.io/). The documentation is handled in a separate [git repository](https://github.com/balloob/home-assistant.io).
|
||||
- Make sure all your code passes Pylint and flake8 (PEP8 and some more) validation. To generate reports, run `pylint homeassistant > pylint.txt` and `flake8 homeassistant --exclude bower_components,external > flake8.txt`.
|
||||
|
@ -1,7 +1,9 @@
|
||||
homeassistant:
|
||||
# Omitted values in this section will be auto detected using freegeoip.net
|
||||
|
||||
# Location required to calculate the time the sun rises and sets
|
||||
# Location required to calculate the time the sun rises and sets.
|
||||
# Cooridinates are also used for location for weather related components.
|
||||
# Google Maps can be used to determine more precise GPS cooridinates.
|
||||
latitude: 32.87336
|
||||
longitude: 117.22743
|
||||
|
||||
|
@ -297,11 +297,15 @@ def process_ha_core_config(hass, config):
|
||||
else:
|
||||
_LOGGER.error('Received invalid time zone %s', time_zone_str)
|
||||
|
||||
for key, attr in ((CONF_LATITUDE, 'latitude'),
|
||||
(CONF_LONGITUDE, 'longitude'),
|
||||
(CONF_NAME, 'location_name')):
|
||||
for key, attr, typ in ((CONF_LATITUDE, 'latitude', float),
|
||||
(CONF_LONGITUDE, 'longitude', float),
|
||||
(CONF_NAME, 'location_name', str)):
|
||||
if key in config:
|
||||
setattr(hac, attr, config[key])
|
||||
try:
|
||||
setattr(hac, attr, typ(config[key]))
|
||||
except ValueError:
|
||||
_LOGGER.error('Received invalid %s value for %s: %s',
|
||||
typ.__name__, key, attr)
|
||||
|
||||
set_time_zone(config.get(CONF_TIME_ZONE))
|
||||
|
||||
|
@ -103,6 +103,10 @@ def _handle_get_api_stream(handler, path_match, data):
|
||||
write_lock = threading.Lock()
|
||||
block = threading.Event()
|
||||
|
||||
restrict = data.get('restrict')
|
||||
if restrict:
|
||||
restrict = restrict.split(',')
|
||||
|
||||
def write_message(payload):
|
||||
""" Writes a message to the output. """
|
||||
with write_lock:
|
||||
@ -118,7 +122,8 @@ def _handle_get_api_stream(handler, path_match, data):
|
||||
""" Forwards events to the open request. """
|
||||
nonlocal gracefully_closed
|
||||
|
||||
if block.is_set() or event.event_type == EVENT_TIME_CHANGED:
|
||||
if block.is_set() or event.event_type == EVENT_TIME_CHANGED or \
|
||||
restrict and event.event_type not in restrict:
|
||||
return
|
||||
elif event.event_type == EVENT_HOMEASSISTANT_STOP:
|
||||
gracefully_closed = True
|
||||
|
@ -16,9 +16,9 @@ DOMAIN = 'automation'
|
||||
DEPENDENCIES = ['group']
|
||||
|
||||
CONF_ALIAS = 'alias'
|
||||
CONF_SERVICE = 'execute_service'
|
||||
CONF_SERVICE_ENTITY_ID = 'service_entity_id'
|
||||
CONF_SERVICE_DATA = 'service_data'
|
||||
CONF_SERVICE = 'service'
|
||||
CONF_SERVICE_ENTITY_ID = 'entity_id'
|
||||
CONF_SERVICE_DATA = 'data'
|
||||
|
||||
CONF_CONDITION = 'condition'
|
||||
CONF_ACTION = 'action'
|
||||
@ -40,25 +40,45 @@ def setup(hass, config):
|
||||
found = 1
|
||||
|
||||
while config_key in config:
|
||||
p_config = _migrate_old_config(config[config_key])
|
||||
# check for one block syntax
|
||||
if isinstance(config[config_key], dict):
|
||||
config_block = _migrate_old_config(config[config_key])
|
||||
name = config_block.get(CONF_ALIAS, config_key)
|
||||
_setup_automation(hass, config_block, name, config)
|
||||
|
||||
# check for multiple block syntax
|
||||
elif isinstance(config[config_key], list):
|
||||
for list_no, config_block in enumerate(config[config_key]):
|
||||
name = config_block.get(CONF_ALIAS,
|
||||
"{}, {}".format(config_key, list_no))
|
||||
_setup_automation(hass, config_block, name, config)
|
||||
|
||||
# any scalar value is incorrect
|
||||
else:
|
||||
_LOGGER.error('Error in config in section %s.', config_key)
|
||||
|
||||
found += 1
|
||||
config_key = "{} {}".format(DOMAIN, found)
|
||||
|
||||
name = p_config.get(CONF_ALIAS, config_key)
|
||||
action = _get_action(hass, p_config.get(CONF_ACTION, {}), name)
|
||||
return True
|
||||
|
||||
|
||||
def _setup_automation(hass, config_block, name, config):
|
||||
""" Setup one instance of automation """
|
||||
|
||||
action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)
|
||||
|
||||
if action is None:
|
||||
return False
|
||||
|
||||
if CONF_CONDITION in config_block or CONF_CONDITION_TYPE in config_block:
|
||||
action = _process_if(hass, config, config_block, action)
|
||||
|
||||
if action is None:
|
||||
continue
|
||||
|
||||
if CONF_CONDITION in p_config or CONF_CONDITION_TYPE in p_config:
|
||||
action = _process_if(hass, config, p_config, action)
|
||||
|
||||
if action is None:
|
||||
continue
|
||||
|
||||
_process_trigger(hass, config, p_config.get(CONF_TRIGGER, []), name,
|
||||
action)
|
||||
return False
|
||||
|
||||
_process_trigger(hass, config, config_block.get(CONF_TRIGGER, []), name,
|
||||
action)
|
||||
return True
|
||||
|
||||
|
||||
@ -118,7 +138,10 @@ def _migrate_old_config(config):
|
||||
('trigger', 'state_from', 'from'),
|
||||
('trigger', 'state_hours', 'hours'),
|
||||
('trigger', 'state_minutes', 'minutes'),
|
||||
('trigger', 'state_seconds', 'seconds')):
|
||||
('trigger', 'state_seconds', 'seconds'),
|
||||
('action', 'execute_service', 'service'),
|
||||
('action', 'service_entity_id', 'entity_id'),
|
||||
('action', 'service_data', 'data')):
|
||||
if key in new_conf[cat]:
|
||||
new_conf[cat][new_key] = new_conf[cat].pop(key)
|
||||
|
||||
|
@ -20,11 +20,12 @@ def trigger(hass, config, action):
|
||||
_LOGGER.error("Missing configuration key %s", CONF_EVENT_TYPE)
|
||||
return False
|
||||
|
||||
event_data = config.get(CONF_EVENT_DATA, {})
|
||||
event_data = config.get(CONF_EVENT_DATA)
|
||||
|
||||
def handle_event(event):
|
||||
""" Listens for events and calls the action when data matches. """
|
||||
if event_data == event.data:
|
||||
if not event_data or all(val == event.data.get(key) for key, val
|
||||
in event_data.items()):
|
||||
action()
|
||||
|
||||
hass.bus.listen(event_type, handle_event)
|
||||
|
@ -79,11 +79,11 @@ def if_action(hass, config):
|
||||
now = dt_util.now()
|
||||
if before is not None and now > now.replace(hour=before.hour,
|
||||
minute=before.minute):
|
||||
return False
|
||||
return False
|
||||
|
||||
if after is not None and now < now.replace(hour=after.hour,
|
||||
minute=after.minute):
|
||||
return False
|
||||
return False
|
||||
|
||||
if weekday is not None:
|
||||
now_weekday = WEEKDAYS[now.weekday()]
|
||||
|
@ -33,10 +33,10 @@ def setup(hass, config):
|
||||
|
||||
# Setup sun
|
||||
if not hass.config.latitude:
|
||||
hass.config.latitude = '32.87336'
|
||||
hass.config.latitude = 32.87336
|
||||
|
||||
if not hass.config.longitude:
|
||||
hass.config.longitude = '117.22743'
|
||||
hass.config.longitude = 117.22743
|
||||
|
||||
bootstrap.setup_component(hass, 'sun')
|
||||
|
||||
@ -108,7 +108,9 @@ def setup(hass, config):
|
||||
"http://graph.facebook.com/297400035/picture",
|
||||
ATTR_FRIENDLY_NAME: 'Paulus'})
|
||||
hass.states.set("device_tracker.anne_therese", "not_home",
|
||||
{ATTR_FRIENDLY_NAME: 'Anne Therese'})
|
||||
{ATTR_FRIENDLY_NAME: 'Anne Therese',
|
||||
'latitude': hass.config.latitude + 0.002,
|
||||
'longitude': hass.config.longitude + 0.002})
|
||||
|
||||
hass.states.set("group.all_devices", "home",
|
||||
{
|
||||
|
@ -17,7 +17,12 @@ device_tracker:
|
||||
|
||||
# New found devices auto found
|
||||
track_new_devices: yes
|
||||
|
||||
# Maximum distance from home we consider people home
|
||||
range_home: 100
|
||||
"""
|
||||
# pylint: disable=too-many-instance-attributes, too-many-arguments
|
||||
# pylint: disable=too-many-locals
|
||||
import csv
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
@ -52,7 +57,7 @@ CONF_TRACK_NEW = "track_new_devices"
|
||||
DEFAULT_CONF_TRACK_NEW = True
|
||||
|
||||
CONF_CONSIDER_HOME = 'consider_home'
|
||||
DEFAULT_CONF_CONSIDER_HOME = 180 # seconds
|
||||
DEFAULT_CONSIDER_HOME = 180 # seconds
|
||||
|
||||
CONF_SCAN_INTERVAL = "interval_seconds"
|
||||
DEFAULT_SCAN_INTERVAL = 12
|
||||
@ -60,6 +65,9 @@ DEFAULT_SCAN_INTERVAL = 12
|
||||
CONF_AWAY_HIDE = 'hide_if_away'
|
||||
DEFAULT_AWAY_HIDE = False
|
||||
|
||||
CONF_HOME_RANGE = 'home_range'
|
||||
DEFAULT_HOME_RANGE = 100
|
||||
|
||||
SERVICE_SEE = 'see'
|
||||
|
||||
ATTR_LATITUDE = 'latitude'
|
||||
@ -69,6 +77,8 @@ ATTR_DEV_ID = 'dev_id'
|
||||
ATTR_HOST_NAME = 'host_name'
|
||||
ATTR_LOCATION_NAME = 'location_name'
|
||||
ATTR_GPS = 'gps'
|
||||
ATTR_GPS_ACCURACY = 'gps_accuracy'
|
||||
ATTR_BATTERY = 'battery'
|
||||
|
||||
DISCOVERY_PLATFORMS = {
|
||||
discovery.SERVICE_NETGEAR: 'netgear',
|
||||
@ -86,7 +96,7 @@ def is_on(hass, entity_id=None):
|
||||
|
||||
|
||||
def see(hass, mac=None, dev_id=None, host_name=None, location_name=None,
|
||||
gps=None):
|
||||
gps=None, gps_accuracy=None, battery=None):
|
||||
""" Call service to notify you see device. """
|
||||
data = {key: value for key, value in
|
||||
((ATTR_MAC, mac),
|
||||
@ -106,13 +116,17 @@ def setup(hass, config):
|
||||
os.remove(csv_path)
|
||||
|
||||
conf = config.get(DOMAIN, {})
|
||||
consider_home = util.convert(conf.get(CONF_CONSIDER_HOME), int,
|
||||
DEFAULT_CONF_CONSIDER_HOME)
|
||||
consider_home = timedelta(
|
||||
seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int,
|
||||
DEFAULT_CONSIDER_HOME))
|
||||
track_new = util.convert(conf.get(CONF_TRACK_NEW), bool,
|
||||
DEFAULT_CONF_TRACK_NEW)
|
||||
home_range = util.convert(conf.get(CONF_HOME_RANGE), int,
|
||||
DEFAULT_HOME_RANGE)
|
||||
|
||||
devices = load_config(yaml_path, hass, timedelta(seconds=consider_home))
|
||||
tracker = DeviceTracker(hass, consider_home, track_new, devices)
|
||||
devices = load_config(yaml_path, hass, consider_home, home_range)
|
||||
tracker = DeviceTracker(hass, consider_home, track_new, home_range,
|
||||
devices)
|
||||
|
||||
def setup_platform(p_type, p_config, disc_info=None):
|
||||
""" Setup a device tracker platform. """
|
||||
@ -158,7 +172,7 @@ def setup(hass, config):
|
||||
""" Service to see a device. """
|
||||
args = {key: value for key, value in call.data.items() if key in
|
||||
(ATTR_MAC, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_LOCATION_NAME,
|
||||
ATTR_GPS)}
|
||||
ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_BATTERY)}
|
||||
tracker.see(**args)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_SEE, see_service)
|
||||
@ -168,12 +182,13 @@ def setup(hass, config):
|
||||
|
||||
class DeviceTracker(object):
|
||||
""" Track devices """
|
||||
def __init__(self, hass, consider_home, track_new, devices):
|
||||
def __init__(self, hass, consider_home, track_new, home_range, devices):
|
||||
self.hass = hass
|
||||
self.devices = {dev.dev_id: dev for dev in devices}
|
||||
self.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac}
|
||||
self.consider_home = timedelta(seconds=consider_home)
|
||||
self.consider_home = consider_home
|
||||
self.track_new = track_new
|
||||
self.home_range = home_range
|
||||
self.lock = threading.Lock()
|
||||
|
||||
for device in devices:
|
||||
@ -183,7 +198,7 @@ class DeviceTracker(object):
|
||||
self.group = None
|
||||
|
||||
def see(self, mac=None, dev_id=None, host_name=None, location_name=None,
|
||||
gps=None):
|
||||
gps=None, gps_accuracy=None, battery=None):
|
||||
""" Notify device tracker that you see a device. """
|
||||
with self.lock:
|
||||
if mac is None and dev_id is None:
|
||||
@ -198,20 +213,21 @@ class DeviceTracker(object):
|
||||
device = self.devices.get(dev_id)
|
||||
|
||||
if device:
|
||||
device.seen(host_name, location_name, gps)
|
||||
device.seen(host_name, location_name, gps, gps_accuracy,
|
||||
battery)
|
||||
if device.track:
|
||||
device.update_ha_state()
|
||||
return
|
||||
|
||||
# If no device can be found, create it
|
||||
device = Device(
|
||||
self.hass, self.consider_home, self.track_new, dev_id, mac,
|
||||
(host_name or dev_id).replace('_', ' '))
|
||||
self.hass, self.consider_home, self.home_range, self.track_new,
|
||||
dev_id, mac, (host_name or dev_id).replace('_', ' '))
|
||||
self.devices[dev_id] = device
|
||||
if mac is not None:
|
||||
self.mac_to_dev[mac] = device
|
||||
|
||||
device.seen(host_name, location_name, gps)
|
||||
device.seen(host_name, location_name, gps, gps_accuracy, battery)
|
||||
if device.track:
|
||||
device.update_ha_state()
|
||||
|
||||
@ -239,19 +255,20 @@ class DeviceTracker(object):
|
||||
|
||||
class Device(Entity):
|
||||
""" Tracked device. """
|
||||
# pylint: disable=too-many-instance-attributes, too-many-arguments
|
||||
|
||||
host_name = None
|
||||
location_name = None
|
||||
gps = None
|
||||
gps_accuracy = 0
|
||||
last_seen = None
|
||||
battery = None
|
||||
|
||||
# Track if the last update of this device was HOME
|
||||
last_update_home = False
|
||||
_state = STATE_NOT_HOME
|
||||
|
||||
def __init__(self, hass, consider_home, track, dev_id, mac, name=None,
|
||||
picture=None, away_hide=False):
|
||||
def __init__(self, hass, consider_home, home_range, track, dev_id, mac,
|
||||
name=None, picture=None, away_hide=False):
|
||||
self.hass = hass
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
|
||||
|
||||
@ -259,6 +276,8 @@ class Device(Entity):
|
||||
# detected anymore.
|
||||
self.consider_home = consider_home
|
||||
|
||||
# Distance in meters
|
||||
self.home_range = home_range
|
||||
# Device ID
|
||||
self.dev_id = dev_id
|
||||
self.mac = mac
|
||||
@ -273,6 +292,13 @@ class Device(Entity):
|
||||
self.config_picture = picture
|
||||
self.away_hide = away_hide
|
||||
|
||||
@property
|
||||
def gps_home(self):
|
||||
""" Return if device is within range of home. """
|
||||
distance = max(
|
||||
0, self.hass.config.distance(*self.gps) - self.gps_accuracy)
|
||||
return self.gps is not None and distance <= self.home_range
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the entity. """
|
||||
@ -292,8 +318,12 @@ class Device(Entity):
|
||||
attr[ATTR_ENTITY_PICTURE] = self.config_picture
|
||||
|
||||
if self.gps:
|
||||
attr[ATTR_LATITUDE] = self.gps[0],
|
||||
attr[ATTR_LONGITUDE] = self.gps[1],
|
||||
attr[ATTR_LATITUDE] = self.gps[0]
|
||||
attr[ATTR_LONGITUDE] = self.gps[1]
|
||||
attr[ATTR_GPS_ACCURACY] = self.gps_accuracy
|
||||
|
||||
if self.battery:
|
||||
attr[ATTR_BATTERY] = self.battery
|
||||
|
||||
return attr
|
||||
|
||||
@ -302,12 +332,23 @@ class Device(Entity):
|
||||
""" If device should be hidden. """
|
||||
return self.away_hide and self.state != STATE_HOME
|
||||
|
||||
def seen(self, host_name=None, location_name=None, gps=None):
|
||||
def seen(self, host_name=None, location_name=None, gps=None,
|
||||
gps_accuracy=0, battery=None):
|
||||
""" Mark the device as seen. """
|
||||
self.last_seen = dt_util.utcnow()
|
||||
self.host_name = host_name
|
||||
self.location_name = location_name
|
||||
self.gps = gps
|
||||
self.gps_accuracy = gps_accuracy
|
||||
self.battery = battery
|
||||
if gps is None:
|
||||
self.gps = None
|
||||
else:
|
||||
try:
|
||||
self.gps = tuple(float(val) for val in gps)
|
||||
except ValueError:
|
||||
_LOGGER.warning('Could not parse gps value for %s: %s',
|
||||
self.dev_id, gps)
|
||||
self.gps = None
|
||||
self.update()
|
||||
|
||||
def stale(self, now=None):
|
||||
@ -321,6 +362,8 @@ class Device(Entity):
|
||||
return
|
||||
elif self.location_name:
|
||||
self._state = self.location_name
|
||||
elif self.gps is not None:
|
||||
self._state = STATE_HOME if self.gps_home else STATE_NOT_HOME
|
||||
elif self.stale():
|
||||
self._state = STATE_NOT_HOME
|
||||
self.last_update_home = False
|
||||
@ -338,21 +381,21 @@ def convert_csv_config(csv_path, yaml_path):
|
||||
(util.slugify(row['name']) or DEVICE_DEFAULT_NAME).lower(),
|
||||
used_ids)
|
||||
used_ids.add(dev_id)
|
||||
device = Device(None, None, row['track'] == '1', dev_id,
|
||||
device = Device(None, None, None, row['track'] == '1', dev_id,
|
||||
row['device'], row['name'], row['picture'])
|
||||
update_config(yaml_path, dev_id, device)
|
||||
return True
|
||||
|
||||
|
||||
def load_config(path, hass, consider_home):
|
||||
def load_config(path, hass, consider_home, home_range):
|
||||
""" Load devices from YAML config file. """
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
return [
|
||||
Device(hass, consider_home, device.get('track', False),
|
||||
Device(hass, consider_home, home_range, device.get('track', False),
|
||||
str(dev_id).lower(), str(device.get('mac')).upper(),
|
||||
device.get('name'), device.get('picture'),
|
||||
device.get(CONF_AWAY_HIDE, False))
|
||||
device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE))
|
||||
for dev_id, device in load_yaml_config_file(path).items()]
|
||||
|
||||
|
||||
|
50
homeassistant/components/device_tracker/demo.py
Normal file
50
homeassistant/components/device_tracker/demo.py
Normal file
@ -0,0 +1,50 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.demo
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Demo platform for the device tracker.
|
||||
|
||||
device_tracker:
|
||||
platform: demo
|
||||
"""
|
||||
import random
|
||||
|
||||
from homeassistant.components.device_tracker import DOMAIN
|
||||
|
||||
|
||||
def setup_scanner(hass, config, see):
|
||||
""" Set up a demo tracker. """
|
||||
|
||||
def offset():
|
||||
""" Return random offset. """
|
||||
return (random.randrange(500, 2000)) / 2e5 * random.choice((-1, 1))
|
||||
|
||||
def random_see(dev_id, name):
|
||||
""" Randomize a sighting. """
|
||||
see(
|
||||
dev_id=dev_id,
|
||||
host_name=name,
|
||||
gps=(hass.config.latitude + offset(),
|
||||
hass.config.longitude + offset()),
|
||||
gps_accuracy=random.randrange(50, 150),
|
||||
battery=random.randrange(10, 90)
|
||||
)
|
||||
|
||||
def observe(call=None):
|
||||
""" Observe three entities. """
|
||||
random_see('demo_paulus', 'Paulus')
|
||||
random_see('demo_anne_therese', 'Anne Therese')
|
||||
|
||||
observe()
|
||||
|
||||
see(
|
||||
dev_id='demo_home_boy',
|
||||
host_name='Home Boy',
|
||||
gps=[hass.config.latitude - 0.00002, hass.config.longitude + 0.00002],
|
||||
gps_accuracy=20,
|
||||
battery=53
|
||||
)
|
||||
|
||||
hass.services.register(DOMAIN, 'demo', observe)
|
||||
|
||||
return True
|
54
homeassistant/components/device_tracker/owntracks.py
Normal file
54
homeassistant/components/device_tracker/owntracks.py
Normal file
@ -0,0 +1,54 @@
|
||||
"""
|
||||
homeassistant.components.device_tracker.owntracks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
OwnTracks platform for the device tracker.
|
||||
|
||||
device_tracker:
|
||||
platform: owntracks
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
|
||||
import homeassistant.components.mqtt as mqtt
|
||||
|
||||
DEPENDENCIES = ['mqtt']
|
||||
|
||||
LOCATION_TOPIC = 'owntracks/+/+'
|
||||
|
||||
|
||||
def setup_scanner(hass, config, see):
|
||||
""" Set up a OwnTracksks tracker. """
|
||||
|
||||
def owntracks_location_update(topic, payload, qos):
|
||||
""" MQTT message received. """
|
||||
|
||||
# Docs on available data:
|
||||
# http://owntracks.org/booklet/tech/json/#_typelocation
|
||||
try:
|
||||
data = json.loads(payload)
|
||||
except ValueError:
|
||||
# If invalid JSON
|
||||
logging.getLogger(__name__).error(
|
||||
'Unable to parse payload as JSON: %s', payload)
|
||||
return
|
||||
|
||||
if data.get('_type') != 'location':
|
||||
return
|
||||
|
||||
parts = topic.split('/')
|
||||
kwargs = {
|
||||
'dev_id': '{}_{}'.format(parts[1], parts[2]),
|
||||
'host_name': parts[1],
|
||||
'gps': (data['lat'], data['lon']),
|
||||
}
|
||||
if 'acc' in data:
|
||||
kwargs['gps_accuracy'] = data['acc']
|
||||
if 'batt' in data:
|
||||
kwargs['battery'] = data['batt']
|
||||
|
||||
see(**kwargs)
|
||||
|
||||
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
|
||||
|
||||
return True
|
@ -19,7 +19,7 @@ from homeassistant.const import (
|
||||
|
||||
DOMAIN = "discovery"
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = ['netdisco==0.4']
|
||||
REQUIREMENTS = ['netdisco==0.4.1']
|
||||
|
||||
SCAN_INTERVAL = 300 # seconds
|
||||
|
||||
|
@ -21,7 +21,8 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
FRONTEND_URLS = [
|
||||
URL_ROOT, '/logbook', '/history', '/devService', '/devState', '/devEvent']
|
||||
URL_ROOT, '/logbook', '/history', '/map', '/devService', '/devState',
|
||||
'/devEvent']
|
||||
STATES_URL = re.compile(r'/states(/([a-zA-Z\._\-0-9/]+)|)')
|
||||
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||
VERSION = "0ab148ece11ddde26b95460c2c91da3d"
|
||||
VERSION = "7301f590f66ffc5b34d3991fc9eb0247"
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
Subproject commit 63e039a221ae6771e0d7c6990d9a93b7cc22fc64
|
||||
Subproject commit be2312de03386b05c6d0ff91abe894fd36338d96
|
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 797 B |
@ -147,8 +147,6 @@ def _api_history_period(handler, path_match, data):
|
||||
|
||||
end_time = start_time + one_day
|
||||
|
||||
print("Fetching", start_time, end_time)
|
||||
|
||||
entity_id = data.get('filter_entity_id')
|
||||
|
||||
handler.write_json(
|
||||
|
@ -232,7 +232,12 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||
|
||||
def log_message(self, fmt, *arguments):
|
||||
""" Redirect built-in log to HA logging """
|
||||
_LOGGER.info(fmt, *arguments)
|
||||
if self.server.no_password_set:
|
||||
_LOGGER.info(fmt, *arguments)
|
||||
else:
|
||||
_LOGGER.info(
|
||||
fmt, *(arg.replace(self.server.api_password, '*******')
|
||||
if isinstance(arg, str) else arg for arg in arguments))
|
||||
|
||||
def _handle_request(self, method): # pylint: disable=too-many-branches
|
||||
""" Does some common checks and calls appropriate method. """
|
||||
|
@ -6,12 +6,14 @@ Support for Tellstick lights.
|
||||
import logging
|
||||
# pylint: disable=no-name-in-module, import-error
|
||||
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
|
||||
from homeassistant.const import ATTR_FRIENDLY_NAME
|
||||
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP,
|
||||
ATTR_FRIENDLY_NAME)
|
||||
import tellcore.constants as tellcore_constants
|
||||
|
||||
REQUIREMENTS = ['tellcore-py==1.0.4']
|
||||
from tellcore.library import DirectCallbackDispatcher
|
||||
REQUIREMENTS = ['tellcore-py==1.1.2']
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
""" Find and return Tellstick lights. """
|
||||
|
||||
@ -22,13 +24,32 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"Failed to import tellcore")
|
||||
return []
|
||||
|
||||
core = telldus.TelldusCore()
|
||||
core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher())
|
||||
|
||||
switches_and_lights = core.devices()
|
||||
lights = []
|
||||
|
||||
for switch in switches_and_lights:
|
||||
if switch.methods(tellcore_constants.TELLSTICK_DIM):
|
||||
lights.append(TellstickLight(switch))
|
||||
|
||||
def _device_event_callback(id_, method, data, cid):
|
||||
""" Called from the TelldusCore library to update one device """
|
||||
for light_device in lights:
|
||||
if light_device.tellstick_device.id == id_:
|
||||
# Execute the update in another thread
|
||||
light_device.update_ha_state(True)
|
||||
break
|
||||
|
||||
callback_id = core.register_device_event(_device_event_callback)
|
||||
|
||||
def unload_telldus_lib(event):
|
||||
""" Un-register the callback bindings """
|
||||
if callback_id is not None:
|
||||
core.unregister_callback(callback_id)
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, unload_telldus_lib)
|
||||
|
||||
add_devices_callback(lights)
|
||||
|
||||
|
||||
@ -40,15 +61,15 @@ class TellstickLight(Light):
|
||||
tellcore_constants.TELLSTICK_UP |
|
||||
tellcore_constants.TELLSTICK_DOWN)
|
||||
|
||||
def __init__(self, tellstick):
|
||||
self.tellstick = tellstick
|
||||
self.state_attr = {ATTR_FRIENDLY_NAME: tellstick.name}
|
||||
def __init__(self, tellstick_device):
|
||||
self.tellstick_device = tellstick_device
|
||||
self.state_attr = {ATTR_FRIENDLY_NAME: tellstick_device.name}
|
||||
self._brightness = 0
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the switch if any. """
|
||||
return self.tellstick.name
|
||||
return self.tellstick_device.name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
@ -62,8 +83,9 @@ class TellstickLight(Light):
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
""" Turns the switch off. """
|
||||
self.tellstick.turn_off()
|
||||
self.tellstick_device.turn_off()
|
||||
self._brightness = 0
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
""" Turns the switch on. """
|
||||
@ -74,11 +96,12 @@ class TellstickLight(Light):
|
||||
else:
|
||||
self._brightness = brightness
|
||||
|
||||
self.tellstick.dim(self._brightness)
|
||||
self.tellstick_device.dim(self._brightness)
|
||||
self.update_ha_state()
|
||||
|
||||
def update(self):
|
||||
""" Update state of the light. """
|
||||
last_command = self.tellstick.last_sent_command(
|
||||
last_command = self.tellstick_device.last_sent_command(
|
||||
self.last_sent_command_mask)
|
||||
|
||||
if last_command == tellcore_constants.TELLSTICK_TURNON:
|
||||
@ -88,6 +111,11 @@ class TellstickLight(Light):
|
||||
elif (last_command == tellcore_constants.TELLSTICK_DIM or
|
||||
last_command == tellcore_constants.TELLSTICK_UP or
|
||||
last_command == tellcore_constants.TELLSTICK_DOWN):
|
||||
last_sent_value = self.tellstick.last_sent_value()
|
||||
last_sent_value = self.tellstick_device.last_sent_value()
|
||||
if last_sent_value is not None:
|
||||
self._brightness = last_sent_value
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" Tells Home Assistant not to poll this entity. """
|
||||
return False
|
||||
|
@ -132,7 +132,10 @@ def humanify(events):
|
||||
# Process events
|
||||
for event in events_batch:
|
||||
if event.event_type == EVENT_STATE_CHANGED:
|
||||
entity_id = event.data['entity_id']
|
||||
entity_id = event.data.get('entity_id')
|
||||
|
||||
if entity_id is None:
|
||||
continue
|
||||
|
||||
if entity_id.startswith('sensor.'):
|
||||
last_sensor_event[entity_id] = event
|
||||
@ -159,10 +162,12 @@ def humanify(events):
|
||||
|
||||
to_state = State.from_dict(event.data.get('new_state'))
|
||||
|
||||
# if last_changed == last_updated only attributes have changed
|
||||
# we do not report on that yet.
|
||||
# if last_changed != last_updated only attributes have changed
|
||||
# we do not report on that yet. Also filter auto groups.
|
||||
if not to_state or \
|
||||
to_state.last_changed != to_state.last_updated:
|
||||
to_state.last_changed != to_state.last_updated or \
|
||||
to_state.domain == 'group' and \
|
||||
to_state.attributes.get('auto', False):
|
||||
continue
|
||||
|
||||
domain = to_state.domain
|
||||
|
188
homeassistant/components/media_player/plex.py
Normal file
188
homeassistant/components/media_player/plex.py
Normal file
@ -0,0 +1,188 @@
|
||||
"""
|
||||
homeassistant.components.media_player.plex
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides an interface to the Plex API
|
||||
|
||||
Configuration:
|
||||
|
||||
To use Plex add something like this to your configuration:
|
||||
|
||||
media_player:
|
||||
platform: plex
|
||||
name: plex_server
|
||||
user: plex
|
||||
password: my_secure_password
|
||||
|
||||
Variables:
|
||||
|
||||
name
|
||||
*Required
|
||||
The name of the backend device (Under Plex Media Server > settings > server).
|
||||
|
||||
user
|
||||
*Required
|
||||
The Plex username
|
||||
|
||||
password
|
||||
*Required
|
||||
The Plex password
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_NEXT_TRACK, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
|
||||
from homeassistant.const import (
|
||||
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN)
|
||||
|
||||
REQUIREMENTS = ['https://github.com/miniconfig/python-plexapi/archive/'
|
||||
'437e36dca3b7780dc0cb73941d662302c0cd2fa9.zip'
|
||||
'#python-plexapi==1.0.2']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the plex platform. """
|
||||
from plexapi.myplex import MyPlexUser
|
||||
name = config.get('name', '')
|
||||
user = config.get('user', '')
|
||||
password = config.get('password', '')
|
||||
plexuser = MyPlexUser.signin(user, password)
|
||||
plexserver = plexuser.getResource(name).connect()
|
||||
dev = plexserver.clients()
|
||||
for device in dev:
|
||||
if "PlayStation" not in device.name:
|
||||
add_devices([PlexClient(device.name, plexserver)])
|
||||
|
||||
|
||||
class PlexClient(MediaPlayerDevice):
|
||||
""" Represents a Plex device. """
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
|
||||
def __init__(self, name, plexserver):
|
||||
self.client = plexserver.client(name)
|
||||
self._name = name
|
||||
self._media = None
|
||||
self.update()
|
||||
self.server = plexserver
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
if self._media is None:
|
||||
return STATE_IDLE
|
||||
else:
|
||||
state = self._media.get('state')
|
||||
if state == 'playing':
|
||||
return STATE_PLAYING
|
||||
elif state == 'paused':
|
||||
return STATE_PAUSED
|
||||
return STATE_UNKNOWN
|
||||
|
||||
def update(self):
|
||||
timeline = self.client.timeline()
|
||||
for timeline_item in timeline:
|
||||
if timeline_item.get('state') in ('playing', 'paused'):
|
||||
self._media = timeline_item
|
||||
|
||||
@property
|
||||
def media_content_id(self):
|
||||
""" Content ID of current playing media. """
|
||||
if self._media is not None:
|
||||
return self._media.get('ratingKey')
|
||||
|
||||
@property
|
||||
def media_content_type(self):
|
||||
""" Content type of current playing media. """
|
||||
if self._media is None:
|
||||
return None
|
||||
media_type = self.server.library.getByKey(
|
||||
self.media_content_id).type
|
||||
if media_type == 'episode':
|
||||
return MEDIA_TYPE_TVSHOW
|
||||
elif media_type == 'movie':
|
||||
return MEDIA_TYPE_VIDEO
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_duration(self):
|
||||
""" Duration of current playing media in seconds. """
|
||||
if self._media is not None:
|
||||
total_time = self._media.get('duration')
|
||||
return total_time
|
||||
|
||||
@property
|
||||
def media_image_url(self):
|
||||
""" Image url of current playing media. """
|
||||
if self._media is not None:
|
||||
return self.server.library.getByKey(self.media_content_id).thumbUrl
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_title(self):
|
||||
""" Title of current playing media. """
|
||||
# find a string we can use as a title
|
||||
if self._media is not None:
|
||||
return self.server.library.getByKey(self.media_content_id).title
|
||||
|
||||
@property
|
||||
def media_season(self):
|
||||
""" Season of curent playing media. (TV Show only) """
|
||||
if self._media is not None:
|
||||
show_season = self.server.library.getByKey(
|
||||
self.media_content_id).season().index
|
||||
return show_season
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_series_title(self):
|
||||
""" Series title of current playing media. (TV Show only)"""
|
||||
if self._media is not None:
|
||||
series_title = self.server.library.getByKey(
|
||||
self.media_content_id).show().title
|
||||
return series_title
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_episode(self):
|
||||
""" Episode of current playing media. (TV Show only) """
|
||||
if self._media is not None:
|
||||
show_episode = self.server.library.getByKey(
|
||||
self.media_content_id).index
|
||||
return show_episode
|
||||
return None
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
""" Flags of media commands that are supported. """
|
||||
return SUPPORT_PLEX
|
||||
|
||||
def media_play(self):
|
||||
""" media_play media player. """
|
||||
self.client.play()
|
||||
|
||||
def media_pause(self):
|
||||
""" media_pause media player. """
|
||||
self.client.pause()
|
||||
|
||||
def media_next_track(self):
|
||||
""" Send next track command. """
|
||||
self.client.skipNext()
|
||||
|
||||
def media_previous_track(self):
|
||||
""" Send previous track command. """
|
||||
self.client.skipPrevious()
|
@ -1,7 +1,7 @@
|
||||
"""
|
||||
homeassistant.components.script
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
entity_id
|
||||
Scripts are a sequence of actions that can be triggered manually
|
||||
by the user or automatically based upon automation events, etc.
|
||||
"""
|
||||
@ -25,6 +25,7 @@ CONF_SEQUENCE = "sequence"
|
||||
CONF_EVENT = "event"
|
||||
CONF_EVENT_DATA = "event_data"
|
||||
CONF_DELAY = "delay"
|
||||
ATTR_ENTITY_ID = "entity_id"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -43,15 +44,22 @@ def setup(hass, config):
|
||||
hass.services.register(DOMAIN, name, script)
|
||||
scripts.append(script)
|
||||
|
||||
def _get_entities(service):
|
||||
""" Make sure that we always get a list of entities """
|
||||
if isinstance(service.data[ATTR_ENTITY_ID], list):
|
||||
return service.data[ATTR_ENTITY_ID]
|
||||
else:
|
||||
return [service.data[ATTR_ENTITY_ID]]
|
||||
|
||||
def turn_on(service):
|
||||
""" Calls a script. """
|
||||
for entity_id in service.data['entity_id']:
|
||||
for entity_id in _get_entities(service):
|
||||
domain, service = split_entity_id(entity_id)
|
||||
hass.services.call(domain, service, {})
|
||||
|
||||
def turn_off(service):
|
||||
""" Cancels a script. """
|
||||
for entity_id in service.data['entity_id']:
|
||||
for entity_id in _get_entities(service):
|
||||
for script in scripts:
|
||||
if script.entity_id == entity_id:
|
||||
script.cancel()
|
||||
|
@ -108,12 +108,15 @@ class CommandSensor(Entity):
|
||||
self.data.update()
|
||||
value = self.data.value
|
||||
|
||||
if value is not None:
|
||||
if self._corr_factor is not None:
|
||||
self._state = round((int(value) * self._corr_factor),
|
||||
self._decimal_places)
|
||||
else:
|
||||
self._state = value
|
||||
try:
|
||||
if value is not None:
|
||||
if self._corr_factor is not None:
|
||||
self._state = round((float(value) * self._corr_factor),
|
||||
self._decimal_places)
|
||||
else:
|
||||
self._state = value
|
||||
except ValueError:
|
||||
self._state = value
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
182
homeassistant/components/sensor/rest.py
Normal file
182
homeassistant/components/sensor/rest.py
Normal file
@ -0,0 +1,182 @@
|
||||
"""
|
||||
homeassistant.components.sensor.rest
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
The rest sensor will consume JSON responses sent by an exposed REST API.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.rest.html
|
||||
"""
|
||||
import logging
|
||||
import requests
|
||||
from json import loads
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'REST Sensor'
|
||||
DEFAULT_METHOD = 'GET'
|
||||
|
||||
# Return cached results if last scan was less then this time ago
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
||||
|
||||
|
||||
# pylint: disable=unused-variable
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Get the REST sensor. """
|
||||
|
||||
use_get = False
|
||||
use_post = False
|
||||
|
||||
resource = config.get('resource', None)
|
||||
method = config.get('method', DEFAULT_METHOD)
|
||||
payload = config.get('payload', None)
|
||||
|
||||
if method == 'GET':
|
||||
use_get = True
|
||||
elif method == 'POST':
|
||||
use_post = True
|
||||
|
||||
try:
|
||||
if use_get:
|
||||
response = requests.get(resource, timeout=10)
|
||||
elif use_post:
|
||||
response = requests.post(resource, data=payload, timeout=10)
|
||||
if not response.ok:
|
||||
_LOGGER.error('Response status is "%s"', response.status_code)
|
||||
return False
|
||||
except requests.exceptions.MissingSchema:
|
||||
_LOGGER.error('Missing resource or schema in configuration. '
|
||||
'Add http:// to your URL.')
|
||||
return False
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error('No route to resource/endpoint. '
|
||||
'Please check the URL in the configuration file.')
|
||||
return False
|
||||
|
||||
try:
|
||||
data = loads(response.text)
|
||||
except ValueError:
|
||||
_LOGGER.error('No valid JSON in the response in: %s', data)
|
||||
return False
|
||||
|
||||
try:
|
||||
data[config.get('variable')]
|
||||
except KeyError:
|
||||
_LOGGER.error('Variable "%s" not found in response: "%s"',
|
||||
config.get('variable'), data)
|
||||
return False
|
||||
|
||||
if use_get:
|
||||
rest = RestDataGet(resource)
|
||||
elif use_post:
|
||||
rest = RestDataPost(resource, payload)
|
||||
|
||||
add_devices([RestSensor(rest,
|
||||
config.get('name', DEFAULT_NAME),
|
||||
config.get('variable'),
|
||||
config.get('unit_of_measurement'),
|
||||
config.get('correction_factor', None),
|
||||
config.get('decimal_places', None))])
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
class RestSensor(Entity):
|
||||
""" Implements a REST sensor. """
|
||||
|
||||
def __init__(self, rest, name, variable, unit_of_measurement, corr_factor,
|
||||
decimal_places):
|
||||
self.rest = rest
|
||||
self._name = name
|
||||
self._variable = variable
|
||||
self._state = 'n/a'
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self._corr_factor = corr_factor
|
||||
self._decimal_places = decimal_places
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" The name of the sensor. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Unit the value is expressed in. """
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
return self._state
|
||||
|
||||
def update(self):
|
||||
""" Gets the latest data from REST API and updates the state. """
|
||||
self.rest.update()
|
||||
value = self.rest.data
|
||||
|
||||
if 'error' in value:
|
||||
self._state = value['error']
|
||||
else:
|
||||
try:
|
||||
if value is not None:
|
||||
if self._corr_factor is not None \
|
||||
and self._decimal_places is not None:
|
||||
self._state = round(
|
||||
(float(value[self._variable]) *
|
||||
float(self._corr_factor)),
|
||||
self._decimal_places)
|
||||
elif self._corr_factor is not None \
|
||||
and self._decimal_places is None:
|
||||
self._state = round(float(value[self._variable]) *
|
||||
float(self._corr_factor))
|
||||
else:
|
||||
self._state = value[self._variable]
|
||||
except ValueError:
|
||||
self._state = value[self._variable]
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class RestDataGet(object):
|
||||
""" Class for handling the data retrieval with GET method. """
|
||||
|
||||
def __init__(self, resource):
|
||||
self._resource = resource
|
||||
self.data = dict()
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
""" Gets the latest data from REST service with GET method. """
|
||||
try:
|
||||
response = requests.get(self._resource, timeout=10)
|
||||
if 'error' in self.data:
|
||||
del self.data['error']
|
||||
self.data = response.json()
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error("No route to resource/endpoint.")
|
||||
self.data['error'] = 'N/A'
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class RestDataPost(object):
|
||||
""" Class for handling the data retrieval with POST method. """
|
||||
|
||||
def __init__(self, resource, payload):
|
||||
self._resource = resource
|
||||
self._payload = payload
|
||||
self.data = dict()
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
""" Gets the latest data from REST service with POST method. """
|
||||
try:
|
||||
response = requests.post(self._resource, data=self._payload,
|
||||
timeout=10)
|
||||
if 'error' in self.data:
|
||||
del self.data['error']
|
||||
self.data = response.json()
|
||||
except requests.exceptions.ConnectionError:
|
||||
_LOGGER.error("No route to resource/endpoint.")
|
||||
self.data['error'] = 'N/A'
|
@ -34,7 +34,7 @@ import homeassistant.util as util
|
||||
|
||||
DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit'])
|
||||
|
||||
REQUIREMENTS = ['tellcore-py==1.0.4']
|
||||
REQUIREMENTS = ['tellcore-py==1.1.2']
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
@ -11,14 +11,14 @@ signal_repetitions: 3
|
||||
"""
|
||||
import logging
|
||||
|
||||
|
||||
from homeassistant.const import ATTR_FRIENDLY_NAME
|
||||
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP,
|
||||
ATTR_FRIENDLY_NAME)
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
import tellcore.constants as tellcore_constants
|
||||
|
||||
from tellcore.library import DirectCallbackDispatcher
|
||||
SINGAL_REPETITIONS = 1
|
||||
|
||||
REQUIREMENTS = ['tellcore-py==1.0.4']
|
||||
REQUIREMENTS = ['tellcore-py==1.1.2']
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@ -31,16 +31,34 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"Failed to import tellcore")
|
||||
return
|
||||
|
||||
core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher())
|
||||
|
||||
signal_repetitions = config.get('signal_repetitions', SINGAL_REPETITIONS)
|
||||
|
||||
core = telldus.TelldusCore()
|
||||
switches_and_lights = core.devices()
|
||||
|
||||
switches = []
|
||||
|
||||
for switch in switches_and_lights:
|
||||
if not switch.methods(tellcore_constants.TELLSTICK_DIM):
|
||||
switches.append(TellstickSwitchDevice(switch, signal_repetitions))
|
||||
switches.append(
|
||||
TellstickSwitchDevice(switch, signal_repetitions))
|
||||
|
||||
def _device_event_callback(id_, method, data, cid):
|
||||
""" Called from the TelldusCore library to update one device """
|
||||
for switch_device in switches:
|
||||
if switch_device.tellstick_device.id == id_:
|
||||
switch_device.update_ha_state()
|
||||
break
|
||||
|
||||
callback_id = core.register_device_event(_device_event_callback)
|
||||
|
||||
def unload_telldus_lib(event):
|
||||
""" Un-register the callback bindings """
|
||||
if callback_id is not None:
|
||||
core.unregister_callback(callback_id)
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, unload_telldus_lib)
|
||||
|
||||
add_devices_callback(switches)
|
||||
|
||||
@ -50,15 +68,20 @@ class TellstickSwitchDevice(ToggleEntity):
|
||||
last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
|
||||
tellcore_constants.TELLSTICK_TURNOFF)
|
||||
|
||||
def __init__(self, tellstick, signal_repetitions):
|
||||
self.tellstick = tellstick
|
||||
self.state_attr = {ATTR_FRIENDLY_NAME: tellstick.name}
|
||||
def __init__(self, tellstick_device, signal_repetitions):
|
||||
self.tellstick_device = tellstick_device
|
||||
self.state_attr = {ATTR_FRIENDLY_NAME: tellstick_device.name}
|
||||
self.signal_repetitions = signal_repetitions
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" Tells Home Assistant not to poll this entity. """
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the switch if any. """
|
||||
return self.tellstick.name
|
||||
return self.tellstick_device.name
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
@ -68,7 +91,7 @@ class TellstickSwitchDevice(ToggleEntity):
|
||||
@property
|
||||
def is_on(self):
|
||||
""" True if switch is on. """
|
||||
last_command = self.tellstick.last_sent_command(
|
||||
last_command = self.tellstick_device.last_sent_command(
|
||||
self.last_sent_command_mask)
|
||||
|
||||
return last_command == tellcore_constants.TELLSTICK_TURNON
|
||||
@ -76,9 +99,11 @@ class TellstickSwitchDevice(ToggleEntity):
|
||||
def turn_on(self, **kwargs):
|
||||
""" Turns the switch on. """
|
||||
for _ in range(self.signal_repetitions):
|
||||
self.tellstick.turn_on()
|
||||
self.tellstick_device.turn_on()
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
""" Turns the switch off. """
|
||||
for _ in range(self.signal_repetitions):
|
||||
self.tellstick.turn_off()
|
||||
self.tellstick_device.turn_off()
|
||||
self.update_ha_state()
|
||||
|
@ -1,6 +1,6 @@
|
||||
""" Constants used by Home Assistant components. """
|
||||
|
||||
__version__ = "0.7.3dev0"
|
||||
__version__ = "0.7.4dev0"
|
||||
|
||||
# Can be used to specify a catch all when registering state or event listeners.
|
||||
MATCH_ALL = '*'
|
||||
|
@ -26,6 +26,7 @@ from homeassistant.exceptions import (
|
||||
HomeAssistantError, InvalidEntityFormatError)
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.dt as date_util
|
||||
import homeassistant.util.location as location
|
||||
import homeassistant.helpers.temperature as temp_helper
|
||||
from homeassistant.config import get_default_config_dir
|
||||
|
||||
@ -676,6 +677,10 @@ class Config(object):
|
||||
# Directory that holds the configuration
|
||||
self.config_dir = get_default_config_dir()
|
||||
|
||||
def distance(self, lat, lon):
|
||||
""" Calculate distance from Home Assistant in meters. """
|
||||
return location.distance(self.latitude, self.longitude, lat, lon)
|
||||
|
||||
def path(self, *path):
|
||||
""" Returns path to the file within the config dir. """
|
||||
return os.path.join(self.config_dir, *path)
|
||||
|
@ -9,7 +9,9 @@ import logging
|
||||
from homeassistant.core import State
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import (
|
||||
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
||||
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
|
||||
STATE_PLAYING, STATE_PAUSED, ATTR_ENTITY_ID)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -55,7 +57,11 @@ def reproduce_state(hass, states, blocking=False):
|
||||
state.entity_id)
|
||||
continue
|
||||
|
||||
if state.state == STATE_ON:
|
||||
if state.domain == 'media_player' and state.state == STATE_PAUSED:
|
||||
service = SERVICE_MEDIA_PAUSE
|
||||
elif state.domain == 'media_player' and state.state == STATE_PLAYING:
|
||||
service = SERVICE_MEDIA_PLAY
|
||||
elif state.state == STATE_ON:
|
||||
service = SERVICE_TURN_ON
|
||||
elif state.state == STATE_OFF:
|
||||
service = SERVICE_TURN_OFF
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Module with location helpers."""
|
||||
import collections
|
||||
from math import radians, cos, sin, asin, sqrt
|
||||
|
||||
import requests
|
||||
|
||||
@ -28,3 +29,20 @@ def detect_location_info():
|
||||
'BS', 'BZ', 'KY', 'PW', 'US', 'AS', 'VI')
|
||||
|
||||
return LocationInfo(**data)
|
||||
|
||||
|
||||
# From: http://stackoverflow.com/a/4913653/646416
|
||||
def distance(lon1, lat1, lon2, lat2):
|
||||
"""
|
||||
Calculate the great circle distance in meters between two points specified
|
||||
in decimal degrees on the earth using the Haversine algorithm.
|
||||
"""
|
||||
# convert decimal degrees to radians
|
||||
lon1, lat1, lon2, lat2 = (radians(val) for val in (lon1, lat1, lon2, lat2))
|
||||
|
||||
dlon = lon2 - lon1
|
||||
dlat = lat2 - lat1
|
||||
angle = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
|
||||
# Radius of earth in meters.
|
||||
radius = 6371000
|
||||
return 2 * radius * asin(sqrt(angle))
|
||||
|
2
pytest.ini
Normal file
2
pytest.ini
Normal file
@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
testpaths = tests
|
@ -22,7 +22,7 @@ pychromecast==0.6.12
|
||||
pyuserinput==0.1.9
|
||||
|
||||
# Tellstick bindings (*.tellstick)
|
||||
tellcore-py==1.0.4
|
||||
tellcore-py==1.1.2
|
||||
|
||||
# Nmap bindings (device_tracker.nmap)
|
||||
python-nmap==0.4.3
|
||||
@ -86,7 +86,7 @@ https://github.com/theolind/pymysensors/archive/35b87d880147a34107da0d40cb815d75
|
||||
pynetgear==0.3
|
||||
|
||||
# Netdisco (discovery)
|
||||
netdisco==0.4
|
||||
netdisco==0.4.1
|
||||
|
||||
# Wemo (switch.wemo)
|
||||
pywemo==0.3
|
||||
@ -133,3 +133,6 @@ https://github.com/balloob/home-assistant-vera-api/archive/a8f823066ead6c7da6fb5
|
||||
|
||||
# Sonos bindings (media_player.sonos)
|
||||
SoCo==0.11.1
|
||||
|
||||
# PlexAPI (media_player.plex)
|
||||
https://github.com/miniconfig/python-plexapi/archive/437e36dca3b7780dc0cb73941d662302c0cd2fa9.zip#python-plexapi==1.0.2
|
||||
|
@ -3,5 +3,12 @@
|
||||
# script/cibuild: Setup environment for CI to run tests. This is primarily
|
||||
# designed to run on the continuous integration server.
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
script/test coverage
|
||||
|
||||
STATUS=$?
|
||||
|
||||
coveralls
|
||||
|
||||
exit $STATUS
|
||||
|
@ -34,25 +34,27 @@ RUN_AS="USER"
|
||||
PID_FILE="/var/run/hass.pid"
|
||||
CONFIG_DIR="/var/opt/homeassistant"
|
||||
FLAGS="-v --config $CONFIG_DIR --pid-file $PID_FILE --daemon"
|
||||
REDIRECT="> $CONFIG_DIR/home-assistant.log 2>&1"
|
||||
|
||||
start() {
|
||||
if [ -f $PID_FILE ] && kill -0 $(cat $PID_FILE); then
|
||||
if [ -f $PID_FILE ] && kill -0 $(cat $PID_FILE) 2> /dev/null; then
|
||||
echo 'Service already running' >&2
|
||||
return 1
|
||||
fi
|
||||
echo 'Starting service…' >&2
|
||||
local CMD="$PRE_EXEC hass $FLAGS;"
|
||||
local CMD="$PRE_EXEC hass $FLAGS $REDIRECT;"
|
||||
su -c "$CMD" $RUN_AS
|
||||
echo 'Service started' >&2
|
||||
}
|
||||
|
||||
stop() {
|
||||
if [ ! -f "$PID_FILE" ] || ! kill -0 $(cat "$PID_FILE"); then
|
||||
if [ ! -f "$PID_FILE" ] || ! kill -0 $(cat "$PID_FILE") 2> /dev/null; then
|
||||
echo 'Service not running' >&2
|
||||
return 1
|
||||
fi
|
||||
echo 'Stopping service…' >&2
|
||||
kill -3 $(cat "$PID_FILE")
|
||||
while ps -p $(cat "$PID_FILE") > /dev/null 2>&1; do sleep 1;done;
|
||||
echo 'Service stopped' >&2
|
||||
}
|
||||
|
||||
|
14
script/home-assistant@.service
Normal file
14
script/home-assistant@.service
Normal file
@ -0,0 +1,14 @@
|
||||
# This is a simple service file for systems with systemd to tun HA as user.
|
||||
#
|
||||
[Unit]
|
||||
Description=Home Assistant for %i
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=%i
|
||||
WorkingDirectory=%h
|
||||
ExecStart=/usr/bin/hass --config %h/.homeassistant/
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
12
script/lint
12
script/lint
@ -3,7 +3,17 @@
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
echo "Checking style with flake8..."
|
||||
flake8 homeassistant
|
||||
flake8 --exclude www_static homeassistant
|
||||
|
||||
FLAKE8_STATUS=$?
|
||||
|
||||
echo "Checking style with pylint..."
|
||||
pylint homeassistant
|
||||
PYLINT_STATUS=$?
|
||||
|
||||
if [ $FLAKE8_STATUS -eq 0 ]
|
||||
then
|
||||
exit $PYLINT_STATUS
|
||||
else
|
||||
exit $FLAKE8_STATUS
|
||||
fi
|
||||
|
21
script/release
Executable file
21
script/release
Executable file
@ -0,0 +1,21 @@
|
||||
# Pushes a new version to PyPi
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
head -n 3 homeassistant/const.py | tail -n 1 | grep dev
|
||||
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
echo "Release version should not contain dev tag"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CURRENT_BRANCH=`git rev-parse --abbrev-ref HEAD`
|
||||
|
||||
if [ "$CURRENT_BRANCH" != "master" ]
|
||||
then
|
||||
echo "You have to be on the master branch to release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 setup.py sdist bdist_wheel upload
|
15
script/test
15
script/test
@ -7,10 +7,21 @@ cd "$(dirname "$0")/.."
|
||||
|
||||
script/lint
|
||||
|
||||
LINT_STATUS=$?
|
||||
|
||||
echo "Running tests..."
|
||||
|
||||
if [ "$1" = "coverage" ]; then
|
||||
py.test --cov homeassistant tests
|
||||
py.test --cov --cov-report=
|
||||
TEST_STATUS=$?
|
||||
else
|
||||
py.test tests
|
||||
py.test
|
||||
TEST_STATUS=$?
|
||||
fi
|
||||
|
||||
if [ $LINT_STATUS -eq 0 ]
|
||||
then
|
||||
exit $TEST_STATUS
|
||||
else
|
||||
exit $LINT_STATUS
|
||||
fi
|
||||
|
@ -75,7 +75,7 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -93,12 +93,13 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
'event_data': {'some_attr': 'some_value'}
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
self.hass.bus.fire('test_event', {'some_attr': 'some_value'})
|
||||
self.hass.bus.fire('test_event', {'some_attr': 'some_value',
|
||||
'another': 'value'})
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
@ -111,7 +112,7 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
'event_data': {'some_attr': 'some_value'}
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
@ -10,7 +10,7 @@ import homeassistant.components.automation as automation
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
|
||||
|
||||
class TestAutomationEvent(unittest.TestCase):
|
||||
class TestAutomation(unittest.TestCase):
|
||||
""" Test the event automation. """
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
@ -26,7 +26,7 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
""" Stop down stuff we started. """
|
||||
self.hass.stop()
|
||||
|
||||
def test_service_data_not_a_dict(self):
|
||||
def test_old_config_service_data_not_a_dict(self):
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
'platform': 'event',
|
||||
@ -87,6 +87,24 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
self.assertEqual(['hello.world', 'hello.world2'],
|
||||
self.calls[0].data.get(ATTR_ENTITY_ID))
|
||||
|
||||
def test_service_data_not_a_dict(self):
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation',
|
||||
'data': 100,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
def test_service_specify_data(self):
|
||||
automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
@ -95,8 +113,8 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service_data': {'some': 'data'}
|
||||
'service': 'test.automation',
|
||||
'data': {'some': 'data'}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -114,8 +132,8 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service_entity_id': 'hello.world'
|
||||
'service': 'test.automation',
|
||||
'entity_id': 'hello.world'
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -134,8 +152,8 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service_entity_id': ['hello.world', 'hello.world2']
|
||||
'service': 'test.automation',
|
||||
'entity_id': ['hello.world', 'hello.world2']
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -160,7 +178,7 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
}
|
||||
],
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -195,7 +213,7 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
}
|
||||
],
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -239,7 +257,7 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
}
|
||||
],
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -278,7 +296,7 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
],
|
||||
'condition': 'use_trigger_values',
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -314,7 +332,7 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
],
|
||||
'condition': 'use_trigger_values',
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -322,3 +340,34 @@ class TestAutomationEvent(unittest.TestCase):
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
def test_automation_list_setting(self):
|
||||
""" Event is not a valid condition. Will it still work? """
|
||||
self.assertTrue(automation.setup(self.hass, {
|
||||
automation.DOMAIN: [{
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
|
||||
'action': {
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}, {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event_2',
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}]
|
||||
}))
|
||||
|
||||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
|
||||
self.hass.bus.fire('test_event_2')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(2, len(self.calls))
|
||||
|
@ -77,7 +77,7 @@ class TestAutomationState(unittest.TestCase):
|
||||
'topic': 'test-topic'
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -95,7 +95,7 @@ class TestAutomationState(unittest.TestCase):
|
||||
'payload': 'hello'
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -113,7 +113,7 @@ class TestAutomationState(unittest.TestCase):
|
||||
'payload': 'hello'
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
@ -35,7 +35,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
||||
'below': 10,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -56,7 +56,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
||||
'below': 10,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -78,7 +78,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
||||
'below': 10,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -97,7 +97,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
||||
'above': 10,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -119,7 +119,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
||||
'above': 10,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -142,7 +142,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
||||
'above': 10,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -162,7 +162,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
||||
'above': 5,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -181,7 +181,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
||||
'above': 5,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -203,7 +203,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
||||
'above': 5,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -226,7 +226,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
||||
'above': 5,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -244,7 +244,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
||||
'entity_id': 'test.another_entity',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -269,7 +269,7 @@ class TestAutomationNumericState(unittest.TestCase):
|
||||
'below': test_state + 2
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -164,7 +164,7 @@ class TestAutomationState(unittest.TestCase):
|
||||
'entity_id': 'test.entity',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -182,7 +182,7 @@ class TestAutomationState(unittest.TestCase):
|
||||
'from': 'hello'
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -200,7 +200,7 @@ class TestAutomationState(unittest.TestCase):
|
||||
'to': 'world'
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -218,7 +218,7 @@ class TestAutomationState(unittest.TestCase):
|
||||
'state': 'world'
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -237,7 +237,7 @@ class TestAutomationState(unittest.TestCase):
|
||||
'to': 'world'
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -256,7 +256,7 @@ class TestAutomationState(unittest.TestCase):
|
||||
'to': 'world'
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -277,7 +277,7 @@ class TestAutomationState(unittest.TestCase):
|
||||
'to': 'world'
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -294,7 +294,7 @@ class TestAutomationState(unittest.TestCase):
|
||||
'entity_id': 'test.anoter_entity',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -318,7 +318,7 @@ class TestAutomationState(unittest.TestCase):
|
||||
'state': test_state
|
||||
}],
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -51,7 +51,7 @@ class TestAutomationSun(unittest.TestCase):
|
||||
'event': 'sunset',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -77,7 +77,7 @@ class TestAutomationSun(unittest.TestCase):
|
||||
'event': 'sunrise',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -104,7 +104,7 @@ class TestAutomationSun(unittest.TestCase):
|
||||
'offset': '0:30:00'
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -131,7 +131,7 @@ class TestAutomationSun(unittest.TestCase):
|
||||
'offset': '-0:30:00'
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation',
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
@ -36,9 +36,9 @@ class TestAutomationTime(unittest.TestCase):
|
||||
def test_old_config_if_fires_when_hour_matches(self):
|
||||
self.assertTrue(automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
CONF_PLATFORM: 'time',
|
||||
'platform': 'time',
|
||||
time.CONF_HOURS: 0,
|
||||
automation.CONF_SERVICE: 'test.automation'
|
||||
'execute_service': 'test.automation'
|
||||
}
|
||||
}))
|
||||
|
||||
@ -51,9 +51,9 @@ class TestAutomationTime(unittest.TestCase):
|
||||
def test_old_config_if_fires_when_minute_matches(self):
|
||||
self.assertTrue(automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
CONF_PLATFORM: 'time',
|
||||
'platform': 'time',
|
||||
time.CONF_MINUTES: 0,
|
||||
automation.CONF_SERVICE: 'test.automation'
|
||||
'execute_service': 'test.automation'
|
||||
}
|
||||
}))
|
||||
|
||||
@ -66,9 +66,9 @@ class TestAutomationTime(unittest.TestCase):
|
||||
def test_old_config_if_fires_when_second_matches(self):
|
||||
self.assertTrue(automation.setup(self.hass, {
|
||||
automation.DOMAIN: {
|
||||
CONF_PLATFORM: 'time',
|
||||
'platform': 'time',
|
||||
time.CONF_SECONDS: 0,
|
||||
automation.CONF_SERVICE: 'test.automation'
|
||||
'execute_service': 'test.automation'
|
||||
}
|
||||
}))
|
||||
|
||||
@ -85,7 +85,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
time.CONF_HOURS: 0,
|
||||
time.CONF_MINUTES: 0,
|
||||
time.CONF_SECONDS: 0,
|
||||
automation.CONF_SERVICE: 'test.automation'
|
||||
'execute_service': 'test.automation'
|
||||
}
|
||||
}))
|
||||
|
||||
@ -101,7 +101,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
automation.DOMAIN: {
|
||||
CONF_PLATFORM: 'event',
|
||||
event.CONF_EVENT_TYPE: 'test_event',
|
||||
automation.CONF_SERVICE: 'test.automation',
|
||||
'execute_service': 'test.automation',
|
||||
'if': {
|
||||
CONF_PLATFORM: 'time',
|
||||
time.CONF_BEFORE: '10:00'
|
||||
@ -131,7 +131,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
automation.DOMAIN: {
|
||||
CONF_PLATFORM: 'event',
|
||||
event.CONF_EVENT_TYPE: 'test_event',
|
||||
automation.CONF_SERVICE: 'test.automation',
|
||||
'execute_service': 'test.automation',
|
||||
'if': {
|
||||
CONF_PLATFORM: 'time',
|
||||
time.CONF_AFTER: '10:00'
|
||||
@ -161,7 +161,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
automation.DOMAIN: {
|
||||
CONF_PLATFORM: 'event',
|
||||
event.CONF_EVENT_TYPE: 'test_event',
|
||||
automation.CONF_SERVICE: 'test.automation',
|
||||
'execute_service': 'test.automation',
|
||||
'if': {
|
||||
CONF_PLATFORM: 'time',
|
||||
time.CONF_WEEKDAY: 'mon',
|
||||
@ -192,7 +192,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
automation.DOMAIN: {
|
||||
CONF_PLATFORM: 'event',
|
||||
event.CONF_EVENT_TYPE: 'test_event',
|
||||
automation.CONF_SERVICE: 'test.automation',
|
||||
'execute_service': 'test.automation',
|
||||
'if': {
|
||||
CONF_PLATFORM: 'time',
|
||||
time.CONF_WEEKDAY: ['mon', 'tue'],
|
||||
@ -234,7 +234,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
'hours': 0,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -252,7 +252,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
'minutes': 0,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -270,7 +270,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
'seconds': 0,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -290,7 +290,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
'seconds': 3,
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -309,7 +309,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
'after': '5:00:00',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -332,7 +332,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
# Total seconds. Hour = 3600 second
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -356,7 +356,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
'before': '10:00',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -390,7 +390,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
'after': '10:00',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -424,7 +424,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
'weekday': 'mon',
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -459,7 +459,7 @@ class TestAutomationTime(unittest.TestCase):
|
||||
'weekday': ['mon', 'tue'],
|
||||
},
|
||||
'action': {
|
||||
'execute_service': 'test.automation'
|
||||
'service': 'test.automation'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -103,12 +103,12 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
||||
def test_reading_yaml_config(self):
|
||||
dev_id = 'test'
|
||||
device = device_tracker.Device(
|
||||
self.hass, timedelta(seconds=180), True, dev_id, 'AB:CD:EF:GH:IJ',
|
||||
'Test name', 'http://test.picture', True)
|
||||
self.hass, timedelta(seconds=180), 0, True, dev_id,
|
||||
'AB:CD:EF:GH:IJ', 'Test name', 'http://test.picture', True)
|
||||
device_tracker.update_config(self.yaml_devices, dev_id, device)
|
||||
self.assertTrue(device_tracker.setup(self.hass, {}))
|
||||
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
||||
device.consider_home)[0]
|
||||
device.consider_home, 0)[0]
|
||||
self.assertEqual(device.dev_id, config.dev_id)
|
||||
self.assertEqual(device.track, config.track)
|
||||
self.assertEqual(device.mac, config.mac)
|
||||
@ -126,7 +126,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
||||
self.assertTrue(device_tracker.setup(self.hass, {
|
||||
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}))
|
||||
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
||||
timedelta(seconds=0))[0]
|
||||
timedelta(seconds=0), 0)[0]
|
||||
self.assertEqual('dev1', config.dev_id)
|
||||
self.assertEqual(True, config.track)
|
||||
|
||||
@ -176,7 +176,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
||||
picture = 'http://placehold.it/200x200'
|
||||
|
||||
device = device_tracker.Device(
|
||||
self.hass, timedelta(seconds=180), True, dev_id, None,
|
||||
self.hass, timedelta(seconds=180), 0, True, dev_id, None,
|
||||
friendly_name, picture, away_hide=True)
|
||||
device_tracker.update_config(self.yaml_devices, dev_id, device)
|
||||
|
||||
@ -191,7 +191,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
||||
dev_id = 'test_entity'
|
||||
entity_id = device_tracker.ENTITY_ID_FORMAT.format(dev_id)
|
||||
device = device_tracker.Device(
|
||||
self.hass, timedelta(seconds=180), True, dev_id, None,
|
||||
self.hass, timedelta(seconds=180), 0, True, dev_id, None,
|
||||
away_hide=True)
|
||||
device_tracker.update_config(self.yaml_devices, dev_id, device)
|
||||
|
||||
@ -208,7 +208,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
||||
dev_id = 'test_entity'
|
||||
entity_id = device_tracker.ENTITY_ID_FORMAT.format(dev_id)
|
||||
device = device_tracker.Device(
|
||||
self.hass, timedelta(seconds=180), True, dev_id, None,
|
||||
self.hass, timedelta(seconds=180), 0, True, dev_id, None,
|
||||
away_hide=True)
|
||||
device_tracker.update_config(self.yaml_devices, dev_id, device)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user