Moved stuff away from core. Made component interface more uniform.
This commit is contained in:
parent
b387f9d9d7
commit
0fc3d359cb
13 changed files with 302 additions and 158 deletions
|
@ -20,16 +20,9 @@ MATCH_ALL = '*'
|
|||
|
||||
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"
|
||||
|
||||
EVENT_HOMEASSISTANT_START = "homeassistant.start"
|
||||
EVENT_HOMEASSISTANT_START = "homeassistant_start"
|
||||
EVENT_STATE_CHANGED = "state_changed"
|
||||
EVENT_TIME_CHANGED = "time_changed"
|
||||
|
||||
|
@ -78,21 +71,6 @@ def _matcher(subject, 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):
|
||||
""" Helper method to track specific state changes. """
|
||||
from_state = _process_match_param(from_state)
|
||||
|
|
|
@ -134,7 +134,7 @@ def from_config_file(config_path):
|
|||
add_status("Downloader", downloader.setup(
|
||||
bus, get_opt("downloader", "download_dir")))
|
||||
|
||||
add_status("General", general.setup(bus, statemachine))
|
||||
add_status("General", general.setup(bus))
|
||||
|
||||
if has_section('browser'):
|
||||
add_status("Browser", load_module('browser').setup(bus))
|
||||
|
|
|
@ -6,46 +6,46 @@ Provides functionality to interact with Chromecasts.
|
|||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.external import pychromecast
|
||||
|
||||
import homeassistant as ha
|
||||
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 + '.{}'
|
||||
STATE_NO_APP = "none"
|
||||
STATE_NO_APP = 'no_app'
|
||||
|
||||
ATTR_FRIENDLY_NAME = "friendly_name"
|
||||
ATTR_HOST = "host"
|
||||
ATTR_STATE = "state"
|
||||
ATTR_OPTIONS = "options"
|
||||
ATTR_FRIENDLY_NAME = 'friendly_name'
|
||||
ATTR_HOST = 'host'
|
||||
ATTR_STATE = 'state'
|
||||
ATTR_OPTIONS = 'options'
|
||||
|
||||
|
||||
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. """
|
||||
def is_on(statemachine, entity_id=None):
|
||||
""" Returns true if specified ChromeCast entity_id is on.
|
||||
Will check all chromecasts if no entity_id specified. """
|
||||
|
||||
entity_ids = [ENTITY_ID_FORMAT.format(cc_id)] if cc_id \
|
||||
else ha.filter_entity_ids(statemachine.entity_ids, DOMAIN)
|
||||
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)
|
||||
|
||||
if (state and
|
||||
(state.state != STATE_NO_APP or
|
||||
state.state != pychromecast.APP_ID_HOME)):
|
||||
|
||||
pychromecast.quit_app(state.attributes[ATTR_HOST])
|
||||
return any(not statemachine.is_state(entity_id, STATE_NO_APP)
|
||||
for entity_id in entity_ids)
|
||||
|
||||
|
||||
def setup(bus, statemachine, host):
|
||||
""" Listen for chromecast events. """
|
||||
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")
|
||||
device = pychromecast.get_device_status(host)
|
||||
|
||||
|
@ -53,13 +53,30 @@ def setup(bus, statemachine, host):
|
|||
logger.error("Could not find Chromecast")
|
||||
return False
|
||||
|
||||
entity = ENTITY_ID_FORMAT.format(util.slugify(
|
||||
device.friendly_name))
|
||||
entity = ENTITY_ID_FORMAT.format(util.slugify(device.friendly_name))
|
||||
|
||||
bus.register_service(DOMAIN, ha.SERVICE_TURN_OFF,
|
||||
lambda service:
|
||||
turn_off(statemachine,
|
||||
service.data.get("cc_id", None)))
|
||||
if not bus.has_service(DOMAIN, general.SERVICE_TURN_OFF):
|
||||
def _turn_off_service(service):
|
||||
""" Service to exit any running app on the specified ChromeCast and
|
||||
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",
|
||||
lambda service:
|
||||
|
@ -74,7 +91,7 @@ def setup(bus, statemachine, host):
|
|||
pychromecast.play_youtube_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. """
|
||||
logger.info("Updating app status")
|
||||
status = pychromecast.get_app_status(host)
|
||||
|
@ -92,8 +109,8 @@ def setup(bus, statemachine, host):
|
|||
else:
|
||||
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
|
||||
|
|
|
@ -9,6 +9,7 @@ import logging
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import homeassistant as ha
|
||||
import homeassistant.util as util
|
||||
|
||||
from . import light, sun, device_tracker, general, group
|
||||
|
||||
|
@ -22,7 +23,7 @@ def setup(bus, statemachine, light_group=None):
|
|||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
# 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)
|
||||
|
||||
if not light_ids:
|
||||
|
@ -55,7 +56,7 @@ def setup(bus, statemachine, light_group=None):
|
|||
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_tracker.is_home(statemachine) and
|
||||
if (device_tracker.is_on(statemachine) and
|
||||
not light.is_on(statemachine, light_id)):
|
||||
|
||||
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
|
||||
# schedule the time-based pre-sun set event
|
||||
if sun.is_up(statemachine):
|
||||
if sun.is_on(statemachine):
|
||||
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. """
|
||||
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 ?
|
||||
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
|
||||
now = datetime.now()
|
||||
|
@ -104,6 +105,8 @@ def setup(bus, statemachine, light_group=None):
|
|||
"Home coming event for {}. Turning lights on".
|
||||
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:
|
||||
light.turn_on(bus, light_id)
|
||||
|
||||
|
@ -127,21 +130,21 @@ def setup(bus, statemachine, light_group=None):
|
|||
|
||||
# Did all devices leave the house?
|
||||
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(
|
||||
"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
|
||||
for entity in device_entity_ids:
|
||||
ha.track_state_change(bus, entity, handle_device_state_change,
|
||||
ha.STATE_NOT_HOME, ha.STATE_HOME)
|
||||
ha.track_state_change(bus, entity, _handle_device_state_change,
|
||||
general.STATE_NOT_HOME, general.STATE_HOME)
|
||||
|
||||
# Track when all devices are gone to shut down lights
|
||||
ha.track_state_change(bus, device_tracker.ENTITY_ID_ALL_DEVICES,
|
||||
handle_device_state_change, ha.STATE_HOME,
|
||||
ha.STATE_NOT_HOME)
|
||||
_handle_device_state_change, general.STATE_HOME,
|
||||
general.STATE_NOT_HOME)
|
||||
|
||||
return True
|
||||
|
|
|
@ -16,9 +16,7 @@ import requests
|
|||
|
||||
import homeassistant as ha
|
||||
import homeassistant.util as util
|
||||
import homeassistant.components.group as group
|
||||
|
||||
import homeassistant.external.pynetgear as pynetgear
|
||||
from homeassistant.components import general, group
|
||||
|
||||
DOMAIN = "device_tracker"
|
||||
|
||||
|
@ -41,12 +39,11 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
|||
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. """
|
||||
entity = ENTITY_ID_FORMAT.format(device_id) if device_id \
|
||||
else ENTITY_ID_ALL_DEVICES
|
||||
entity = entity_id or 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
|
||||
|
@ -100,30 +97,30 @@ class DeviceTracker(object):
|
|||
|
||||
now = datetime.now()
|
||||
|
||||
temp_tracking_devices = [device for device in self.known_devices
|
||||
if self.known_devices[device]['track']]
|
||||
known_dev = self.known_devices
|
||||
|
||||
temp_tracking_devices = [device for device in known_dev
|
||||
if known_dev[device]['track']]
|
||||
|
||||
for device in found_devices:
|
||||
# Are we tracking this device?
|
||||
if device in temp_tracking_devices:
|
||||
temp_tracking_devices.remove(device)
|
||||
|
||||
self.known_devices[device]['last_seen'] = now
|
||||
known_dev[device]['last_seen'] = now
|
||||
|
||||
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
|
||||
# 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
|
||||
# not show up for 1 scan beacuse of reboot etc
|
||||
for device in temp_tracking_devices:
|
||||
if (now - self.known_devices[device]['last_seen'] >
|
||||
self.error_scanning):
|
||||
if (now - known_dev[device]['last_seen'] > self.error_scanning):
|
||||
|
||||
self.statemachine.set_state(
|
||||
self.known_devices[device]['entity_id'],
|
||||
ha.STATE_NOT_HOME)
|
||||
self.statemachine.set_state(known_dev[device]['entity_id'],
|
||||
general.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
|
||||
|
@ -131,7 +128,7 @@ class DeviceTracker(object):
|
|||
if not self.invalid_known_devices_file:
|
||||
|
||||
unknown_devices = [device for device in found_devices
|
||||
if device not in self.known_devices]
|
||||
if device not in known_dev]
|
||||
|
||||
if unknown_devices:
|
||||
try:
|
||||
|
@ -151,13 +148,12 @@ class DeviceTracker(object):
|
|||
|
||||
for device in unknown_devices:
|
||||
# See if the device scanner knows the name
|
||||
temp_name = \
|
||||
self.device_scanner.get_device_name(device)
|
||||
|
||||
name = temp_name if temp_name else "unknown_device"
|
||||
# else defaults to unknown device
|
||||
name = (self.device_scanner.get_device_name(device)
|
||||
or "unknown_device")
|
||||
|
||||
writer.writerow((device, name, 0))
|
||||
self.known_devices[device] = {'name': name,
|
||||
known_dev[device] = {'name': name,
|
||||
'track': False}
|
||||
|
||||
except IOError:
|
||||
|
@ -380,17 +376,28 @@ class NetgearDeviceScanner(object):
|
|||
""" This class queries a Netgear wireless router using the SOAP-api. """
|
||||
|
||||
def __init__(self, host, username, password):
|
||||
self._api = pynetgear.Netgear(host, username, password)
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.lock = threading.Lock()
|
||||
|
||||
self.date_updated = None
|
||||
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")
|
||||
if self._api.login():
|
||||
self.success_init = self._update_info()
|
||||
self.success_init = True
|
||||
self._update_info()
|
||||
|
||||
else:
|
||||
self.logger.error("Netgear:Failed to Login")
|
||||
|
@ -423,6 +430,8 @@ class NetgearDeviceScanner(object):
|
|||
def _update_info(self):
|
||||
""" Retrieves latest information from the Netgear router.
|
||||
Returns boolean if scanning successful. """
|
||||
if not self.success_init:
|
||||
return
|
||||
|
||||
with self.lock:
|
||||
# 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()
|
||||
|
||||
return True
|
||||
return
|
||||
|
||||
else:
|
||||
return True
|
||||
return
|
||||
|
|
|
@ -31,7 +31,7 @@ def setup(bus, download_path):
|
|||
|
||||
return False
|
||||
|
||||
def download_file(service):
|
||||
def _download_file(service):
|
||||
""" Downloads file specified in the url. """
|
||||
|
||||
try:
|
||||
|
@ -78,6 +78,6 @@ def setup(bus, download_path):
|
|||
format(service.data['url']))
|
||||
|
||||
bus.register_service(DOMAIN, SERVICE_DOWNLOAD_FILE,
|
||||
download_file)
|
||||
_download_file)
|
||||
|
||||
return True
|
||||
|
|
|
@ -4,23 +4,100 @@ homeassistant.components.general
|
|||
|
||||
This component contains a service to shut down all devices.
|
||||
"""
|
||||
import importlib
|
||||
|
||||
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):
|
||||
""" Tries to shutdown all devices that are currently on. """
|
||||
chromecast.turn_off(statemachine)
|
||||
light.turn_off(bus)
|
||||
def _get_module(module):
|
||||
""" Helper function to load a module. """
|
||||
try:
|
||||
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):
|
||||
""" Setup services related to homeassistant. """
|
||||
def is_on(statemachine, entity_id=None):
|
||||
""" 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,
|
||||
lambda service: shutdown_devices(bus, statemachine))
|
||||
for entity_id in entity_ids:
|
||||
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
|
||||
|
|
|
@ -8,6 +8,7 @@ Provides functionality to group devices that can be turned on or off.
|
|||
import logging
|
||||
|
||||
import homeassistant as ha
|
||||
from homeassistant.components import general as gen
|
||||
|
||||
DOMAIN = "group"
|
||||
|
||||
|
@ -16,8 +17,8 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
|||
STATE_ATTR_ENTITY_IDS = "entity_ids"
|
||||
|
||||
_GROUP_TYPES = {
|
||||
"on_off": (ha.STATE_ON, ha.STATE_OFF),
|
||||
"home_not_home": (ha.STATE_HOME, ha.STATE_NOT_HOME)
|
||||
"on_off": (gen.STATE_ON, gen.STATE_OFF),
|
||||
"home_not_home": (gen.STATE_HOME, gen.STATE_NOT_HOME)
|
||||
}
|
||||
|
||||
|
||||
|
@ -30,9 +31,9 @@ def _get_group_type(state):
|
|||
return None
|
||||
|
||||
|
||||
def is_on(statemachine, group):
|
||||
def is_on(statemachine, entity_id):
|
||||
""" Returns if the group state is in its ON-state. """
|
||||
state = statemachine.get_state(group)
|
||||
state = statemachine.get_state(entity_id)
|
||||
|
||||
if state:
|
||||
group_type = _get_group_type(state.state)
|
||||
|
@ -46,17 +47,18 @@ def is_on(statemachine, group):
|
|||
return False
|
||||
|
||||
|
||||
def get_entity_ids(statemachine, group):
|
||||
def get_entity_ids(statemachine, entity_id):
|
||||
""" Get the entity ids that make up this group. """
|
||||
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):
|
||||
# AttributeError if state did not exist
|
||||
# KeyError if key did not exist in attributes
|
||||
return []
|
||||
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-branches, too-many-locals
|
||||
def setup(bus, statemachine, name, entity_ids):
|
||||
""" Sets up a group state that is the combined state of
|
||||
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:
|
||||
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)
|
||||
|
||||
return True
|
||||
|
|
|
@ -592,6 +592,10 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||
|
||||
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:
|
||||
# Occurs if domain or service does not exist in data
|
||||
self._message("No domain or service received.", HTTP_BAD_REQUEST)
|
||||
|
|
|
@ -10,7 +10,7 @@ from datetime import datetime, timedelta
|
|||
|
||||
import homeassistant as ha
|
||||
import homeassistant.util as util
|
||||
import homeassistant.components.group as group
|
||||
from homeassistant.components import general, group
|
||||
|
||||
DOMAIN = "light"
|
||||
|
||||
|
@ -27,33 +27,35 @@ def is_on(statemachine, entity_id=None):
|
|||
""" Returns if the lights are on based on the statemachine. """
|
||||
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):
|
||||
""" Turns all or specified light on. """
|
||||
data = {}
|
||||
|
||||
if entity_id:
|
||||
data["light_id"] = ha.split_entity_id(entity_id)[1]
|
||||
data[general.ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
if 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):
|
||||
""" Turns all or specified light off. """
|
||||
data = {}
|
||||
|
||||
if entity_id:
|
||||
data["light_id"] = ha.split_entity_id(entity_id)[1]
|
||||
data[general.ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
if 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):
|
||||
|
@ -61,6 +63,13 @@ def setup(bus, statemachine, light_control):
|
|||
|
||||
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
|
||||
""" Track the state of the lights. """
|
||||
try:
|
||||
|
@ -79,39 +88,36 @@ def setup(bus, statemachine, light_control):
|
|||
for light_id in light_control.light_ids}
|
||||
|
||||
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_id, new_state)
|
||||
statemachine.set_state(entity_ids[light_id], new_state)
|
||||
|
||||
ha.track_time_change(bus, update_light_state, second=[0, 30])
|
||||
|
||||
update_light_state(None)
|
||||
|
||||
# Track the all lights state
|
||||
entity_ids = [ENTITY_ID_FORMAT.format(light_id) for light_id
|
||||
in light_control.light_ids]
|
||||
|
||||
group.setup(bus, statemachine, GROUP_NAME_ALL_LIGHTS, entity_ids)
|
||||
group.setup(bus, statemachine, GROUP_NAME_ALL_LIGHTS, entity_ids.values())
|
||||
|
||||
def handle_light_service(service):
|
||||
""" 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)
|
||||
|
||||
if service.service == ha.SERVICE_TURN_ON:
|
||||
light_control.turn_light_on(light_id, transition_seconds)
|
||||
object_id = util.split_entity_id(entity_id)[1] if entity_id else None
|
||||
|
||||
if service.service == general.SERVICE_TURN_ON:
|
||||
light_control.turn_light_on(object_id, transition_seconds)
|
||||
else:
|
||||
light_control.turn_light_off(light_id, transition_seconds)
|
||||
light_control.turn_light_off(object_id, transition_seconds)
|
||||
|
||||
update_light_state(None)
|
||||
|
||||
# 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)
|
||||
|
||||
bus.register_service(DOMAIN, ha.SERVICE_TURN_OFF,
|
||||
bus.register_service(DOMAIN, general.SERVICE_TURN_OFF,
|
||||
handle_light_service)
|
||||
|
||||
return True
|
||||
|
|
|
@ -10,7 +10,7 @@ from datetime import timedelta
|
|||
import homeassistant as ha
|
||||
import homeassistant.util as util
|
||||
|
||||
ENTITY_ID = "weather.sun"
|
||||
ENTITY_ID = "sun.sun"
|
||||
|
||||
STATE_ABOVE_HORIZON = "above_horizon"
|
||||
STATE_BELOW_HORIZON = "below_horizon"
|
||||
|
@ -19,9 +19,11 @@ STATE_ATTR_NEXT_RISING = "next_rising"
|
|||
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. """
|
||||
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):
|
||||
|
|
|
@ -6,7 +6,7 @@ A module containing drop in replacements for core parts that will interface
|
|||
with a remote instance of home assistant.
|
||||
|
||||
If a connection error occurs while communicating with the API a
|
||||
HomeAssistantException will be raised.
|
||||
HomeAssistantError will be raised.
|
||||
"""
|
||||
|
||||
import threading
|
||||
|
@ -44,7 +44,7 @@ def _setup_call_api(host, port, api_password):
|
|||
|
||||
except requests.exceptions.ConnectionError:
|
||||
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
|
||||
|
||||
|
@ -85,17 +85,17 @@ class Bus(ha.Bus):
|
|||
return data['services']
|
||||
|
||||
else:
|
||||
raise ha.HomeAssistantException(
|
||||
raise ha.HomeAssistantError(
|
||||
"Got unexpected result (3): {}.".format(req.text))
|
||||
|
||||
except ValueError: # If req.json() can't parse the json
|
||||
self.logger.exception("Bus:Got unexpected result")
|
||||
raise ha.HomeAssistantException(
|
||||
raise ha.HomeAssistantError(
|
||||
"Got unexpected result: {}".format(req.text))
|
||||
|
||||
except KeyError: # If not all expected keys are in the returned JSON
|
||||
self.logger.exception("Bus:Got unexpected result (2)")
|
||||
raise ha.HomeAssistantException(
|
||||
raise ha.HomeAssistantError(
|
||||
"Got unexpected result (2): {}".format(req.text))
|
||||
|
||||
@property
|
||||
|
@ -110,17 +110,17 @@ class Bus(ha.Bus):
|
|||
return data['event_listeners']
|
||||
|
||||
else:
|
||||
raise ha.HomeAssistantException(
|
||||
raise ha.HomeAssistantError(
|
||||
"Got unexpected result (3): {}.".format(req.text))
|
||||
|
||||
except ValueError: # If req.json() can't parse the json
|
||||
self.logger.exception("Bus:Got unexpected result")
|
||||
raise ha.HomeAssistantException(
|
||||
raise ha.HomeAssistantError(
|
||||
"Got unexpected result: {}".format(req.text))
|
||||
|
||||
except KeyError: # If not all expected keys are in the returned JSON
|
||||
self.logger.exception("Bus:Got unexpected result (2)")
|
||||
raise ha.HomeAssistantException(
|
||||
raise ha.HomeAssistantError(
|
||||
"Got unexpected result (2): {}".format(req.text))
|
||||
|
||||
def call_service(self, domain, service, service_data=None):
|
||||
|
@ -141,7 +141,11 @@ class Bus(ha.Bus):
|
|||
req.status_code, req.text)
|
||||
|
||||
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):
|
||||
""" Not implemented for remote bus.
|
||||
|
@ -166,7 +170,7 @@ class Bus(ha.Bus):
|
|||
req.status_code, req.text)
|
||||
|
||||
self.logger.error("Bus:{}".format(error))
|
||||
raise ha.HomeAssistantException(error)
|
||||
raise ha.HomeAssistantError(error)
|
||||
|
||||
def listen_event(self, event_type, listener):
|
||||
""" Not implemented for remote bus.
|
||||
|
@ -251,11 +255,11 @@ class StateMachine(ha.StateMachine):
|
|||
req.status_code, req.text)
|
||||
|
||||
self.logger.error("StateMachine:{}".format(error))
|
||||
raise ha.HomeAssistantException(error)
|
||||
raise ha.HomeAssistantError(error)
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
self.logger.exception("StateMachine:Error connecting to server")
|
||||
raise ha.HomeAssistantException("Error connecting to server")
|
||||
raise ha.HomeAssistantError("Error connecting to server")
|
||||
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
@ -277,19 +281,19 @@ class StateMachine(ha.StateMachine):
|
|||
return None
|
||||
|
||||
else:
|
||||
raise ha.HomeAssistantException(
|
||||
raise ha.HomeAssistantError(
|
||||
"Got unexpected result (3): {}.".format(req.text))
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
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
|
||||
self.logger.exception("StateMachine:Got unexpected result")
|
||||
raise ha.HomeAssistantException(
|
||||
raise ha.HomeAssistantError(
|
||||
"Got unexpected result: {}".format(req.text))
|
||||
|
||||
except KeyError: # If not all expected keys are in the returned JSON
|
||||
self.logger.exception("StateMachine:Got unexpected result (2)")
|
||||
raise ha.HomeAssistantException(
|
||||
raise ha.HomeAssistantError(
|
||||
"Got unexpected result (2): {}".format(req.text))
|
||||
|
|
|
@ -38,3 +38,18 @@ def str_to_datetime(dt_str):
|
|||
return datetime.datetime.strptime(dt_str, DATE_STR_FORMAT)
|
||||
except ValueError: # If dt_str did not match our format
|
||||
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)
|
||||
]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue