Moved stuff away from core. Made component interface more uniform.

This commit is contained in:
Paulus Schoutsen 2014-01-23 22:03:13 -08:00
parent b387f9d9d7
commit 0fc3d359cb
13 changed files with 302 additions and 158 deletions

View file

@ -20,16 +20,9 @@ MATCH_ALL = '*'
DOMAIN = "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"
SERVICE_HOMEASSISTANT_STOP = "stop" SERVICE_HOMEASSISTANT_STOP = "stop"
EVENT_HOMEASSISTANT_START = "homeassistant.start" EVENT_HOMEASSISTANT_START = "homeassistant_start"
EVENT_STATE_CHANGED = "state_changed" EVENT_STATE_CHANGED = "state_changed"
EVENT_TIME_CHANGED = "time_changed" EVENT_TIME_CHANGED = "time_changed"
@ -78,21 +71,6 @@ def _matcher(subject, pattern):
return MATCH_ALL == pattern or subject in pattern return MATCH_ALL == pattern or subject in pattern
def split_entity_id(entity_id):
""" Splits a state entity_id into domain, object_id. """
return entity_id.split(".", 1)
def filter_entity_ids(entity_ids, domain_filter=None, strip_domain=False):
""" Filter a list of entities based on domain. Setting strip_domain
will only return the object_ids. """
return [
split_entity_id(entity_id)[1] if strip_domain else entity_id
for entity_id in entity_ids if
not domain_filter or entity_id.startswith(domain_filter)
]
def track_state_change(bus, entity_id, action, from_state=None, to_state=None): def track_state_change(bus, entity_id, action, from_state=None, to_state=None):
""" Helper method to track specific state changes. """ """ Helper method to track specific state changes. """
from_state = _process_match_param(from_state) from_state = _process_match_param(from_state)

View file

@ -134,7 +134,7 @@ def from_config_file(config_path):
add_status("Downloader", downloader.setup( add_status("Downloader", downloader.setup(
bus, get_opt("downloader", "download_dir"))) bus, get_opt("downloader", "download_dir")))
add_status("General", general.setup(bus, statemachine)) add_status("General", general.setup(bus))
if has_section('browser'): if has_section('browser'):
add_status("Browser", load_module('browser').setup(bus)) add_status("Browser", load_module('browser').setup(bus))

View file

