Automatic discovery and setting up of devices
This commit is contained in:
parent
035d994705
commit
ba179bc638
13 changed files with 252 additions and 110 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -4,3 +4,6 @@
|
||||||
[submodule "homeassistant/external/pywemo"]
|
[submodule "homeassistant/external/pywemo"]
|
||||||
path = homeassistant/external/pywemo
|
path = homeassistant/external/pywemo
|
||||||
url = https://github.com/balloob/pywemo.git
|
url = https://github.com/balloob/pywemo.git
|
||||||
|
[submodule "homeassistant/external/netdisco"]
|
||||||
|
path = homeassistant/external/netdisco
|
||||||
|
url = https://github.com/balloob/netdisco.git
|
||||||
|
|
|
@ -51,6 +51,7 @@ class HomeAssistant(object):
|
||||||
self.bus = EventBus(pool)
|
self.bus = EventBus(pool)
|
||||||
self.services = ServiceRegistry(self.bus, pool)
|
self.services = ServiceRegistry(self.bus, pool)
|
||||||
self.states = StateMachine(self.bus)
|
self.states = StateMachine(self.bus)
|
||||||
|
self.components = []
|
||||||
|
|
||||||
self.config_dir = os.path.join(os.getcwd(), 'config')
|
self.config_dir = os.path.join(os.getcwd(), 'config')
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,33 @@ import homeassistant.loader as loader
|
||||||
import homeassistant.components as core_components
|
import homeassistant.components as core_components
|
||||||
|
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_component(hass, domain, config=None):
|
||||||
|
""" Setup a component for Home Assistant. """
|
||||||
|
if config is None:
|
||||||
|
config = defaultdict(dict)
|
||||||
|
|
||||||
|
component = loader.get_component(domain)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if component.setup(hass, config):
|
||||||
|
hass.components.append(component.DOMAIN)
|
||||||
|
|
||||||
|
_LOGGER.info("component %s initialized", domain)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
_LOGGER.error("component %s failed to initialize", domain)
|
||||||
|
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Error during setup of component %s", domain)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches, too-many-statements
|
# pylint: disable=too-many-branches, too-many-statements
|
||||||
def from_config_dict(config, hass=None):
|
def from_config_dict(config, hass=None):
|
||||||
"""
|
"""
|
||||||
|
@ -29,8 +56,6 @@ def from_config_dict(config, hass=None):
|
||||||
if hass is None:
|
if hass is None:
|
||||||
hass = homeassistant.HomeAssistant()
|
hass = homeassistant.HomeAssistant()
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
loader.prepare(hass)
|
loader.prepare(hass)
|
||||||
|
|
||||||
# Make a copy because we are mutating it.
|
# Make a copy because we are mutating it.
|
||||||
|
@ -42,12 +67,12 @@ def from_config_dict(config, hass=None):
|
||||||
if ' ' not in key and key != homeassistant.DOMAIN)
|
if ' ' not in key and key != homeassistant.DOMAIN)
|
||||||
|
|
||||||
if not core_components.setup(hass, config):
|
if not core_components.setup(hass, config):
|
||||||
logger.error(("Home Assistant core failed to initialize. "
|
_LOGGER.error("Home Assistant core failed to initialize. "
|
||||||
"Further initialization aborted."))
|
"Further initialization aborted.")
|
||||||
|
|
||||||
return hass
|
return hass
|
||||||
|
|
||||||
logger.info("Home Assistant core initialized")
|
_LOGGER.info("Home Assistant core initialized")
|
||||||
|
|
||||||
# Setup the components
|
# Setup the components
|
||||||
|
|
||||||
|
@ -57,22 +82,11 @@ def from_config_dict(config, hass=None):
|
||||||
add_worker = True
|
add_worker = True
|
||||||
|
|
||||||
for domain in loader.load_order_components(components):
|
for domain in loader.load_order_components(components):
|
||||||
component = loader.get_component(domain)
|
if setup_component(hass, domain, config):
|
||||||
|
add_worker = add_worker and domain != "group"
|
||||||
|
|
||||||
try:
|
if add_worker:
|
||||||
if component.setup(hass, config):
|
hass.pool.add_worker()
|
||||||
logger.info("component %s initialized", domain)
|
|
||||||
|
|
||||||
add_worker = add_worker and domain != "group"
|
|
||||||
|
|
||||||
if add_worker:
|
|
||||||
hass.pool.add_worker()
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.error("component %s failed to initialize", domain)
|
|
||||||
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
logger.exception("Error during setup of component %s", domain)
|
|
||||||
|
|
||||||
return hass
|
return hass
|
||||||
|
|
||||||
|
@ -112,7 +126,7 @@ def from_config_file(config_path, hass=None, enable_logging=True):
|
||||||
logging.getLogger('').addHandler(err_handler)
|
logging.getLogger('').addHandler(err_handler)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logging.getLogger(__name__).error(
|
_LOGGER.error(
|
||||||
"Unable to setup error log %s (access denied)", err_log_path)
|
"Unable to setup error log %s (access denied)", err_log_path)
|
||||||
|
|
||||||
# Read config
|
# Read config
|
||||||
|
|
|
@ -6,13 +6,19 @@ Provides functionality to interact with Chromecasts.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pychromecast
|
||||||
|
except ImportError:
|
||||||
|
# Ignore, we will raise appropriate error later
|
||||||
|
pass
|
||||||
|
|
||||||
|
from homeassistant.loader import get_component
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.helpers import extract_entity_ids
|
from homeassistant.helpers import extract_entity_ids
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, SERVICE_TURN_OFF, SERVICE_VOLUME_UP,
|
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, SERVICE_TURN_OFF, SERVICE_VOLUME_UP,
|
||||||
SERVICE_VOLUME_DOWN, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY,
|
SERVICE_VOLUME_DOWN, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY,
|
||||||
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK,
|
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK)
|
||||||
CONF_HOSTS)
|
|
||||||
|
|
||||||
|
|
||||||
DOMAIN = 'chromecast'
|
DOMAIN = 'chromecast'
|
||||||
|
@ -105,12 +111,30 @@ def media_prev_track(hass, entity_id=None):
|
||||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK, data)
|
hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK, data)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-locals, too-many-branches
|
def setup_chromecast(casts, host):
|
||||||
|
""" Tries to convert host to Chromecast object and set it up. """
|
||||||
|
try:
|
||||||
|
cast = pychromecast.PyChromecast(host)
|
||||||
|
|
||||||
|
entity_id = util.ensure_unique_string(
|
||||||
|
ENTITY_ID_FORMAT.format(
|
||||||
|
util.slugify(cast.device.friendly_name)),
|
||||||
|
casts.keys())
|
||||||
|
|
||||||
|
casts[entity_id] = cast
|
||||||
|
|
||||||
|
except pychromecast.ChromecastConnectionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
|
# pylint: disable=unused-argument,too-many-locals
|
||||||
""" Listen for chromecast events. """
|
""" Listen for chromecast events. """
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
discovery = get_component('discovery')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# pylint: disable=redefined-outer-name
|
||||||
import pychromecast
|
import pychromecast
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.exception(("Failed to import pychromecast. "
|
logger.exception(("Failed to import pychromecast. "
|
||||||
|
@ -119,33 +143,24 @@ def setup(hass, config):
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if CONF_HOSTS in config[DOMAIN]:
|
casts = {}
|
||||||
hosts = config[DOMAIN][CONF_HOSTS].split(",")
|
|
||||||
|
|
||||||
# If no hosts given, scan for chromecasts
|
# If discovery component not loaded, scan ourselves
|
||||||
else:
|
if discovery.DOMAIN not in hass.components:
|
||||||
logger.info("Scanning for Chromecasts")
|
logger.info("Scanning for Chromecasts")
|
||||||
hosts = pychromecast.discover_chromecasts()
|
hosts = pychromecast.discover_chromecasts()
|
||||||
|
|
||||||
casts = {}
|
for host in hosts:
|
||||||
|
setup_chromecast(casts, host)
|
||||||
|
|
||||||
for host in hosts:
|
# pylint: disable=unused-argument
|
||||||
try:
|
def chromecast_discovered(service, info):
|
||||||
cast = pychromecast.PyChromecast(host)
|
""" Called when a Chromecast has been discovered. """
|
||||||
|
logger.info("New Chromecast discovered: %s", info[0])
|
||||||
|
setup_chromecast(casts, info[0])
|
||||||
|
|
||||||
entity_id = util.ensure_unique_string(
|
discovery.listen(
|
||||||
ENTITY_ID_FORMAT.format(
|
hass, discovery.services.GOOGLE_CAST, chromecast_discovered)
|
||||||
util.slugify(cast.device.friendly_name)),
|
|
||||||
casts.keys())
|
|
||||||
|
|
||||||
casts[entity_id] = cast
|
|
||||||
|
|
||||||
except pychromecast.ChromecastConnectionError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not casts:
|
|
||||||
logger.error("Could not find Chromecasts")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def update_chromecast_state(entity_id, chromecast):
|
def update_chromecast_state(entity_id, chromecast):
|
||||||
""" Retrieve state of Chromecast and update statemachine. """
|
""" Retrieve state of Chromecast and update statemachine. """
|
||||||
|
@ -194,10 +209,11 @@ def setup(hass, config):
|
||||||
|
|
||||||
def update_chromecast_states(time): # pylint: disable=unused-argument
|
def update_chromecast_states(time): # pylint: disable=unused-argument
|
||||||
""" Updates all chromecast states. """
|
""" Updates all chromecast states. """
|
||||||
logger.info("Updating Chromecast status")
|
if casts:
|
||||||
|
logger.info("Updating Chromecast status")
|
||||||
|
|
||||||
for entity_id, cast in casts.items():
|
for entity_id, cast in casts.items():
|
||||||
update_chromecast_state(entity_id, cast)
|
update_chromecast_state(entity_id, cast)
|
||||||
|
|
||||||
def _service_to_entities(service):
|
def _service_to_entities(service):
|
||||||
""" Helper method to get entities from service. """
|
""" Helper method to get entities from service. """
|
||||||
|
|
88
homeassistant/components/discovery.py
Normal file
88
homeassistant/components/discovery.py
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
"""
|
||||||
|
Starts a service to scan in intervals for new devices.
|
||||||
|
|
||||||
|
Will emit EVENT_SERVICE_DISCOVERED whenever a new service has been discovered.
|
||||||
|
|
||||||
|
Knows which components handle certain types, will make sure they are
|
||||||
|
loaded before the EVENT_SERVICE_DISCOVERED is fired.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
# pylint: disable=no-name-in-module, import-error
|
||||||
|
from homeassistant.external.netdisco.netdisco import DiscoveryService
|
||||||
|
import homeassistant.external.netdisco.netdisco.const as services
|
||||||
|
|
||||||
|
from homeassistant import bootstrap
|
||||||
|
from homeassistant.const import EVENT_HOMEASSISTANT_START, ATTR_SERVICE
|
||||||
|
|
||||||
|
DOMAIN = "discovery"
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
|
EVENT_SERVICE_DISCOVERED = "service_discovered"
|
||||||
|
|
||||||
|
ATTR_DISCOVERED = "discovered"
|
||||||
|
|
||||||
|
SCAN_INTERVAL = 300 # seconds
|
||||||
|
|
||||||
|
SERVICE_HANDLERS = {
|
||||||
|
services.BELKIN_WEMO: "switch",
|
||||||
|
services.GOOGLE_CAST: "chromecast",
|
||||||
|
services.PHILIPS_HUE: "light",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def listen(hass, service, callback):
|
||||||
|
"""
|
||||||
|
Setup listener for discovery of specific service.
|
||||||
|
Service can be a string or a list/tuple.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(service, str):
|
||||||
|
service = (service,)
|
||||||
|
|
||||||
|
def discovery_event_listener(event):
|
||||||
|
""" Listens for discovery events. """
|
||||||
|
if event.data[ATTR_SERVICE] in service:
|
||||||
|
callback(event.data[ATTR_SERVICE], event.data[ATTR_DISCOVERED])
|
||||||
|
|
||||||
|
hass.bus.listen(EVENT_SERVICE_DISCOVERED, discovery_event_listener)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
""" Starts a discovery service. """
|
||||||
|
|
||||||
|
# Disable zeroconf logging, it spams
|
||||||
|
logging.getLogger('zeroconf').setLevel(logging.CRITICAL)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
lock = threading.Lock()
|
||||||
|
|
||||||
|
def new_service_listener(service, info):
|
||||||
|
""" Called when a new service is found. """
|
||||||
|
with lock:
|
||||||
|
component = SERVICE_HANDLERS.get(service)
|
||||||
|
|
||||||
|
logger.info("Found new service: %s %s", service, info)
|
||||||
|
|
||||||
|
if component and component not in hass.components:
|
||||||
|
if bootstrap.setup_component(hass, component, config):
|
||||||
|
hass.pool.add_worker()
|
||||||
|
|
||||||
|
hass.bus.fire(EVENT_SERVICE_DISCOVERED, {
|
||||||
|
ATTR_SERVICE: service,
|
||||||
|
ATTR_DISCOVERED: info
|
||||||
|
})
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def start_discovery(event):
|
||||||
|
""" Start discovering. """
|
||||||
|
netdisco = DiscoveryService(SCAN_INTERVAL)
|
||||||
|
netdisco.add_listener(new_service_listener)
|
||||||
|
netdisco.start()
|
||||||
|
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_discovery)
|
||||||
|
|
||||||
|
return True
|
|
@ -132,8 +132,7 @@ class Group(object):
|
||||||
def update_tracked_entity_ids(self, entity_ids):
|
def update_tracked_entity_ids(self, entity_ids):
|
||||||
""" Update the tracked entity IDs. """
|
""" Update the tracked entity IDs. """
|
||||||
self.stop()
|
self.stop()
|
||||||
|
self.tracking = tuple(entity_ids)
|
||||||
self.tracking = list(entity_ids)
|
|
||||||
self.group_on, self.group_off = None, None
|
self.group_on, self.group_off = None, None
|
||||||
|
|
||||||
self.force_update()
|
self.force_update()
|
||||||
|
@ -150,7 +149,8 @@ class Group(object):
|
||||||
|
|
||||||
# If parsing the entitys did not result in a state, set UNKNOWN
|
# If parsing the entitys did not result in a state, set UNKNOWN
|
||||||
if self.state is None:
|
if self.state is None:
|
||||||
self.hass.states.set(self.entity_id, STATE_UNKNOWN)
|
self.hass.states.set(
|
||||||
|
self.entity_id, STATE_UNKNOWN, self.state_attr)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
""" Starts the tracking. """
|
""" Starts the tracking. """
|
||||||
|
@ -182,25 +182,25 @@ class Group(object):
|
||||||
|
|
||||||
# There is already a group state
|
# There is already a group state
|
||||||
cur_gr_state = self.hass.states.get(self.entity_id).state
|
cur_gr_state = self.hass.states.get(self.entity_id).state
|
||||||
|
group_on, group_off = self.group_on, self.group_off
|
||||||
|
|
||||||
# if cur_gr_state = OFF and new_state = ON: set ON
|
# if cur_gr_state = OFF and new_state = ON: set ON
|
||||||
# if cur_gr_state = ON and new_state = OFF: research
|
# if cur_gr_state = ON and new_state = OFF: research
|
||||||
# else: ignore
|
# else: ignore
|
||||||
|
|
||||||
if cur_gr_state == self.group_off and new_state.state == self.group_on:
|
if cur_gr_state == group_off and new_state.state == group_on:
|
||||||
|
|
||||||
self.hass.states.set(
|
self.hass.states.set(
|
||||||
self.entity_id, self.group_on, self.state_attr)
|
self.entity_id, group_on, self.state_attr)
|
||||||
|
|
||||||
elif (cur_gr_state == self.group_on and
|
elif (cur_gr_state == group_on and
|
||||||
new_state.state == self.group_off):
|
new_state.state == group_off):
|
||||||
|
|
||||||
# Check if any of the other states is still on
|
# Check if any of the other states is still on
|
||||||
if not any([self.hass.states.is_state(ent_id, self.group_on)
|
if not any(self.hass.states.is_state(ent_id, group_on)
|
||||||
for ent_id in self.tracking
|
for ent_id in self.tracking if entity_id != ent_id):
|
||||||
if entity_id != ent_id]):
|
|
||||||
self.hass.states.set(
|
self.hass.states.set(
|
||||||
self.entity_id, self.group_off, self.state_attr)
|
self.entity_id, group_off, self.state_attr)
|
||||||
|
|
||||||
|
|
||||||
def setup_group(hass, name, entity_ids, user_defined=True):
|
def setup_group(hass, name, entity_ids, user_defined=True):
|
||||||
|
|
|
@ -6,12 +6,13 @@ Component to interface with various switches that can be controlled remotely.
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from homeassistant.loader import get_component
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
extract_entity_ids, platform_devices_from_config)
|
extract_entity_ids, platform_devices_from_config)
|
||||||
from homeassistant.components import group
|
from homeassistant.components import group, discovery
|
||||||
|
|
||||||
DOMAIN = 'switch'
|
DOMAIN = 'switch'
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
|
@ -27,6 +28,11 @@ ATTR_CURRENT_POWER_MWH = "current_power_mwh"
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
|
|
||||||
|
# Maps discovered services to their platforms
|
||||||
|
DISCOVERY = {
|
||||||
|
discovery.services.BELKIN_WEMO: 'wemo'
|
||||||
|
}
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,21 +64,41 @@ def setup(hass, config):
|
||||||
switches = platform_devices_from_config(
|
switches = platform_devices_from_config(
|
||||||
config, DOMAIN, hass, ENTITY_ID_FORMAT, logger)
|
config, DOMAIN, hass, ENTITY_ID_FORMAT, logger)
|
||||||
|
|
||||||
if not switches:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
|
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
|
||||||
def update_states(now):
|
def update_states(now):
|
||||||
""" Update states of all switches. """
|
""" Update states of all switches. """
|
||||||
|
if switches:
|
||||||
|
logger.info("Updating switch states")
|
||||||
|
|
||||||
logger.info("Updating switch states")
|
for switch in switches.values():
|
||||||
|
switch.update_ha_state(hass)
|
||||||
for switch in switches.values():
|
|
||||||
switch.update_ha_state(hass)
|
|
||||||
|
|
||||||
update_states(None)
|
update_states(None)
|
||||||
|
|
||||||
|
# Track all switches in a group
|
||||||
|
switch_group = group.Group(
|
||||||
|
hass, GROUP_NAME_ALL_SWITCHES, switches.keys(), False)
|
||||||
|
|
||||||
|
def switch_discovered(service, info):
|
||||||
|
""" Called when a switch is discovered. """
|
||||||
|
platform = get_component("{}.{}".format(DOMAIN, DISCOVERY[service]))
|
||||||
|
|
||||||
|
switch = platform.device_discovered(hass, config, info)
|
||||||
|
|
||||||
|
if switch is not None:
|
||||||
|
switch.entity_id = util.ensure_unique_string(
|
||||||
|
ENTITY_ID_FORMAT.format(util.slugify(switch.get_name())),
|
||||||
|
switches.keys())
|
||||||
|
|
||||||
|
switches[switch.entity_id] = switch
|
||||||
|
|
||||||
|
switch.update_ha_state(hass)
|
||||||
|
|
||||||
|
switch_group.update_tracked_entity_ids(switches.keys())
|
||||||
|
|
||||||
|
discovery.listen(hass, discovery.services.BELKIN_WEMO, switch_discovered)
|
||||||
|
|
||||||
def handle_switch_service(service):
|
def handle_switch_service(service):
|
||||||
""" Handles calls to the switch services. """
|
""" Handles calls to the switch services. """
|
||||||
target_switches = [switches[entity_id] for entity_id
|
target_switches = [switches[entity_id] for entity_id
|
||||||
|
@ -90,9 +116,6 @@ def setup(hass, config):
|
||||||
|
|
||||||
switch.update_ha_state(hass)
|
switch.update_ha_state(hass)
|
||||||
|
|
||||||
# Track all switches in a group
|
|
||||||
group.Group(hass, GROUP_NAME_ALL_SWITCHES, switches.keys(), False)
|
|
||||||
|
|
||||||
# Update state every 30 seconds
|
# Update state every 30 seconds
|
||||||
hass.track_time_change(update_states, second=[0, 30])
|
hass.track_time_change(update_states, second=[0, 30])
|
||||||
|
|
||||||
|
|
|
@ -11,16 +11,9 @@ from homeassistant.components.switch import (
|
||||||
def get_devices(hass, config):
|
def get_devices(hass, config):
|
||||||
""" Find and return WeMo switches. """
|
""" Find and return WeMo switches. """
|
||||||
|
|
||||||
try:
|
pywemo, _ = get_pywemo()
|
||||||
# Pylint does not play nice if not every folders has an __init__.py
|
|
||||||
# pylint: disable=no-name-in-module, import-error
|
|
||||||
import homeassistant.external.pywemo.pywemo as pywemo
|
|
||||||
except ImportError:
|
|
||||||
logging.getLogger(__name__).exception((
|
|
||||||
"Failed to import pywemo. "
|
|
||||||
"Did you maybe not run `git submodule init` "
|
|
||||||
"and `git submodule update`?"))
|
|
||||||
|
|
||||||
|
if pywemo is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
logging.getLogger(__name__).info("Scanning for WeMo devices")
|
logging.getLogger(__name__).info("Scanning for WeMo devices")
|
||||||
|
@ -31,6 +24,36 @@ def get_devices(hass, config):
|
||||||
if isinstance(switch, pywemo.Switch)]
|
if isinstance(switch, pywemo.Switch)]
|
||||||
|
|
||||||
|
|
||||||
|
def device_discovered(hass, config, info):
|
||||||
|
""" Called when a device is discovered. """
|
||||||
|
_, discovery = get_pywemo()
|
||||||
|
|
||||||
|
if discovery is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
device = discovery.device_from_description(info)
|
||||||
|
|
||||||
|
return None if device is None else WemoSwitch(device)
|
||||||
|
|
||||||
|
|
||||||
|
def get_pywemo():
|
||||||
|
""" Tries to import PyWemo. """
|
||||||
|
try:
|
||||||
|
# pylint: disable=no-name-in-module, import-error
|
||||||
|
import homeassistant.external.pywemo.pywemo as pywemo
|
||||||
|
import homeassistant.external.pywemo.pywemo.discovery as discovery
|
||||||
|
|
||||||
|
return pywemo, discovery
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
logging.getLogger(__name__).exception((
|
||||||
|
"Failed to import pywemo. "
|
||||||
|
"Did you maybe not run `git submodule init` "
|
||||||
|
"and `git submodule update`?"))
|
||||||
|
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
class WemoSwitch(ToggleDevice):
|
class WemoSwitch(ToggleDevice):
|
||||||
""" represents a WeMo switch within home assistant. """
|
""" represents a WeMo switch within home assistant. """
|
||||||
def __init__(self, wemo):
|
def __init__(self, wemo):
|
||||||
|
|
1
homeassistant/external/netdisco
vendored
Submodule
1
homeassistant/external/netdisco
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 20cb8863fce3ce7d771ae077ce29ecafe98f8960
|
2
homeassistant/external/pywemo
vendored
2
homeassistant/external/pywemo
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 6355e04357cf78b38d293fae7bd418cf9f8d1ca0
|
Subproject commit 7f6c383ded75f1273cbca28e858b8a8c96da66d4
|
|
@ -179,7 +179,7 @@ class Device(object):
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
""" Returns the name of the device if any. """
|
""" Returns the name of the device if any. """
|
||||||
return None
|
return "No Name"
|
||||||
|
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
""" Returns state of the device. """
|
""" Returns state of the device. """
|
||||||
|
|
|
@ -79,12 +79,3 @@ class TestChromecast(unittest.TestCase):
|
||||||
self.assertEqual(service_name, call.service)
|
self.assertEqual(service_name, call.service)
|
||||||
self.assertEqual(self.test_entity,
|
self.assertEqual(self.test_entity,
|
||||||
call.data.get(ATTR_ENTITY_ID))
|
call.data.get(ATTR_ENTITY_ID))
|
||||||
|
|
||||||
def test_setup(self):
|
|
||||||
"""
|
|
||||||
Test Chromecast setup.
|
|
||||||
We do not have access to a Chromecast while testing so test errors.
|
|
||||||
In an ideal world we would create a mock pychromecast API..
|
|
||||||
"""
|
|
||||||
self.assertFalse(chromecast.setup(
|
|
||||||
self.hass, {chromecast.DOMAIN: {CONF_HOSTS: '127.0.0.1'}}))
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ Tests switch component.
|
||||||
# pylint: disable=too-many-public-methods,protected-access
|
# pylint: disable=too-many-public-methods,protected-access
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import homeassistant as ha
|
|
||||||
import homeassistant.loader as loader
|
import homeassistant.loader as loader
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
|
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
|
||||||
import homeassistant.components.switch as switch
|
import homeassistant.components.switch as switch
|
||||||
|
@ -82,29 +81,12 @@ class TestSwitch(unittest.TestCase):
|
||||||
self.assertTrue(switch.is_on(self.hass, self.switch_2.entity_id))
|
self.assertTrue(switch.is_on(self.hass, self.switch_2.entity_id))
|
||||||
self.assertTrue(switch.is_on(self.hass, self.switch_3.entity_id))
|
self.assertTrue(switch.is_on(self.hass, self.switch_3.entity_id))
|
||||||
|
|
||||||
def test_setup(self):
|
def test_setup_two_platforms(self):
|
||||||
# Bogus config
|
""" Test with bad config. """
|
||||||
self.assertFalse(switch.setup(self.hass, {}))
|
|
||||||
|
|
||||||
self.assertFalse(switch.setup(self.hass, {switch.DOMAIN: {}}))
|
|
||||||
|
|
||||||
# Test with non-existing component
|
|
||||||
self.assertFalse(switch.setup(
|
|
||||||
self.hass, {switch.DOMAIN: {CONF_PLATFORM: 'nonexisting'}}
|
|
||||||
))
|
|
||||||
|
|
||||||
# Test if switch component returns 0 switches
|
# Test if switch component returns 0 switches
|
||||||
test_platform = loader.get_component('switch.test')
|
test_platform = loader.get_component('switch.test')
|
||||||
test_platform.init(True)
|
test_platform.init(True)
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
[], test_platform.get_switches(None, None))
|
|
||||||
|
|
||||||
self.assertFalse(switch.setup(
|
|
||||||
self.hass, {switch.DOMAIN: {CONF_PLATFORM: 'test'}}
|
|
||||||
))
|
|
||||||
|
|
||||||
# Test if we can load 2 platforms
|
|
||||||
loader.set_component('switch.test2', test_platform)
|
loader.set_component('switch.test2', test_platform)
|
||||||
test_platform.init(False)
|
test_platform.init(False)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue