1
mirror of https://github.com/home-assistant/core synced 2024-07-12 07:21:24 +02:00

Added state groups and migrated code base to use them.

This commit is contained in:
Paulus Schoutsen 2014-01-04 17:55:05 -08:00
parent 367433acb2
commit 48026c28c1
12 changed files with 296 additions and 160 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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