@ -6,46 +6,46 @@ Provides functionality to interact with Chromecasts.
""" """
import logging import logging
from homeassistant.external import pychromecast
import homeassistant as ha import homeassistant as ha
import homeassistant.util as util import homeassistant.util as util
from homeassistant.components import general
DOMAIN = 'chromecast'
DOMAIN = "chromecast" SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
SERVICE_YOUTUBE_VIDEO = "play_youtube_video"
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'
STATE_NO_APP = "none" STATE_NO_APP = 'no_app'
ATTR_FRIENDLY_NAME = "friendly_name" ATTR_FRIENDLY_NAME = 'friendly_name'
ATTR_HOST = "host" ATTR_HOST = 'host'
ATTR_STATE = "state" ATTR_STATE = 'state'
ATTR_OPTIONS = "options" ATTR_OPTIONS = 'options'
def turn_off(statemachine, cc_id=None): def is_on(statemachine, entity_id=None):
""" Exits any running app on the specified ChromeCast and shows """ Returns true if specified ChromeCast entity_id is on.
idle screen. Will quit all ChromeCasts if nothing specified. """ Will check all chromecasts if no entity_id specified. """
entity_ids = [ENTITY_ID_FORMAT.format(cc_id)] if cc_id \ entity_ids = [entity_id] if entity_id \
else ha.filter_entity_ids(statemachine.entity_ids, DOMAIN) else util.filter_entity_ids(statemachine.entity_ids, DOMAIN)
for entity_id in entity_ids: return any(not statemachine.is_state(entity_id, STATE_NO_APP)
state = statemachine.get_state(entity_id) for entity_id in entity_ids)
if (state and
(state.state != STATE_NO_APP or
state.state != pychromecast.APP_ID_HOME)):
pychromecast.quit_app(state.attributes[ATTR_HOST])
def setup(bus, statemachine, host): def setup(bus, statemachine, host):
""" Listen for chromecast events. """ """ Listen for chromecast events. """
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try:
from homeassistant.external import pychromecast
except ImportError:
logger.exception(("Failed to import pychromecast. "
"Did you maybe not cloned the git submodules?"))
return False
logger.info("Getting device status") logger.info("Getting device status")
device = pychromecast.get_device_status(host) device = pychromecast.get_device_status(host)
@ -53,13 +53,30 @@ def setup(bus, statemachine, host):
logger.error("Could not find Chromecast") logger.error("Could not find Chromecast")
return False return False
entity = ENTITY_ID_FORMAT.format(util.slugify( entity = ENTITY_ID_FORMAT.format(util.slugify(device.friendly_name))
device.friendly_name))
bus.register_service(DOMAIN, ha.SERVICE_TURN_OFF, if not bus.has_service(DOMAIN, general.SERVICE_TURN_OFF):
lambda service: def _turn_off_service(service):
turn_off(statemachine, """ Service to exit any running app on the specified ChromeCast and
service.data.get("cc_id", None))) shows idle screen. Will quit all ChromeCasts if nothing specified.
"""
entity_id = service.data.get(general.ATTR_ENTITY_ID)
entity_ids = [entity_id] if entity_id \
else util.filter_entity_ids(statemachine.entity_ids, DOMAIN)
for entity_id in entity_ids:
state = statemachine.get_state(entity_id)
try:
pychromecast.quit_app(state.attributes[ATTR_HOST])
except (AttributeError, KeyError):
# AttributeError: state returned None
# KeyError: ATTR_HOST did not exist
pass
bus.register_service(DOMAIN, general.SERVICE_TURN_OFF,
_turn_off_service)
bus.register_service(DOMAIN, "start_fireplace", bus.register_service(DOMAIN, "start_fireplace",
lambda service: lambda service:
@ -74,7 +91,7 @@ def setup(bus, statemachine, host):
pychromecast.play_youtube_video( pychromecast.play_youtube_video(
host, service.data['video'])) host, service.data['video']))
def update_chromecast_state(time): # pylint: disable=unused-argument def _update_chromecast_state(time): # pylint: disable=unused-argument
""" Retrieve state of Chromecast and update statemachine. """ """ Retrieve state of Chromecast and update statemachine. """
logger.info("Updating app status") logger.info("Updating app status")
status = pychromecast.get_app_status(host) status = pychromecast.get_app_status(host)
@ -92,8 +109,8 @@ def setup(bus, statemachine, host):
else: else:
statemachine.set_state(entity, STATE_NO_APP, {ATTR_HOST: host}) statemachine.set_state(entity, STATE_NO_APP, {ATTR_HOST: host})
ha.track_time_change(bus, update_chromecast_state) ha.track_time_change(bus, _update_chromecast_state)
update_chromecast_state(None) _update_chromecast_state(None)
return True return True

View file

@ -9,6 +9,7 @@ import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
import homeassistant as ha import homeassistant as ha
import homeassistant.util as util
from . import light, sun, device_tracker, general, group from . import light, sun, device_tracker, general, group
@ -22,7 +23,7 @@ def setup(bus, statemachine, light_group=None):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
device_entity_ids = ha.filter_entity_ids(statemachine.entity_ids, device_entity_ids = util.filter_entity_ids(statemachine.entity_ids,
device_tracker.DOMAIN) device_tracker.DOMAIN)
if not device_entity_ids: if not device_entity_ids:
@ -33,7 +34,7 @@ def setup(bus, statemachine, light_group=None):
light_group = light_group or light.GROUP_NAME_ALL_LIGHTS light_group = light_group or light.GROUP_NAME_ALL_LIGHTS
# Get the light IDs from the specified group # Get the light IDs from the specified group
light_ids = ha.filter_entity_ids( light_ids = util.filter_entity_ids(
group.get_entity_ids(statemachine, light_group), light.DOMAIN) group.get_entity_ids(statemachine, light_group), light.DOMAIN)
if not light_ids: if not light_ids:
@ -55,7 +56,7 @@ def setup(bus, statemachine, light_group=None):
def turn_light_on_before_sunset(light_id): def turn_light_on_before_sunset(light_id):
""" Helper function to turn on lights slowly if there """ Helper function to turn on lights slowly if there
are devices home and the light is not on yet. """ are devices home and the light is not on yet. """
if (device_tracker.is_home(statemachine) and if (device_tracker.is_on(statemachine) and
not light.is_on(statemachine, light_id)): not light.is_on(statemachine, light_id)):
light.turn_on(bus, light_id, LIGHT_TRANSITION_TIME.seconds) light.turn_on(bus, light_id, LIGHT_TRANSITION_TIME.seconds)
@ -80,18 +81,18 @@ def setup(bus, statemachine, light_group=None):
# If the sun is already above horizon # If the sun is already above horizon
# schedule the time-based pre-sun set event # schedule the time-based pre-sun set event
if sun.is_up(statemachine): if sun.is_on(statemachine):
handle_sun_rising(None, None, None) handle_sun_rising(None, None, None)
def handle_device_state_change(entity, old_state, new_state): def _handle_device_state_change(entity, old_state, new_state):
""" Function to handle tracked device state changes. """ """ Function to handle tracked device state changes. """
lights_are_on = group.is_on(statemachine, light_group) lights_are_on = group.is_on(statemachine, light_group)
light_needed = not (lights_are_on or sun.is_up(statemachine)) light_needed = not (lights_are_on or sun.is_on(statemachine))
# Specific device came home ? # Specific device came home ?
if (entity != device_tracker.ENTITY_ID_ALL_DEVICES and if (entity != device_tracker.ENTITY_ID_ALL_DEVICES and
new_state.state == ha.STATE_HOME): new_state.state == general.STATE_HOME):
# These variables are needed for the elif check # These variables are needed for the elif check
now = datetime.now() now = datetime.now()
@ -104,6 +105,8 @@ def setup(bus, statemachine, light_group=None):
"Home coming event for {}. Turning lights on". "Home coming event for {}. Turning lights on".
format(entity)) format(entity))
# Turn on lights directly instead of calling group.turn_on
# So we skip fetching the entity ids again.
for light_id in light_ids: for light_id in light_ids:
light.turn_on(bus, light_id) light.turn_on(bus, light_id)
@ -127,21 +130,21 @@ def setup(bus, statemachine, light_group=None):
# Did all devices leave the house? # Did all devices leave the house?
elif (entity == device_tracker.ENTITY_ID_ALL_DEVICES and elif (entity == device_tracker.ENTITY_ID_ALL_DEVICES and
new_state.state == ha.STATE_NOT_HOME and lights_are_on): new_state.state == general.STATE_NOT_HOME and lights_are_on):
logger.info( logger.info(
"Everyone has left but there are devices on. Turning them off") "Everyone has left but there are devices on. Turning them off")
general.shutdown_devices(bus, statemachine) light.turn_off(bus, statemachine)
# Track home coming of each seperate device # Track home coming of each seperate device
for entity in device_entity_ids: for entity in device_entity_ids:
ha.track_state_change(bus, entity, handle_device_state_change, ha.track_state_change(bus, entity, _handle_device_state_change,
ha.STATE_NOT_HOME, ha.STATE_HOME) general.STATE_NOT_HOME, general.STATE_HOME)
# Track when all devices are gone to shut down lights # Track when all devices are gone to shut down lights
ha.track_state_change(bus, device_tracker.ENTITY_ID_ALL_DEVICES, ha.track_state_change(bus, device_tracker.ENTITY_ID_ALL_DEVICES,
handle_device_state_change, ha.STATE_HOME, _handle_device_state_change, general.STATE_HOME,
ha.STATE_NOT_HOME) general.STATE_NOT_HOME)
return True return True

