mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Added state groups and migrated code base to use them.
This commit is contained in:
parent
367433acb2
commit
48026c28c1
@ -5,16 +5,16 @@ longitude=-117.22743
|
||||
[httpinterface]
|
||||
api_password=mypass
|
||||
|
||||
[hue]
|
||||
[light.hue]
|
||||
host=192.168.1.2
|
||||
|
||||
[tomato]
|
||||
[device_tracker.tomato]
|
||||
host=192.168.1.1
|
||||
username=admin
|
||||
password=PASSWORD
|
||||
http_id=aaaaaaaaaaaaaaa
|
||||
|
||||
[netgear]
|
||||
[device_tracker.netgear]
|
||||
host=192.168.1.1
|
||||
username=admin
|
||||
password=PASSWORD
|
||||
@ -23,4 +23,14 @@ password=PASSWORD
|
||||
host=192.168.1.3
|
||||
|
||||
[downloader]
|
||||
download_dir=downloads
|
||||
download_dir=downloads
|
||||
|
||||
[device_sun_light_trigger]
|
||||
# Example how you can specify a specific group that has to be turned on
|
||||
# light_group=living_room
|
||||
|
||||
# A comma seperated list of states that have to be tracked
|
||||
# As a single group
|
||||
[groups]
|
||||
living_room=light.Bowl,light.Ceiling,light.TV_back_light
|
||||
bedroom=light.Bed_light
|
||||
|
@ -16,7 +16,12 @@ logging.basicConfig(level=logging.INFO)
|
||||
|
||||
ALL_EVENTS = '*'
|
||||
|
||||
DOMAIN_HOMEASSISTANT = "homeassistant"
|
||||
DOMAIN = "homeassistant"
|
||||
|
||||
STATE_ON = "on"
|
||||
STATE_OFF = "off"
|
||||
STATE_NOT_HOME = 'device_not_home'
|
||||
STATE_HOME = 'device_home'
|
||||
|
||||
SERVICE_TURN_ON = "turn_on"
|
||||
SERVICE_TURN_OFF = "turn_off"
|
||||
@ -40,7 +45,7 @@ def start_home_assistant(bus):
|
||||
""" Start home assistant. """
|
||||
request_shutdown = threading.Event()
|
||||
|
||||
bus.register_service(DOMAIN_HOMEASSISTANT, SERVICE_HOMEASSISTANT_STOP,
|
||||
bus.register_service(DOMAIN, SERVICE_HOMEASSISTANT_STOP,
|
||||
lambda service: request_shutdown.set())
|
||||
|
||||
Timer(bus)
|
||||
@ -88,25 +93,19 @@ def _matcher(subject, pattern):
|
||||
return '*' in pattern or subject in pattern
|
||||
|
||||
|
||||
def get_grouped_state_cats(statemachine, cat_format_string, strip_prefix):
|
||||
""" Get states that are part of a group of states.
|
||||
def split_state_category(category):
|
||||
""" Splits a state category into domain, object_id. """
|
||||
return category.split(".", 1)
|
||||
|
||||
Example category_format_string can be "devices.{}"
|
||||
|
||||
If input states are devices, devices.paulus and devices.paulus.charging
|
||||
then the output will be paulus if strip_prefix is True, else devices.paulus
|
||||
"""
|
||||
group_prefix = cat_format_string.format("")
|
||||
|
||||
if strip_prefix:
|
||||
id_part = slice(len(group_prefix), None)
|
||||
|
||||
return [cat[id_part] for cat in statemachine.categories
|
||||
if cat.startswith(group_prefix) and cat.count(".") == 1]
|
||||
|
||||
else:
|
||||
return [cat for cat in statemachine.categories
|
||||
if cat.startswith(group_prefix) and cat.count(".") == 1]
|
||||
def filter_categories(categories, domain_filter=None, object_id_only=False):
|
||||
""" Filter a list of categories based on domain. Setting object_id_only
|
||||
will only return the object_ids. """
|
||||
return [
|
||||
split_state_category(cat)[1] if object_id_only else cat
|
||||
for cat in categories if
|
||||
not domain_filter or cat.startswith(domain_filter)
|
||||
]
|
||||
|
||||
|
||||
def create_state(state, attributes=None, last_changed=None):
|
||||
@ -119,10 +118,10 @@ def create_state(state, attributes=None, last_changed=None):
|
||||
'last_changed': datetime_to_str(last_changed)}
|
||||
|
||||
|
||||
def track_state_change(bus, category, from_state, to_state, action):
|
||||
def track_state_change(bus, category, action, from_state=None, to_state=None):
|
||||
""" Helper method to track specific state changes. """
|
||||
from_state = _ensure_list(from_state)
|
||||
to_state = _ensure_list(to_state)
|
||||
from_state = _ensure_list(from_state) if from_state else [ALL_EVENTS]
|
||||
to_state = _ensure_list(to_state) if to_state else [ALL_EVENTS]
|
||||
|
||||
def listener(event):
|
||||
""" State change listener that listens for specific state changes. """
|
||||
|
@ -7,9 +7,9 @@ import logging
|
||||
|
||||
import homeassistant as ha
|
||||
from homeassistant.components import (general, chromecast,
|
||||
device_sun_light_trigger, device,
|
||||
device_sun_light_trigger, device_tracker,
|
||||
downloader, keyboard, light, sun,
|
||||
browser, httpinterface)
|
||||
browser, httpinterface, group)
|
||||
|
||||
|
||||
# pylint: disable=too-many-branches,too-many-locals,too-many-statements
|
||||
@ -34,6 +34,13 @@ def from_config_file(config_path):
|
||||
has_section = config.has_section
|
||||
add_status = lambda name, result: statusses.append((name, result))
|
||||
|
||||
def get_opt_safe(section, option, default=None):
|
||||
""" Failure proof option retriever. """
|
||||
try:
|
||||
return config.get(section, option)
|
||||
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
|
||||
return default
|
||||
|
||||
# Device scanner
|
||||
dev_scan = None
|
||||
|
||||
@ -41,23 +48,23 @@ def from_config_file(config_path):
|
||||
# For the error message if not all option fields exist
|
||||
opt_fields = "host, username, password"
|
||||
|
||||
if has_section('tomato'):
|
||||
if has_section('device_tracker.tomato'):
|
||||
dev_scan_name = "Tomato"
|
||||
opt_fields += ", http_id"
|
||||
|
||||
dev_scan = device.TomatoDeviceScanner(
|
||||
get_opt('tomato', 'host'),
|
||||
get_opt('tomato', 'username'),
|
||||
get_opt('tomato', 'password'),
|
||||
get_opt('tomato', 'http_id'))
|
||||
dev_scan = device_tracker.TomatoDeviceScanner(
|
||||
get_opt('device_tracker.tomato', 'host'),
|
||||
get_opt('device_tracker.tomato', 'username'),
|
||||
get_opt('device_tracker.tomato', 'password'),
|
||||
get_opt('device_tracker.tomato', 'http_id'))
|
||||
|
||||
elif has_section('netgear'):
|
||||
elif has_section('device_tracker.netgear'):
|
||||
dev_scan_name = "Netgear"
|
||||
|
||||
dev_scan = device.NetgearDeviceScanner(
|
||||
get_opt('netgear', 'host'),
|
||||
get_opt('netgear', 'username'),
|
||||
get_opt('netgear', 'password'))
|
||||
dev_scan = device_tracker.NetgearDeviceScanner(
|
||||
get_opt('device_tracker.netgear', 'host'),
|
||||
get_opt('device_tracker.netgear', 'username'),
|
||||
get_opt('device_tracker.netgear', 'password'))
|
||||
|
||||
except ConfigParser.NoOptionError:
|
||||
# If one of the options didn't exist
|
||||
@ -76,7 +83,7 @@ def from_config_file(config_path):
|
||||
|
||||
# Device Tracker
|
||||
if dev_scan:
|
||||
device.DeviceTracker(bus, statemachine, dev_scan)
|
||||
device_tracker.DeviceTracker(bus, statemachine, dev_scan)
|
||||
|
||||
add_status("Device Tracker", True)
|
||||
|
||||
@ -100,11 +107,8 @@ def from_config_file(config_path):
|
||||
chromecast_started = False
|
||||
|
||||
# Light control
|
||||
if has_section("hue"):
|
||||
if has_opt("hue", "host"):
|
||||
light_control = light.HueLightControl(get_opt("hue", "host"))
|
||||
else:
|
||||
light_control = light.HueLightControl()
|
||||
if has_section("light.hue"):
|
||||
light_control = light.HueLightControl(get_opt_safe("hue", "host"))
|
||||
|
||||
add_status("Light Control - Hue", light_control.success_init)
|
||||
|
||||
@ -112,11 +116,6 @@ def from_config_file(config_path):
|
||||
else:
|
||||
light_control = None
|
||||
|
||||
# Light trigger
|
||||
if light_control:
|
||||
add_status("Light Trigger",
|
||||
device_sun_light_trigger.setup(bus, statemachine))
|
||||
|
||||
if has_opt("downloader", "download_dir"):
|
||||
add_status("Downloader", downloader.setup(
|
||||
bus, get_opt("downloader", "download_dir")))
|
||||
@ -137,6 +136,21 @@ def from_config_file(config_path):
|
||||
|
||||
add_status("HTTPInterface", True)
|
||||
|
||||
# Init groups
|
||||
if has_section("groups"):
|
||||
for name, categories in config.items("groups"):
|
||||
add_status("Group - {}".format(name),
|
||||
group.setup(bus, statemachine, name,
|
||||
categories.split(",")))
|
||||
|
||||
# Light trigger
|
||||
if light_control:
|
||||
light_group = get_opt_safe("device_sun_light_trigger", "light_group")
|
||||
|
||||
add_status("Light Trigger",
|
||||
device_sun_light_trigger.setup(bus, statemachine,
|
||||
light_group))
|
||||
|
||||
for component, success_init in statusses:
|
||||
status = "initialized" if success_init else "Failed to initialize"
|
||||
|
||||
|
@ -5,7 +5,7 @@ homeassistant.components.browser
|
||||
Provides functionality to launch a webbrowser on the host machine.
|
||||
"""
|
||||
|
||||
DOMAIN_BROWSER = "browser"
|
||||
DOMAIN = "browser"
|
||||
|
||||
SERVICE_BROWSE_URL = "browse_url"
|
||||
|
||||
@ -16,7 +16,7 @@ def setup(bus):
|
||||
|
||||
import webbrowser
|
||||
|
||||
bus.register_service(DOMAIN_BROWSER, SERVICE_BROWSE_URL,
|
||||
bus.register_service(DOMAIN, SERVICE_BROWSE_URL,
|
||||
lambda service: webbrowser.open(service.data['url']))
|
||||
|
||||
return True
|
||||
|
@ -4,6 +4,7 @@ homeassistant.components.chromecast
|
||||
|
||||
Provides functionality to interact with Chromecasts.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.external import pychromecast
|
||||
|
||||
@ -11,11 +12,11 @@ import homeassistant as ha
|
||||
import homeassistant.util as util
|
||||
|
||||
|
||||
DOMAIN_CHROMECAST = "chromecast"
|
||||
DOMAIN = "chromecast"
|
||||
|
||||
SERVICE_YOUTUBE_VIDEO = "play_youtube_video"
|
||||
|
||||
STATE_CATEGORY_FORMAT = 'chromecasts.{}'
|
||||
STATE_CATEGORY_FORMAT = DOMAIN + '.{}'
|
||||
STATE_NO_APP = "none"
|
||||
|
||||
ATTR_FRIENDLY_NAME = "friendly_name"
|
||||
@ -24,24 +25,12 @@ ATTR_STATE = "state"
|
||||
ATTR_OPTIONS = "options"
|
||||
|
||||
|
||||
def get_ids(statemachine):
|
||||
""" Gets the IDs of the different Chromecasts that are being tracked. """
|
||||
return ha.get_grouped_state_cats(statemachine, STATE_CATEGORY_FORMAT, True)
|
||||
|
||||
|
||||
def get_categories(statemachine):
|
||||
""" Gets the categories of the different Chromecasts that are being
|
||||
tracked. """
|
||||
return ha.get_grouped_state_cats(statemachine, STATE_CATEGORY_FORMAT,
|
||||
False)
|
||||
|
||||
|
||||
def turn_off(statemachine, cc_id=None):
|
||||
""" Exits any running app on the specified ChromeCast and shows
|
||||
idle screen. Will quit all ChromeCasts if nothing specified. """
|
||||
|
||||
cats = [STATE_CATEGORY_FORMAT.format(cc_id)] if cc_id \
|
||||
else get_categories(statemachine)
|
||||
else ha.filter_categories(statemachine.categories, DOMAIN)
|
||||
|
||||
for cat in cats:
|
||||
state = statemachine.get_state(cat)
|
||||
@ -55,34 +44,39 @@ def turn_off(statemachine, cc_id=None):
|
||||
|
||||
def setup(bus, statemachine, host):
|
||||
""" Listen for chromecast events. """
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
logger.info("Getting device status")
|
||||
device = pychromecast.get_device_status(host)
|
||||
|
||||
if not device:
|
||||
logger.error("Could not find Chromecast")
|
||||
return False
|
||||
|
||||
category = STATE_CATEGORY_FORMAT.format(util.slugify(
|
||||
device.friendly_name))
|
||||
|
||||
bus.register_service(DOMAIN_CHROMECAST, ha.SERVICE_TURN_OFF,
|
||||
bus.register_service(DOMAIN, ha.SERVICE_TURN_OFF,
|
||||
lambda service:
|
||||
turn_off(statemachine,
|
||||
service.data.get("cc_id", None)))
|
||||
|
||||
bus.register_service(DOMAIN_CHROMECAST, "start_fireplace",
|
||||
bus.register_service(DOMAIN, "start_fireplace",
|
||||
lambda service:
|
||||
pychromecast.play_youtube_video(host, "eyU3bRy2x44"))
|
||||
|
||||
bus.register_service(DOMAIN_CHROMECAST, "start_epic_sax",
|
||||
bus.register_service(DOMAIN, "start_epic_sax",
|
||||
lambda service:
|
||||
pychromecast.play_youtube_video(host, "kxopViU98Xo"))
|
||||
|
||||
bus.register_service(DOMAIN_CHROMECAST, SERVICE_YOUTUBE_VIDEO,
|
||||
bus.register_service(DOMAIN, SERVICE_YOUTUBE_VIDEO,
|
||||
lambda service:
|
||||
pychromecast.play_youtube_video(
|
||||
host, service.data['video']))
|
||||
|
||||
def update_chromecast_state(time): # pylint: disable=unused-argument
|
||||
""" Retrieve state of Chromecast and update statemachine. """
|
||||
logger.info("Updating app status")
|
||||
status = pychromecast.get_app_status(host)
|
||||
|
||||
if status:
|
||||
|
@ -10,29 +10,35 @@ from datetime import datetime, timedelta
|
||||
|
||||
import homeassistant as ha
|
||||
|
||||
from . import light, sun, device, general
|
||||
from . import light, sun, device_tracker, general, group
|
||||
|
||||
|
||||
LIGHT_TRANSITION_TIME = timedelta(minutes=15)
|
||||
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def setup(bus, statemachine):
|
||||
def setup(bus, statemachine, light_group=None):
|
||||
""" Triggers to turn lights on or off based on device precense. """
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
device_state_categories = device.get_categories(statemachine)
|
||||
device_state_categories = ha.filter_categories(statemachine.categories,
|
||||
device_tracker.DOMAIN)
|
||||
|
||||
if len(device_state_categories) == 0:
|
||||
logger.error("LightTrigger:No devices given to track")
|
||||
if not device_state_categories:
|
||||
logger.error("LightTrigger:No devices found to track")
|
||||
|
||||
return False
|
||||
|
||||
light_ids = light.get_ids(statemachine)
|
||||
if not light_group:
|
||||
light_group = light.STATE_GROUP_NAME_ALL_LIGHTS
|
||||
|
||||
if len(light_ids) == 0:
|
||||
logger.error("LightTrigger:No lights found to turn on")
|
||||
# Get the light IDs from the specified group
|
||||
light_ids = ha.filter_categories(
|
||||
group.get_categories(statemachine, light_group), light.DOMAIN, True)
|
||||
|
||||
if not light_ids:
|
||||
logger.error("LightTrigger:No lights found to turn on ")
|
||||
|
||||
return False
|
||||
|
||||
@ -50,7 +56,7 @@ def setup(bus, statemachine):
|
||||
def turn_light_on_before_sunset(light_id):
|
||||
""" Helper function to turn on lights slowly if there
|
||||
are devices home and the light is not on yet. """
|
||||
if (device.is_home(statemachine) and
|
||||
if (device_tracker.is_home(statemachine) and
|
||||
not light.is_on(statemachine, light_id)):
|
||||
|
||||
light.turn_on(bus, light_id, LIGHT_TRANSITION_TIME.seconds)
|
||||
@ -70,8 +76,8 @@ def setup(bus, statemachine):
|
||||
|
||||
# Track every time sun rises so we can schedule a time-based
|
||||
# pre-sun set event
|
||||
ha.track_state_change(bus, sun.STATE_CATEGORY, sun.STATE_BELOW_HORIZON,
|
||||
sun.STATE_ABOVE_HORIZON, handle_sun_rising)
|
||||
ha.track_state_change(bus, sun.STATE_CATEGORY, handle_sun_rising,
|
||||
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON)
|
||||
|
||||
# If the sun is already above horizon
|
||||
# schedule the time-based pre-sun set event
|
||||
@ -85,8 +91,8 @@ def setup(bus, statemachine):
|
||||
light_needed = not (lights_are_on or sun.is_up(statemachine))
|
||||
|
||||
# Specific device came home ?
|
||||
if (category != device.STATE_CATEGORY_ALL_DEVICES and
|
||||
new_state['state'] == device.STATE_HOME):
|
||||
if (category != device_tracker.STATE_CATEGORY_ALL_DEVICES and
|
||||
new_state['state'] == ha.STATE_HOME):
|
||||
|
||||
# These variables are needed for the elif check
|
||||
now = datetime.now()
|
||||
@ -120,8 +126,8 @@ def setup(bus, statemachine):
|
||||
break
|
||||
|
||||
# Did all devices leave the house?
|
||||
elif (category == device.STATE_CATEGORY_ALL_DEVICES and
|
||||
new_state['state'] == device.STATE_NOT_HOME and lights_are_on):
|
||||
elif (category == device_tracker.STATE_CATEGORY_ALL_DEVICES and
|
||||
new_state['state'] == ha.STATE_NOT_HOME and lights_are_on):
|
||||
|
||||
logger.info(
|
||||
"Everyone has left but there are devices on. Turning them off")
|
||||
@ -130,13 +136,12 @@ def setup(bus, statemachine):
|
||||
|
||||
# Track home coming of each seperate device
|
||||
for category in device_state_categories:
|
||||
ha.track_state_change(bus, category,
|
||||
device.STATE_NOT_HOME, device.STATE_HOME,
|
||||
handle_device_state_change)
|
||||
ha.track_state_change(bus, category, handle_device_state_change,
|
||||
ha.STATE_NOT_HOME, ha.STATE_HOME)
|
||||
|
||||
# Track when all devices are gone to shut down lights
|
||||
ha.track_state_change(bus, device.STATE_CATEGORY_ALL_DEVICES,
|
||||
device.STATE_HOME, device.STATE_NOT_HOME,
|
||||
handle_device_state_change)
|
||||
ha.track_state_change(bus, device_tracker.STATE_CATEGORY_ALL_DEVICES,
|
||||
handle_device_state_change, ha.STATE_HOME,
|
||||
ha.STATE_NOT_HOME)
|
||||
|
||||
return True
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.sun
|
||||
homeassistant.components.tracker
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides functionality to keep track of devices.
|
||||
@ -16,23 +16,23 @@ import requests
|
||||
|
||||
import homeassistant as ha
|
||||
import homeassistant.util as util
|
||||
import homeassistant.components.group as group
|
||||
|
||||
import homeassistant.external.pynetgear as pynetgear
|
||||
|
||||
DOMAIN_DEVICE_TRACKER = "device_tracker"
|
||||
DOMAIN = "device_tracker"
|
||||
|
||||
SERVICE_DEVICE_TRACKER_RELOAD = "reload_devices_csv"
|
||||
|
||||
STATE_CATEGORY_ALL_DEVICES = 'devices'
|
||||
STATE_CATEGORY_FORMAT = 'devices.{}'
|
||||
|
||||
STATE_NOT_HOME = 'device_not_home'
|
||||
STATE_HOME = 'device_home'
|
||||
STATE_GROUP_NAME_ALL_DEVICES = 'all_tracked_devices'
|
||||
STATE_CATEGORY_ALL_DEVICES = group.STATE_CATEGORY_FORMAT.format(
|
||||
STATE_GROUP_NAME_ALL_DEVICES)
|
||||
|
||||
STATE_CATEGORY_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
# After how much time do we consider a device not home if
|
||||
# it does not show up on scans
|
||||
TIME_SPAN_FOR_ERROR_IN_SCANNING = timedelta(minutes=1)
|
||||
TIME_SPAN_FOR_ERROR_IN_SCANNING = timedelta(minutes=3)
|
||||
|
||||
# Return cached results if last scan was less then this time ago
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||
@ -41,26 +41,15 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||
KNOWN_DEVICES_FILE = "known_devices.csv"
|
||||
|
||||
|
||||
def get_categories(statemachine):
|
||||
""" Returns the categories of devices that are being tracked in the
|
||||
statemachine. """
|
||||
return ha.get_grouped_state_cats(statemachine, STATE_CATEGORY_FORMAT,
|
||||
False)
|
||||
|
||||
|
||||
def get_ids(statemachine):
|
||||
""" Returns the devices that are being tracked in the statemachine. """
|
||||
return ha.get_grouped_state_cats(statemachine, STATE_CATEGORY_FORMAT, True)
|
||||
|
||||
|
||||
def is_home(statemachine, device_id=None):
|
||||
""" Returns if any or specified device is home. """
|
||||
category = STATE_CATEGORY_FORMAT.format(device_id) if device_id \
|
||||
else STATE_CATEGORY_ALL_DEVICES
|
||||
|
||||
return statemachine.is_state(category, STATE_HOME)
|
||||
return statemachine.is_state(category, ha.STATE_HOME)
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class DeviceTracker(object):
|
||||
""" Class that tracks which devices are home and which are not. """
|
||||
|
||||
@ -88,12 +77,15 @@ class DeviceTracker(object):
|
||||
self.update_devices(
|
||||
device_scanner.scan_devices()))
|
||||
|
||||
bus.register_service(DOMAIN_DEVICE_TRACKER,
|
||||
bus.register_service(DOMAIN,
|
||||
SERVICE_DEVICE_TRACKER_RELOAD,
|
||||
lambda service: self._read_known_devices_file())
|
||||
|
||||
self.update_devices(device_scanner.scan_devices())
|
||||
|
||||
group.setup(bus, statemachine, STATE_GROUP_NAME_ALL_DEVICES,
|
||||
list(self.device_state_categories))
|
||||
|
||||
@property
|
||||
def device_state_categories(self):
|
||||
""" Returns a set containing all categories
|
||||
@ -119,7 +111,7 @@ class DeviceTracker(object):
|
||||
self.known_devices[device]['last_seen'] = now
|
||||
|
||||
self.statemachine.set_state(
|
||||
self.known_devices[device]['category'], STATE_HOME)
|
||||
self.known_devices[device]['category'], ha.STATE_HOME)
|
||||
|
||||
# For all devices we did not find, set state to NH
|
||||
# But only if they have been gone for longer then the error time span
|
||||
@ -131,18 +123,7 @@ class DeviceTracker(object):
|
||||
|
||||
self.statemachine.set_state(
|
||||
self.known_devices[device]['category'],
|
||||
STATE_NOT_HOME)
|
||||
|
||||
# Get the currently used statuses
|
||||
states_of_devices = [self.statemachine.get_state(category)['state']
|
||||
for category in self.device_state_categories]
|
||||
|
||||
# Update the all devices category
|
||||
all_devices_state = (STATE_HOME if STATE_HOME
|
||||
in states_of_devices else STATE_NOT_HOME)
|
||||
|
||||
self.statemachine.set_state(STATE_CATEGORY_ALL_DEVICES,
|
||||
all_devices_state)
|
||||
ha.STATE_NOT_HOME)
|
||||
|
||||
# If we come along any unknown devices we will write them to the
|
||||
# known devices file but only if we did not encounter an invalid
|
||||
@ -407,7 +388,14 @@ class NetgearDeviceScanner(object):
|
||||
self.date_updated = None
|
||||
self.last_results = []
|
||||
|
||||
self.success_init = self._update_info()
|
||||
self.logger.info("Netgear:Logging in")
|
||||
if self._api.login():
|
||||
self.success_init = self._update_info()
|
||||
|
||||
else:
|
||||
self.logger.error("Netgear:Failed to Login")
|
||||
|
||||
self.success_init = False
|
||||
|
||||
def scan_devices(self):
|
||||
""" Scans for new devices and return a
|
||||
@ -446,6 +434,8 @@ class NetgearDeviceScanner(object):
|
||||
|
||||
self.last_results = self._api.get_attached_devices()
|
||||
|
||||
self.date_updated = datetime.now()
|
||||
|
||||
return True
|
||||
|
||||
else:
|
@ -12,7 +12,7 @@ import requests
|
||||
|
||||
import homeassistant.util as util
|
||||
|
||||
DOMAIN_DOWNLOADER = "downloader"
|
||||
DOMAIN = "downloader"
|
||||
|
||||
SERVICE_DOWNLOAD_FILE = "download_file"
|
||||
|
||||
@ -77,7 +77,7 @@ def setup(bus, download_path):
|
||||
logger.exception("FileDownloader:ConnectionError occured for {}".
|
||||
format(service.data['url']))
|
||||
|
||||
bus.register_service(DOMAIN_DOWNLOADER, SERVICE_DOWNLOAD_FILE,
|
||||
bus.register_service(DOMAIN, SERVICE_DOWNLOAD_FILE,
|
||||
download_file)
|
||||
|
||||
return True
|
||||
|
@ -20,7 +20,7 @@ def shutdown_devices(bus, statemachine):
|
||||
def setup(bus, statemachine):
|
||||
""" Setup services related to homeassistant. """
|
||||
|
||||
bus.register_service(ha.DOMAIN_HOMEASSISTANT, SERVICE_SHUTDOWN_DEVICES,
|
||||
bus.register_service(ha.DOMAIN, SERVICE_SHUTDOWN_DEVICES,
|
||||
lambda service: shutdown_devices(bus, statemachine))
|
||||
|
||||
return True
|
||||
|
122
homeassistant/components/group.py
Normal file
122
homeassistant/components/group.py
Normal file
@ -0,0 +1,122 @@
|
||||
"""
|
||||
homeassistant.components.groups
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides functionality to group devices that can be turned on or off.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import homeassistant as ha
|
||||
|
||||
DOMAIN = "group"
|
||||
|
||||
STATE_CATEGORY_FORMAT = DOMAIN + ".{}"
|
||||
|
||||
STATE_ATTR_CATEGORIES = "categories"
|
||||
|
||||
_GROUP_TYPES = {
|
||||
"on_off": (ha.STATE_ON, ha.STATE_OFF),
|
||||
"home_not_home": (ha.STATE_HOME, ha.STATE_NOT_HOME)
|
||||
}
|
||||
|
||||
|
||||
def _get_group_type(state):
|
||||
""" Determine the group type based on the given group type. """
|
||||
for group_type, states in _GROUP_TYPES.items():
|
||||
if state in states:
|
||||
return group_type
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_categories(statemachine, group_name):
|
||||
""" Get the categories that make up this group. """
|
||||
state = statemachine.get_state(STATE_CATEGORY_FORMAT.format(group_name))
|
||||
|
||||
return state['attributes'][STATE_ATTR_CATEGORIES] if state else []
|
||||
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def setup(bus, statemachine, name, categories):
|
||||
""" Sets up a group state that is the combined state of
|
||||
several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Loop over the given categories to:
|
||||
# - determine which group type this is (on_off, device_home)
|
||||
# - if all states exist and have valid states
|
||||
# - retrieve the current state of the group
|
||||
errors = []
|
||||
group_type, group_on, group_off, group_state = None, None, None, None
|
||||
|
||||
for cat in categories:
|
||||
state = statemachine.get_state(cat)
|
||||
|
||||
# Try to determine group type if we didn't yet
|
||||
if not group_type and state:
|
||||
group_type = _get_group_type(state['state'])
|
||||
|
||||
if group_type:
|
||||
group_on, group_off = _GROUP_TYPES[group_type]
|
||||
group_state = group_off
|
||||
|
||||
else:
|
||||
# We did not find a matching group_type
|
||||
errors.append("Found unexpected state '{}'".format(
|
||||
name, state['state']))
|
||||
|
||||
break
|
||||
|
||||
# Check if category exists
|
||||
if not state:
|
||||
errors.append("Category {} does not exist".format(cat))
|
||||
|
||||
# Check if category is valid state
|
||||
elif state['state'] != group_off and state['state'] != group_on:
|
||||
|
||||
errors.append("State of {} is {} (expected: {}, {})".format(
|
||||
cat, state['state'], group_off, group_on))
|
||||
|
||||
# Keep track of the group state to init later on
|
||||
elif group_state == group_off and state['state'] == group_on:
|
||||
group_state = group_on
|
||||
|
||||
if errors:
|
||||
logger.error("Error setting up state group {}: {}".format(
|
||||
name, ", ".join(errors)))
|
||||
|
||||
return False
|
||||
|
||||
group_cat = STATE_CATEGORY_FORMAT.format(name)
|
||||
state_attr = {STATE_ATTR_CATEGORIES: categories}
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def _update_group_state(category, old_state, new_state):
|
||||
""" Updates the group state based on a state change by a tracked
|
||||
category. """
|
||||
|
||||
cur_group_state = statemachine.get_state(group_cat)['state']
|
||||
|
||||
# if cur_group_state = OFF and new_state = ON: set ON
|
||||
# if cur_group_state = ON and new_state = OFF: research
|
||||
# else: ignore
|
||||
|
||||
if cur_group_state == group_off and new_state['state'] == group_on:
|
||||
|
||||
statemachine.set_state(group_cat, group_on, state_attr)
|
||||
|
||||
elif cur_group_state == group_on and new_state['state'] == group_off:
|
||||
|
||||
# Check if any of the other states is still on
|
||||
if not any([statemachine.is_state(cat, group_on)
|
||||
for cat in categories if cat != category]):
|
||||
statemachine.set_state(group_cat, group_off, state_attr)
|
||||
|
||||
for cat in categories:
|
||||
ha.track_state_change(bus, cat, _update_group_state)
|
||||
|
||||
statemachine.set_state(group_cat, group_state, state_attr)
|
||||
|
||||
return True
|
@ -6,7 +6,7 @@ Provides functionality to emulate keyboard presses on host machine.
|
||||
"""
|
||||
import logging
|
||||
|
||||
DOMAIN_KEYBOARD = "keyboard"
|
||||
DOMAIN = "keyboard"
|
||||
|
||||
SERVICE_KEYBOARD_VOLUME_UP = "volume_up"
|
||||
SERVICE_KEYBOARD_VOLUME_DOWN = "volume_down"
|
||||
@ -29,27 +29,27 @@ def setup(bus):
|
||||
keyboard = pykeyboard.PyKeyboard()
|
||||
keyboard.special_key_assignment()
|
||||
|
||||
bus.register_service(DOMAIN_KEYBOARD, SERVICE_KEYBOARD_VOLUME_UP,
|
||||
bus.register_service(DOMAIN, SERVICE_KEYBOARD_VOLUME_UP,
|
||||
lambda service:
|
||||
keyboard.tap_key(keyboard.volume_up_key))
|
||||
|
||||
bus.register_service(DOMAIN_KEYBOARD, SERVICE_KEYBOARD_VOLUME_DOWN,
|
||||
bus.register_service(DOMAIN, SERVICE_KEYBOARD_VOLUME_DOWN,
|
||||
lambda service:
|
||||
keyboard.tap_key(keyboard.volume_down_key))
|
||||
|
||||
bus.register_service(DOMAIN_KEYBOARD, SERVICE_KEYBOARD_VOLUME_MUTE,
|
||||
bus.register_service(DOMAIN, SERVICE_KEYBOARD_VOLUME_MUTE,
|
||||
lambda service:
|
||||
keyboard.tap_key(keyboard.volume_mute_key))
|
||||
|
||||
bus.register_service(DOMAIN_KEYBOARD, SERVICE_KEYBOARD_MEDIA_PLAY_PAUSE,
|
||||
bus.register_service(DOMAIN, SERVICE_KEYBOARD_MEDIA_PLAY_PAUSE,
|
||||
lambda service:
|
||||
keyboard.tap_key(keyboard.media_play_pause_key))
|
||||
|
||||
bus.register_service(DOMAIN_KEYBOARD, SERVICE_KEYBOARD_MEDIA_NEXT_TRACK,
|
||||
bus.register_service(DOMAIN, SERVICE_KEYBOARD_MEDIA_NEXT_TRACK,
|
||||
lambda service:
|
||||
keyboard.tap_key(keyboard.media_next_track_key))
|
||||
|
||||
bus.register_service(DOMAIN_KEYBOARD, SERVICE_KEYBOARD_MEDIA_PREV_TRACK,
|
||||
bus.register_service(DOMAIN, SERVICE_KEYBOARD_MEDIA_PREV_TRACK,
|
||||
lambda service:
|
||||
keyboard.tap_key(keyboard.media_prev_track_key))
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""
|
||||
homeassistant.components.sun
|
||||
homeassistant.components.light
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides functionality to interact with lights.
|
||||
@ -10,14 +10,15 @@ from datetime import datetime, timedelta
|
||||
|
||||
import homeassistant as ha
|
||||
import homeassistant.util as util
|
||||
import homeassistant.components.group as group
|
||||
|
||||
DOMAIN = "light"
|
||||
|
||||
STATE_CATEGORY_ALL_LIGHTS = 'lights'
|
||||
STATE_CATEGORY_FORMAT = "lights.{}"
|
||||
STATE_GROUP_NAME_ALL_LIGHTS = 'all_lights'
|
||||
STATE_CATEGORY_ALL_LIGHTS = group.STATE_CATEGORY_FORMAT.format(
|
||||
STATE_GROUP_NAME_ALL_LIGHTS)
|
||||
|
||||
STATE_ON = "on"
|
||||
STATE_OFF = "off"
|
||||
STATE_CATEGORY_FORMAT = DOMAIN + ".{}"
|
||||
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
|
||||
@ -27,7 +28,7 @@ def is_on(statemachine, light_id=None):
|
||||
category = STATE_CATEGORY_FORMAT.format(light_id) if light_id \
|
||||
else STATE_CATEGORY_ALL_LIGHTS
|
||||
|
||||
return statemachine.is_state(category, STATE_ON)
|
||||
return statemachine.is_state(category, ha.STATE_ON)
|
||||
|
||||
|
||||
def turn_on(bus, light_id=None, transition_seconds=None):
|
||||
@ -56,14 +57,11 @@ def turn_off(bus, light_id=None, transition_seconds=None):
|
||||
bus.call_service(DOMAIN, ha.SERVICE_TURN_OFF, data)
|
||||
|
||||
|
||||
def get_ids(statemachine):
|
||||
""" Get the light IDs that are being tracked in the statemachine. """
|
||||
return ha.get_grouped_state_cats(statemachine, STATE_CATEGORY_FORMAT, True)
|
||||
|
||||
|
||||
def setup(bus, statemachine, light_control):
|
||||
""" Exposes light control via statemachine and services. """
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def update_light_state(time): # pylint: disable=unused-argument
|
||||
""" Track the state of the lights. """
|
||||
try:
|
||||
@ -74,6 +72,8 @@ def setup(bus, statemachine, light_control):
|
||||
should_update = True
|
||||
|
||||
if should_update:
|
||||
logger.info("Updating light status")
|
||||
|
||||
update_light_state.last_updated = datetime.now()
|
||||
|
||||
status = {light_id: light_control.is_light_on(light_id)
|
||||
@ -82,17 +82,21 @@ def setup(bus, statemachine, light_control):
|
||||
for light_id, state in status.items():
|
||||
state_category = STATE_CATEGORY_FORMAT.format(light_id)
|
||||
|
||||
statemachine.set_state(state_category,
|
||||
STATE_ON if state
|
||||
else STATE_OFF)
|
||||
new_state = ha.STATE_ON if state else ha.STATE_OFF
|
||||
|
||||
statemachine.set_state(STATE_CATEGORY_ALL_LIGHTS,
|
||||
STATE_ON if True in status.values()
|
||||
else STATE_OFF)
|
||||
statemachine.set_state(state_category, new_state)
|
||||
|
||||
ha.track_time_change(bus, update_light_state, second=[0, 30])
|
||||
|
||||
def handle_light_event(service):
|
||||
update_light_state(None)
|
||||
|
||||
# Track the all lights state
|
||||
light_cats = [STATE_CATEGORY_FORMAT.format(light_id) for light_id
|
||||
in light_control.light_ids]
|
||||
|
||||
group.setup(bus, statemachine, STATE_GROUP_NAME_ALL_LIGHTS, light_cats)
|
||||
|
||||
def handle_light_service(service):
|
||||
""" Hande a turn light on or off service call. """
|
||||
light_id = service.data.get("light_id", None)
|
||||
transition_seconds = service.data.get("transition_seconds", None)
|
||||
@ -106,12 +110,10 @@ def setup(bus, statemachine, light_control):
|
||||
|
||||
# Listen for light on and light off events
|
||||
bus.register_service(DOMAIN, ha.SERVICE_TURN_ON,
|
||||
handle_light_event)
|
||||
handle_light_service)
|
||||
|
||||
bus.register_service(DOMAIN, ha.SERVICE_TURN_OFF,
|
||||
handle_light_event)
|
||||
|
||||
update_light_state(None)
|
||||
handle_light_service)
|
||||
|
||||
return True
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user