View file

@ -16,9 +16,7 @@ import requests
import homeassistant as ha import homeassistant as ha
import homeassistant.util as util import homeassistant.util as util
import homeassistant.components.group as group from homeassistant.components import general, group
import homeassistant.external.pynetgear as pynetgear
DOMAIN = "device_tracker" DOMAIN = "device_tracker"
@ -41,12 +39,11 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
KNOWN_DEVICES_FILE = "known_devices.csv" KNOWN_DEVICES_FILE = "known_devices.csv"
def is_home(statemachine, device_id=None): def is_on(statemachine, entity_id=None):
""" Returns if any or specified device is home. """ """ Returns if any or specified device is home. """
entity = ENTITY_ID_FORMAT.format(device_id) if device_id \ entity = entity_id or ENTITY_ID_ALL_DEVICES
else ENTITY_ID_ALL_DEVICES
return statemachine.is_state(entity, ha.STATE_HOME) return statemachine.is_state(entity, general.STATE_HOME)
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
@ -100,30 +97,30 @@ class DeviceTracker(object):
now = datetime.now() now = datetime.now()
temp_tracking_devices = [device for device in self.known_devices known_dev = self.known_devices
if self.known_devices[device]['track']]
temp_tracking_devices = [device for device in known_dev
if known_dev[device]['track']]
for device in found_devices: for device in found_devices:
# Are we tracking this device? # Are we tracking this device?
if device in temp_tracking_devices: if device in temp_tracking_devices:
temp_tracking_devices.remove(device) temp_tracking_devices.remove(device)
self.known_devices[device]['last_seen'] = now known_dev[device]['last_seen'] = now
self.statemachine.set_state( self.statemachine.set_state(
self.known_devices[device]['entity_id'], ha.STATE_HOME) known_dev[device]['entity_id'], general.STATE_HOME)
# For all devices we did not find, set state to NH # 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 # But only if they have been gone for longer then the error time span
# Because we do not want to have stuff happening when the device does # Because we do not want to have stuff happening when the device does
# not show up for 1 scan beacuse of reboot etc # not show up for 1 scan beacuse of reboot etc
for device in temp_tracking_devices: for device in temp_tracking_devices:
if (now - self.known_devices[device]['last_seen'] > if (now - known_dev[device]['last_seen'] > self.error_scanning):
self.error_scanning):
self.statemachine.set_state( self.statemachine.set_state(known_dev[device]['entity_id'],
self.known_devices[device]['entity_id'], general.STATE_NOT_HOME)
ha.STATE_NOT_HOME)
# If we come along any unknown devices we will write them to the # 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 # known devices file but only if we did not encounter an invalid
@ -131,7 +128,7 @@ class DeviceTracker(object):
if not self.invalid_known_devices_file: if not self.invalid_known_devices_file:
unknown_devices = [device for device in found_devices unknown_devices = [device for device in found_devices
if device not in self.known_devices] if device not in known_dev]
if unknown_devices: if unknown_devices:
try: try:
@ -151,13 +148,12 @@ class DeviceTracker(object):
for device in unknown_devices: for device in unknown_devices:
# See if the device scanner knows the name # See if the device scanner knows the name
temp_name = \ # else defaults to unknown device
self.device_scanner.get_device_name(device) name = (self.device_scanner.get_device_name(device)
or "unknown_device")
name = temp_name if temp_name else "unknown_device"
writer.writerow((device, name, 0)) writer.writerow((device, name, 0))
self.known_devices[device] = {'name': name, known_dev[device] = {'name': name,
'track': False} 'track': False}
except IOError: except IOError:
@ -380,17 +376,28 @@ class NetgearDeviceScanner(object):
""" This class queries a Netgear wireless router using the SOAP-api. """ """ This class queries a Netgear wireless router using the SOAP-api. """
def __init__(self, host, username, password): def __init__(self, host, username, password):
self._api = pynetgear.Netgear(host, username, password)
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.lock = threading.Lock()
self.date_updated = None self.date_updated = None
self.last_results = [] self.last_results = []
try:
import homeassistant.external.pynetgear as pynetgear
except ImportError:
self.logger.exception(
("Netgear:Failed to import pynetgear. "
"Did you maybe not cloned the git submodules?"))
self.success_init = False
return
self._api = pynetgear.Netgear(host, username, password)
self.lock = threading.Lock()
self.logger.info("Netgear:Logging in") self.logger.info("Netgear:Logging in")
if self._api.login(): if self._api.login():
self.success_init = self._update_info() self.success_init = True
self._update_info()
else: else:
self.logger.error("Netgear:Failed to Login") self.logger.error("Netgear:Failed to Login")
@ -423,6 +430,8 @@ class NetgearDeviceScanner(object):
def _update_info(self): def _update_info(self):
""" Retrieves latest information from the Netgear router. """ Retrieves latest information from the Netgear router.
Returns boolean if scanning successful. """ Returns boolean if scanning successful. """
if not self.success_init:
return
with self.lock: with self.lock:
# if date_updated is None or the date is too old we scan for # if date_updated is None or the date is too old we scan for
@ -436,7 +445,7 @@ class NetgearDeviceScanner(object):
self.date_updated = datetime.now() self.date_updated = datetime.now()
return True return
else: else:
return True return

View file

@ -31,7 +31,7 @@ def setup(bus, download_path):
return False return False
def download_file(service): def _download_file(service):
""" Downloads file specified in the url. """ """ Downloads file specified in the url. """
try: try:
@ -78,6 +78,6 @@ def setup(bus, download_path):
format(service.data['url'])) format(service.data['url']))
bus.register_service(DOMAIN, SERVICE_DOWNLOAD_FILE, bus.register_service(DOMAIN, SERVICE_DOWNLOAD_FILE,
download_file) _download_file)
return True return True

View file

@ -4,23 +4,100 @@ homeassistant.components.general
This component contains a service to shut down all devices. This component contains a service to shut down all devices.
""" """
import importlib
import homeassistant as ha import homeassistant as ha
from . import chromecast, light import homeassistant.util as util
SERVICE_SHUTDOWN_DEVICES = "shutdown_devices" ATTR_ENTITY_ID = 'entity_id'
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"
_LOADED_MOD = {}
def shutdown_devices(bus, statemachine): def _get_module(module):
""" Tries to shutdown all devices that are currently on. """ """ Helper function to load a module. """
chromecast.turn_off(statemachine) try:
light.turn_off(bus) return _LOADED_MOD[module]
except KeyError:
# if module key did not exist in loaded dict
try:
module = _LOADED_MOD[module] = importlib.import_module(
'homeassistant.components.'+module)
return module
except ImportError:
# If module does not exist
return None
def setup(bus, statemachine): def is_on(statemachine, entity_id=None):
""" Setup services related to homeassistant. """ """ Loads up the module to call the turn_on method.
If there is no entity id given we will check all. """
entity_ids = [entity_id] if entity_id else statemachine.entity_ids
bus.register_service(ha.DOMAIN, SERVICE_SHUTDOWN_DEVICES, for entity_id in entity_ids:
lambda service: shutdown_devices(bus, statemachine)) domain = util.split_entity_id(entity_id)[0]
try:
if _get_module(domain).is_on(statemachine, entity_id):
return True
except AttributeError:
# method is_on does not exist within module
pass
return False
def turn_on(bus, entity_id=None):
""" Turns specified entity on if possible. """
# If there is no entity_id we do not know which domain to call.
if not entity_id:
return
domain = util.split_entity_id(entity_id)[0]
try:
bus.call_service(domain, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id})
except ha.ServiceDoesNotExistError:
# turn_on service does not exist
pass
def turn_off(bus, entity_id=None):
""" Turns specified entity off. """
# If there is no entity_id we do not know which domain to call.
if not entity_id:
return
domain = util.split_entity_id(entity_id)[0]
try:
bus.call_service(domain, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id})
except ha.ServiceDoesNotExistError:
# turn_off service does not exist
pass
def setup(bus):
""" Setup general services related to homeassistant. """
bus.register_service(ha.DOMAIN, SERVICE_TURN_OFF,
lambda service:
turn_off(bus, service.data.get(ATTR_ENTITY_ID)))
bus.register_service(ha.DOMAIN, SERVICE_TURN_ON,
lambda service:
turn_on(bus, service.data.get(ATTR_ENTITY_ID)))
return True return True

View file

@ -8,6 +8,7 @@ Provides functionality to group devices that can be turned on or off.
import logging import logging
import homeassistant as ha import homeassistant as ha
from homeassistant.components import general as gen
DOMAIN = "group" DOMAIN = "group"
@ -16,8 +17,8 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}"
STATE_ATTR_ENTITY_IDS = "entity_ids" STATE_ATTR_ENTITY_IDS = "entity_ids"
_GROUP_TYPES = { _GROUP_TYPES = {
"on_off": (ha.STATE_ON, ha.STATE_OFF), "on_off": (gen.STATE_ON, gen.STATE_OFF),
"home_not_home": (ha.STATE_HOME, ha.STATE_NOT_HOME) "home_not_home": (gen.STATE_HOME, gen.STATE_NOT_HOME)
} }
@ -30,9 +31,9 @@ def _get_group_type(state):
return None return None
def is_on(statemachine, group): def is_on(statemachine, entity_id):
""" Returns if the group state is in its ON-state. """ """ Returns if the group state is in its ON-state. """
state = statemachine.get_state(group) state = statemachine.get_state(entity_id)
if state: if state:
group_type = _get_group_type(state.state) group_type = _get_group_type(state.state)
@ -46,17 +47,18 @@ def is_on(statemachine, group):
return False return False
def get_entity_ids(statemachine, group): def get_entity_ids(statemachine, entity_id):
""" Get the entity ids that make up this group. """ """ Get the entity ids that make up this group. """
try: try:
return statemachine.get_state(group).attributes[STATE_ATTR_ENTITY_IDS] return \
statemachine.get_state(entity_id).attributes[STATE_ATTR_ENTITY_IDS]
except (AttributeError, KeyError): except (AttributeError, KeyError):
# AttributeError if state did not exist # AttributeError if state did not exist
# KeyError if key did not exist in attributes # KeyError if key did not exist in attributes
return [] return []
# pylint: disable=too-many-branches # pylint: disable=too-many-branches, too-many-locals
def setup(bus, statemachine, name, entity_ids): def setup(bus, statemachine, name, entity_ids):
""" Sets up a group state that is the combined state of """ Sets up a group state that is the combined state of
several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """ several states. Supports ON/OFF and DEVICE_HOME/DEVICE_NOT_HOME. """
@ -136,6 +138,33 @@ def setup(bus, statemachine, name, entity_ids):
for entity_id in entity_ids: for entity_id in entity_ids:
ha.track_state_change(bus, entity_id, _update_group_state) ha.track_state_change(bus, entity_id, _update_group_state)
# group.setup is called to setup each group. Only the first time will we
# register a turn_on and turn_off method for groups.
if not bus.has_service(DOMAIN, gen.SERVICE_TURN_ON):
def _turn_group_on_service(service):
""" Call general.turn_on for each entity_id from this group. """
for entity_id in get_entity_ids(statemachine,
service.data.get(
gen.ATTR_ENTITY_ID)):
gen.turn_on(bus, entity_id)
bus.register_service(DOMAIN, gen.SERVICE_TURN_ON,
_turn_group_on_service)
if not bus.has_service(DOMAIN, gen.SERVICE_TURN_OFF):
def _turn_group_off_service(service):
""" Call general.turn_off for each entity_id from this group. """
for entity_id in get_entity_ids(statemachine,
service.data.get(
gen.ATTR_ENTITY_ID)):
gen.turn_off(bus, entity_id)
bus.register_service(DOMAIN, gen.SERVICE_TURN_OFF,
_turn_group_off_service)
statemachine.set_state(group_entity_id, group_state, state_attr) statemachine.set_state(group_entity_id, group_state, state_attr)
return True return True

View file

@ -592,6 +592,10 @@ class RequestHandler(BaseHTTPRequestHandler):
self._message("Service {}/{} called.".format(domain, service)) self._message("Service {}/{} called.".format(domain, service))
except ha.ServiceDoesNotExistError:
# If the service does not exist
self._message('Service does not exist', HTTP_BAD_REQUEST)
except KeyError: except KeyError:
# Occurs if domain or service does not exist in data # Occurs if domain or service does not exist in data
self._message("No domain or service received.", HTTP_BAD_REQUEST) self._message("No domain or service received.", HTTP_BAD_REQUEST)

View file

@ -10,7 +10,7 @@ from datetime import datetime, timedelta
import homeassistant as ha import homeassistant as ha
import homeassistant.util as util import homeassistant.util as util
import homeassistant.components.group as group from homeassistant.components import general, group
DOMAIN = "light" DOMAIN = "light"
@ -27,33 +27,35 @@ def is_on(statemachine, entity_id=None):
""" Returns if the lights are on based on the statemachine. """ """ Returns if the lights are on based on the statemachine. """
entity_id = entity_id or ENTITY_ID_ALL_LIGHTS entity_id = entity_id or ENTITY_ID_ALL_LIGHTS
return statemachine.is_state(entity_id, ha.STATE_ON) return statemachine.is_state(entity_id, general.STATE_ON)
# pylint: disable=unused-argument
def turn_on(bus, entity_id=None, transition_seconds=None): def turn_on(bus, entity_id=None, transition_seconds=None):
""" Turns all or specified light on. """ """ Turns all or specified light on. """
data = {} data = {}
if entity_id: if entity_id:
data["light_id"] = ha.split_entity_id(entity_id)[1] data[general.ATTR_ENTITY_ID] = entity_id
if transition_seconds: if transition_seconds:
data["transition_seconds"] = transition_seconds data["transition_seconds"] = transition_seconds
bus.call_service(DOMAIN, ha.SERVICE_TURN_ON, data) bus.call_service(DOMAIN, general.SERVICE_TURN_ON, data)
# pylint: disable=unused-argument
def turn_off(bus, entity_id=None, transition_seconds=None): def turn_off(bus, entity_id=None, transition_seconds=None):
""" Turns all or specified light off. """ """ Turns all or specified light off. """
data = {} data = {}
if entity_id: if entity_id:
data["light_id"] = ha.split_entity_id(entity_id)[1] data[general.ATTR_ENTITY_ID] = entity_id
if transition_seconds: if transition_seconds:
data["transition_seconds"] = transition_seconds data["transition_seconds"] = transition_seconds
bus.call_service(DOMAIN, ha.SERVICE_TURN_OFF, data) bus.call_service(DOMAIN, general.SERVICE_TURN_OFF, data)
def setup(bus, statemachine, light_control): def setup(bus, statemachine, light_control):
@ -61,6 +63,13 @@ def setup(bus, statemachine, light_control):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
entity_ids = {light_id: ENTITY_ID_FORMAT.format(light_id) for light_id
in light_control.light_ids}
if not entity_ids:
logger.error("Light:Found no lights to track")
return
def update_light_state(time): # pylint: disable=unused-argument def update_light_state(time): # pylint: disable=unused-argument
""" Track the state of the lights. """ """ Track the state of the lights. """
try: try:
@ -79,39 +88,36 @@ def setup(bus, statemachine, light_control):
for light_id in light_control.light_ids} for light_id in light_control.light_ids}
for light_id, state in status.items(): for light_id, state in status.items():
entity_id = ENTITY_ID_FORMAT.format(light_id) new_state = general.STATE_ON if state else general.STATE_OFF
new_state = ha.STATE_ON if state else ha.STATE_OFF statemachine.set_state(entity_ids[light_id], new_state)
statemachine.set_state(entity_id, new_state)
ha.track_time_change(bus, update_light_state, second=[0, 30]) ha.track_time_change(bus, update_light_state, second=[0, 30])
update_light_state(None) update_light_state(None)
# Track the all lights state # Track the all lights state
entity_ids = [ENTITY_ID_FORMAT.format(light_id) for light_id group.setup(bus, statemachine, GROUP_NAME_ALL_LIGHTS, entity_ids.values())
in light_control.light_ids]
group.setup(bus, statemachine, GROUP_NAME_ALL_LIGHTS, entity_ids)
def handle_light_service(service): def handle_light_service(service):
""" Hande a turn light on or off service call. """ """ Hande a turn light on or off service call. """
light_id = service.data.get("light_id", None) entity_id = service.data.get(general.ATTR_ENTITY_ID, None)
transition_seconds = service.data.get("transition_seconds", None) transition_seconds = service.data.get("transition_seconds", None)
if service.service == ha.SERVICE_TURN_ON: object_id = util.split_entity_id(entity_id)[1] if entity_id else None
light_control.turn_light_on(light_id, transition_seconds)
if service.service == general.SERVICE_TURN_ON:
light_control.turn_light_on(object_id, transition_seconds)
else: else:
light_control.turn_light_off(light_id, transition_seconds) light_control.turn_light_off(object_id, transition_seconds)
update_light_state(None) update_light_state(None)
# Listen for light on and light off events # Listen for light on and light off events
bus.register_service(DOMAIN, ha.SERVICE_TURN_ON, bus.register_service(DOMAIN, general.SERVICE_TURN_ON,
handle_light_service) handle_light_service)
bus.register_service(DOMAIN, ha.SERVICE_TURN_OFF, bus.register_service(DOMAIN, general.SERVICE_TURN_OFF,
handle_light_service) handle_light_service)
return True return True

View file

@ -10,7 +10,7 @@ from datetime import timedelta
import homeassistant as ha import homeassistant as ha
import homeassistant.util as util import homeassistant.util as util
ENTITY_ID = "weather.sun" ENTITY_ID = "sun.sun"
STATE_ABOVE_HORIZON = "above_horizon" STATE_ABOVE_HORIZON = "above_horizon"
STATE_BELOW_HORIZON = "below_horizon" STATE_BELOW_HORIZON = "below_horizon"
@ -19,9 +19,11 @@ STATE_ATTR_NEXT_RISING = "next_rising"
STATE_ATTR_NEXT_SETTING = "next_setting" STATE_ATTR_NEXT_SETTING = "next_setting"
def is_up(statemachine): def is_on(statemachine, entity_id=None):
""" Returns if the sun is currently up based on the statemachine. """ """ Returns if the sun is currently up based on the statemachine. """
return statemachine.is_state(ENTITY_ID, STATE_ABOVE_HORIZON) entity_id = entity_id or ENTITY_ID
return statemachine.is_state(entity_id, STATE_ABOVE_HORIZON)
def next_setting(statemachine): def next_setting(statemachine):

View file

@ -6,7 +6,7 @@ A module containing drop in replacements for core parts that will interface
with a remote instance of home assistant. with a remote instance of home assistant.
If a connection error occurs while communicating with the API a If a connection error occurs while communicating with the API a
HomeAssistantException will be raised. HomeAssistantError will be raised.
""" """
import threading import threading
@ -44,7 +44,7 @@ def _setup_call_api(host, port, api_password):
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
logging.getLogger(__name__).exception("Error connecting to server") logging.getLogger(__name__).exception("Error connecting to server")
raise ha.HomeAssistantException("Error connecting to server") raise ha.HomeAssistantError("Error connecting to server")
return _call_api return _call_api
@ -85,17 +85,17 @@ class Bus(ha.Bus):
return data['services'] return data['services']
else: else:
raise ha.HomeAssistantException( raise ha.HomeAssistantError(
"Got unexpected result (3): {}.".format(req.text)) "Got unexpected result (3): {}.".format(req.text))
except ValueError: # If req.json() can't parse the json except ValueError: # If req.json() can't parse the json
self.logger.exception("Bus:Got unexpected result") self.logger.exception("Bus:Got unexpected result")
raise ha.HomeAssistantException( raise ha.HomeAssistantError(
"Got unexpected result: {}".format(req.text)) "Got unexpected result: {}".format(req.text))
except KeyError: # If not all expected keys are in the returned JSON except KeyError: # If not all expected keys are in the returned JSON
self.logger.exception("Bus:Got unexpected result (2)") self.logger.exception("Bus:Got unexpected result (2)")
raise ha.HomeAssistantException( raise ha.HomeAssistantError(
"Got unexpected result (2): {}".format(req.text)) "Got unexpected result (2): {}".format(req.text))
@property @property
@ -110,17 +110,17 @@ class Bus(ha.Bus):
return data['event_listeners'] return data['event_listeners']
else: else:
raise ha.HomeAssistantException( raise ha.HomeAssistantError(
"Got unexpected result (3): {}.".format(req.text)) "Got unexpected result (3): {}.".format(req.text))
except ValueError: # If req.json() can't parse the json except ValueError: # If req.json() can't parse the json
self.logger.exception("Bus:Got unexpected result") self.logger.exception("Bus:Got unexpected result")
raise ha.HomeAssistantException( raise ha.HomeAssistantError(
"Got unexpected result: {}".format(req.text)) "Got unexpected result: {}".format(req.text))
except KeyError: # If not all expected keys are in the returned JSON except KeyError: # If not all expected keys are in the returned JSON
self.logger.exception("Bus:Got unexpected result (2)") self.logger.exception("Bus:Got unexpected result (2)")
raise ha.HomeAssistantException( raise ha.HomeAssistantError(
"Got unexpected result (2): {}".format(req.text)) "Got unexpected result (2): {}".format(req.text))
def call_service(self, domain, service, service_data=None): def call_service(self, domain, service, service_data=None):
@ -141,7 +141,11 @@ class Bus(ha.Bus):
req.status_code, req.text) req.status_code, req.text)
self.logger.error("Bus:{}".format(error)) self.logger.error("Bus:{}".format(error))
raise ha.HomeAssistantException(error)
if req.status_code == 400:
raise ha.ServiceDoesNotExistError(error)
else:
raise ha.HomeAssistantError(error)
def register_service(self, domain, service, service_callback): def register_service(self, domain, service, service_callback):
""" Not implemented for remote bus. """ Not implemented for remote bus.
@ -166,7 +170,7 @@ class Bus(ha.Bus):
req.status_code, req.text) req.status_code, req.text)
self.logger.error("Bus:{}".format(error)) self.logger.error("Bus:{}".format(error))
raise ha.HomeAssistantException(error) raise ha.HomeAssistantError(error)
def listen_event(self, event_type, listener): def listen_event(self, event_type, listener):
""" Not implemented for remote bus. """ Not implemented for remote bus.
@ -251,11 +255,11 @@ class StateMachine(ha.StateMachine):
req.status_code, req.text) req.status_code, req.text)
self.logger.error("StateMachine:{}".format(error)) self.logger.error("StateMachine:{}".format(error))
raise ha.HomeAssistantException(error) raise ha.HomeAssistantError(error)
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
self.logger.exception("StateMachine:Error connecting to server") self.logger.exception("StateMachine:Error connecting to server")
raise ha.HomeAssistantException("Error connecting to server") raise ha.HomeAssistantError("Error connecting to server")
finally: finally:
self.lock.release() self.lock.release()
@ -277,19 +281,19 @@ class StateMachine(ha.StateMachine):
return None return None
else: else:
raise ha.HomeAssistantException( raise ha.HomeAssistantError(
"Got unexpected result (3): {}.".format(req.text)) "Got unexpected result (3): {}.".format(req.text))
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
self.logger.exception("StateMachine:Error connecting to server") self.logger.exception("StateMachine:Error connecting to server")
raise ha.HomeAssistantException("Error connecting to server") raise ha.HomeAssistantError("Error connecting to server")
except ValueError: # If req.json() can't parse the json except ValueError: # If req.json() can't parse the json
self.logger.exception("StateMachine:Got unexpected result") self.logger.exception("StateMachine:Got unexpected result")
raise ha.HomeAssistantException( raise ha.HomeAssistantError(
"Got unexpected result: {}".format(req.text)) "Got unexpected result: {}".format(req.text))
except KeyError: # If not all expected keys are in the returned JSON except KeyError: # If not all expected keys are in the returned JSON
self.logger.exception("StateMachine:Got unexpected result (2)") self.logger.exception("StateMachine:Got unexpected result (2)")
raise ha.HomeAssistantException( raise ha.HomeAssistantError(
"Got unexpected result (2): {}".format(req.text)) "Got unexpected result (2): {}".format(req.text))

View file

@ -38,3 +38,18 @@ def str_to_datetime(dt_str):
return datetime.datetime.strptime(dt_str, DATE_STR_FORMAT) return datetime.datetime.strptime(dt_str, DATE_STR_FORMAT)
except ValueError: # If dt_str did not match our format except ValueError: # If dt_str did not match our format
return None return None
def split_entity_id(entity_id):
""" Splits a state entity_id into domain, object_id. """
return entity_id.split(".", 1)
def filter_entity_ids(entity_ids, domain_filter=None, strip_domain=False):
""" Filter a list of entities based on domain. Setting strip_domain
will only return the object_ids. """
return [
split_entity_id(entity_id)[1] if strip_domain else entity_id
for entity_id in entity_ids if
not domain_filter or entity_id.startswith(domain_filter)
]