예제 #1
0
def setup(hass, config):
    """ Sets up the HTTP API and debug interface. """

    if not util.validate_config(config, {DOMAIN: [CONF_API_PASSWORD]},
                                _LOGGER):
        return False

    api_password = config[DOMAIN]['api_password']

    # If no server host is given, accept all incoming requests
    server_host = config[DOMAIN].get(CONF_SERVER_HOST, '0.0.0.0')

    server_port = config[DOMAIN].get(CONF_SERVER_PORT, rem.SERVER_PORT)

    development = config[DOMAIN].get(CONF_DEVELOPMENT, "") == "1"

    server = HomeAssistantHTTPServer((server_host, server_port),
                                     RequestHandler, hass, api_password,
                                     development)

    hass.listen_once_event(
        ha.EVENT_HOMEASSISTANT_START,
        lambda event:
        threading.Thread(target=server.start, daemon=True).start())

    # If no local api set, set one with known information
    if isinstance(hass, rem.HomeAssistant) and hass.local_api is None:
        hass.local_api = \
            rem.API(util.get_local_ip(), api_password, server_port)

    return True
예제 #2
0
def setup(hass, config):
    """ Sets up the HTTP API and debug interface. """

    if not util.validate_config(config, {DOMAIN: [CONF_API_PASSWORD]},
                                _LOGGER):
        return False

    api_password = config[DOMAIN]['api_password']

    # If no server host is given, accept all incoming requests
    server_host = config[DOMAIN].get(CONF_SERVER_HOST, '0.0.0.0')

    server_port = config[DOMAIN].get(CONF_SERVER_PORT, rem.SERVER_PORT)

    development = config[DOMAIN].get(CONF_DEVELOPMENT, "") == "1"

    server = HomeAssistantHTTPServer((server_host, server_port),
                                     RequestHandler, hass, api_password,
                                     development)

    hass.listen_once_event(
        ha.EVENT_HOMEASSISTANT_START,
        lambda event:
        threading.Thread(target=server.start, daemon=True).start())

    hass.listen_once_event(
        ha.EVENT_HOMEASSISTANT_STOP,
        lambda event: server.shutdown())

    # If no local api set, set one with known information
    if isinstance(hass, rem.HomeAssistant) and hass.local_api is None:
        hass.local_api = \
            rem.API(util.get_local_ip(), api_password, server_port)

    return True
예제 #3
0
def setup(hass, config):
    """ Sets up the device tracker. """

    if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, _LOGGER):
        return False

    tracker_type = config[DOMAIN][ha.CONF_TYPE]

    tracker_implementation = get_component(
        'device_tracker.{}'.format(tracker_type))

    if tracker_implementation is None:
        _LOGGER.error("Unknown device_tracker type specified.")

        return False

    device_scanner = tracker_implementation.get_scanner(hass, config)

    if device_scanner is None:
        _LOGGER.error("Failed to initialize device scanner for %s",
                      tracker_type)

        return False

    DeviceTracker(hass, device_scanner)

    return True
예제 #4
0
def setup(hass, config):
    """ Sets up the device tracker. """

    if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, _LOGGER):
        return False

    tracker_type = config[DOMAIN][ha.CONF_TYPE]

    tracker_implementation = get_component(
        'device_tracker.{}'.format(tracker_type))

    if tracker_implementation is None:
        _LOGGER.error("Unknown device_tracker type specified.")

        return False

    device_scanner = tracker_implementation.get_scanner(hass, config)

    if device_scanner is None:
        _LOGGER.error("Failed to initialize device scanner for %s",
                      tracker_type)

        return False

    DeviceTracker(hass, device_scanner)

    return True
예제 #5
0
def setup(hass, config):
    """ Tracks the state of the sun. """
    logger = logging.getLogger(__name__)

    if not util.validate_config(config,
                                {ha.DOMAIN: [ha.CONF_LATITUDE,
                                             ha.CONF_LONGITUDE]},
                                logger):
        return False

    try:
        import ephem
    except ImportError:
        logger.exception("Error while importing dependency ephem.")
        return False

    sun = ephem.Sun()  # pylint: disable=no-member

    latitude = config[ha.DOMAIN][ha.CONF_LATITUDE]
    longitude = config[ha.DOMAIN][ha.CONF_LONGITUDE]

    def update_sun_state(now):    # pylint: disable=unused-argument
        """ Method to update the current state of the sun and
            set time of next setting and rising. """
        observer = ephem.Observer()
        observer.lat = latitude  # pylint: disable=assigning-non-slot
        observer.long = longitude  # pylint: disable=assigning-non-slot

        next_rising_dt = ephem.localtime(observer.next_rising(sun))
        next_setting_dt = ephem.localtime(observer.next_setting(sun))

        if next_rising_dt > next_setting_dt:
            new_state = STATE_ABOVE_HORIZON
            next_change = next_setting_dt

        else:
            new_state = STATE_BELOW_HORIZON
            next_change = next_rising_dt

        logger.info(
            "{}. Next change: {}".format(new_state,
                                         next_change.strftime("%H:%M")))

        state_attributes = {
            STATE_ATTR_NEXT_RISING: util.datetime_to_str(next_rising_dt),
            STATE_ATTR_NEXT_SETTING: util.datetime_to_str(next_setting_dt)
        }

        hass.states.set(ENTITY_ID, new_state, state_attributes)

        # +10 seconds to be sure that the change has occured
        hass.track_point_in_time(update_sun_state,
                                 next_change + timedelta(seconds=10))

    update_sun_state(None)

    return True
예제 #6
0
def get_scanner(hass, config):
    """ Validates config and returns a Tomato scanner. """
    if not util.validate_config(config,
                                {DOMAIN: [ha.CONF_HOST, ha.CONF_USERNAME,
                                          ha.CONF_PASSWORD, CONF_HTTP_ID]},
                                _LOGGER):
        return None

    return TomatoDeviceScanner(config[DOMAIN])
예제 #7
0
def get_scanner(hass, config):
    """ Validates config and returns a Tomato scanner. """
    if not util.validate_config(config, {
            DOMAIN:
        [ha.CONF_HOST, ha.CONF_USERNAME, ha.CONF_PASSWORD, CONF_HTTP_ID]
    }, _LOGGER):
        return None

    return TomatoDeviceScanner(config[DOMAIN])
예제 #8
0
def get_scanner(hass, config):
    """ Validates config and returns a Netgear scanner. """
    if not util.validate_config(
            config,
        {DOMAIN: [ha.CONF_HOST, ha.CONF_USERNAME, ha.CONF_PASSWORD]}, _LOGGER):
        return None

    scanner = NetgearDeviceScanner(config[DOMAIN])

    return scanner if scanner.success_init else None
예제 #9
0
def setup(hass, config):
    """ Tracks the state of the sun. """
    logger = logging.getLogger(__name__)

    if not util.validate_config(
            config, {ha.DOMAIN: [ha.CONF_LATITUDE, ha.CONF_LONGITUDE]},
            logger):
        return False

    try:
        import ephem
    except ImportError:
        logger.exception("Error while importing dependency ephem.")
        return False

    sun = ephem.Sun()  # pylint: disable=no-member

    latitude = config[ha.DOMAIN][ha.CONF_LATITUDE]
    longitude = config[ha.DOMAIN][ha.CONF_LONGITUDE]

    def update_sun_state(now):  # pylint: disable=unused-argument
        """ Method to update the current state of the sun and
            set time of next setting and rising. """
        observer = ephem.Observer()
        observer.lat = latitude  # pylint: disable=assigning-non-slot
        observer.long = longitude  # pylint: disable=assigning-non-slot

        next_rising_dt = ephem.localtime(observer.next_rising(sun))
        next_setting_dt = ephem.localtime(observer.next_setting(sun))

        if next_rising_dt > next_setting_dt:
            new_state = STATE_ABOVE_HORIZON
            next_change = next_setting_dt

        else:
            new_state = STATE_BELOW_HORIZON
            next_change = next_rising_dt

        logger.info("%s. Next change: %s", new_state,
                    next_change.strftime("%H:%M"))

        state_attributes = {
            STATE_ATTR_NEXT_RISING: util.datetime_to_str(next_rising_dt),
            STATE_ATTR_NEXT_SETTING: util.datetime_to_str(next_setting_dt)
        }

        hass.states.set(ENTITY_ID, new_state, state_attributes)

        # +10 seconds to be sure that the change has occured
        hass.track_point_in_time(update_sun_state,
                                 next_change + timedelta(seconds=10))

    update_sun_state(None)

    return True
예제 #10
0
def get_scanner(hass, config):
    """ Validates config and returns a Luci scanner. """
    if not util.validate_config(config,
                                {DOMAIN: [ha.CONF_HOST, ha.CONF_USERNAME,
                                          ha.CONF_PASSWORD]},
                                _LOGGER):
        return None

    scanner = LuciDeviceScanner(config[DOMAIN])

    return scanner if scanner.success_init else None
예제 #11
0
def setup(hass, config):
    """ Sets up the device tracker. """

    logger = logging.getLogger(__name__)

    # We have flexible requirements for device tracker so
    # we cannot use util.validate_config

    conf = config[DOMAIN]

    if ha.CONF_TYPE not in conf:
        logger.error(
            'Missing required configuration item in {}: {}'.format(
                DOMAIN, ha.CONF_TYPE))

        return False

    fields = [ha.CONF_HOST, ha.CONF_USERNAME, ha.CONF_PASSWORD]

    router_type = conf[ha.CONF_TYPE]

    if router_type == 'tomato':
        fields.append(CONF_HTTP_ID)

        scanner = TomatoDeviceScanner

    elif router_type == 'netgear':
        scanner = NetgearDeviceScanner

    elif router_type == 'luci':
        scanner = LuciDeviceScanner

    else:
        logger.error('Found unknown router type {}'.format(router_type))

        return False

    if not util.validate_config(config, {DOMAIN: fields}, logger):
        return False

    device_scanner = scanner(conf)

    if not device_scanner.success_init:
        logger.error(
            "Failed to initialize device scanner for {}".format(router_type))

        return False

    DeviceTracker(hass, device_scanner)

    return True
예제 #12
0
def setup(hass, config):
    """ Track states and offer events for switches. """
    logger = logging.getLogger(__name__)

    if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, logger):
        return False

    switch_type = config[DOMAIN][ha.CONF_TYPE]

    switch_init = get_component('switch.{}'.format(switch_type))

    if switch_init is None:
        logger.error("Error loading switch component %s", switch_type)

        return False

    switches = switch_init.get_switches(hass, config[DOMAIN])

    if len(switches) == 0:
        logger.error("No switches found")
        return False

    # Setup a dict mapping entity IDs to devices
    ent_to_switch = {}

    no_name_count = 1

    for switch in switches:
        name = switch.get_name()

        if name is None:
            name = "Switch #{}".format(no_name_count)
            no_name_count += 1

        entity_id = util.ensure_unique_string(
            ENTITY_ID_FORMAT.format(util.slugify(name)),
            list(ent_to_switch.keys()))

        switch.entity_id = entity_id
        ent_to_switch[entity_id] = switch

    # pylint: disable=unused-argument
    def update_states(time, force_reload=False):
        """ Update states of all switches. """

        # First time this method gets called, force_reload should be True
        if force_reload or \
           datetime.now() - update_states.last_updated > \
           MIN_TIME_BETWEEN_SCANS:

            logger.info("Updating switch states")
            update_states.last_updated = datetime.now()

            for switch in switches:
                switch.update_ha_state(hass)

    update_states(None, True)

    def handle_switch_service(service):
        """ Handles calls to the switch services. """
        devices = [
            ent_to_switch[entity_id]
            for entity_id in extract_entity_ids(hass, service)
            if entity_id in ent_to_switch
        ]

        if not devices:
            devices = switches

        for switch in devices:
            if service.service == SERVICE_TURN_ON:
                switch.turn_on()
            else:
                switch.turn_off()

            switch.update_ha_state(hass)

    # Track all wemos in a group
    group.setup_group(hass, GROUP_NAME_ALL_SWITCHES, ent_to_switch.keys(),
                      False)

    # Update state every 30 seconds
    hass.track_time_change(update_states, second=[0, 30])

    hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service)

    hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service)

    return True
예제 #13
0
def setup(hass, config):
    """ Tracks the state of the sun. """
    logger = logging.getLogger(__name__)

    if not util.validate_config(config,
                                {ha.DOMAIN: [ha.CONF_LATITUDE,
                                             ha.CONF_LONGITUDE]},
                                logger):
        return False

    try:
        import ephem
    except ImportError:
        logger.exception("Error while importing dependency ephem.")
        return False

    sun = ephem.Sun()  # pylint: disable=no-member

    latitude = config[ha.DOMAIN][ha.CONF_LATITUDE]
    longitude = config[ha.DOMAIN][ha.CONF_LONGITUDE]

    # Validate latitude and longitude
    observer = ephem.Observer()

    errors = []

    try:
        observer.lat = latitude  # pylint: disable=assigning-non-slot
    except ValueError:
        errors.append("invalid value for latitude given: {}".format(latitude))

    try:
        observer.long = longitude  # pylint: disable=assigning-non-slot
    except ValueError:
        errors.append("invalid value for latitude given: {}".format(latitude))

    if errors:
        logger.error("Error setting up: %s", ", ".join(errors))
        return False

    def update_sun_state(now):
        """ Method to update the current state of the sun and
            set time of next setting and rising. """
        utc_offset = datetime.utcnow() - datetime.now()
        utc_now = now + utc_offset

        observer = ephem.Observer()
        observer.lat = latitude  # pylint: disable=assigning-non-slot
        observer.long = longitude  # pylint: disable=assigning-non-slot

        next_rising_dt = ephem.localtime(
            observer.next_rising(sun, start=utc_now))
        next_setting_dt = ephem.localtime(
            observer.next_setting(sun, start=utc_now))

        if next_rising_dt > next_setting_dt:
            new_state = STATE_ABOVE_HORIZON
            next_change = next_setting_dt

        else:
            new_state = STATE_BELOW_HORIZON
            next_change = next_rising_dt

        logger.info("%s. Next change: %s",
                    new_state, next_change.strftime("%H:%M"))

        state_attributes = {
            STATE_ATTR_NEXT_RISING: util.datetime_to_str(next_rising_dt),
            STATE_ATTR_NEXT_SETTING: util.datetime_to_str(next_setting_dt)
        }

        hass.states.set(ENTITY_ID, new_state, state_attributes)

        # +1 second so Ephem will report it has set
        hass.track_point_in_time(update_sun_state,
                                 next_change + timedelta(seconds=1))

    update_sun_state(datetime.now())

    return True
예제 #14
0
def setup(hass, config):
    """ Exposes light control via statemachine and services. """

    if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, _LOGGER):
        return False

    # Load built-in profiles and custom profiles
    profile_paths = [os.path.join(os.path.dirname(__file__),
                                  LIGHT_PROFILES_FILE),
                     hass.get_config_path(LIGHT_PROFILES_FILE)]
    profiles = {}

    for profile_path in profile_paths:

        if os.path.isfile(profile_path):
            with open(profile_path) as inp:
                reader = csv.reader(inp)

                # Skip the header
                next(reader, None)

                try:
                    for profile_id, color_x, color_y, brightness in reader:
                        profiles[profile_id] = (float(color_x), float(color_y),
                                                int(brightness))

                except ValueError:
                    # ValueError if not 4 values per row
                    # ValueError if convert to float/int failed
                    _LOGGER.error(
                        "Error parsing light profiles from %s", profile_path)

                    return False

    # Load platform
    light_type = config[DOMAIN][ha.CONF_TYPE]

    light_init = get_component('light.{}'.format(light_type))

    if light_init is None:
        _LOGGER.error("Unknown light type specified: %s", light_type)

        return False

    lights = light_init.get_lights(hass, config[DOMAIN])

    if len(lights) == 0:
        _LOGGER.error("No lights found")
        return False

    ent_to_light = {}

    no_name_count = 1

    for light in lights:
        name = light.get_name()

        if name is None:
            name = "Light #{}".format(no_name_count)
            no_name_count += 1

        entity_id = util.ensure_unique_string(
            ENTITY_ID_FORMAT.format(util.slugify(name)),
            list(ent_to_light.keys()))

        light.entity_id = entity_id
        ent_to_light[entity_id] = light

    # pylint: disable=unused-argument
    def update_lights_state(now):
        """ Update the states of all the lights. """
        for light in lights:
            light.update_ha_state(hass)

    update_lights_state(None)

    # Track all lights in a group
    group.setup_group(
        hass, GROUP_NAME_ALL_LIGHTS, ent_to_light.keys(), False)

    def handle_light_service(service):
        """ Hande a turn light on or off service call. """
        # Get and validate data
        dat = service.data

        # Convert the entity ids to valid light ids
        lights = [ent_to_light[entity_id] for entity_id
                  in extract_entity_ids(hass, service)
                  if entity_id in ent_to_light]

        if not lights:
            lights = list(ent_to_light.values())

        params = {}

        transition = util.convert(dat.get(ATTR_TRANSITION), int)

        if transition is not None:
            params[ATTR_TRANSITION] = transition

        if service.service == SERVICE_TURN_OFF:
            for light in lights:
                # pylint: disable=star-args
                light.turn_off(**params)

        else:
            # Processing extra data for turn light on request

            # We process the profile first so that we get the desired
            # behavior that extra service data attributes overwrite
            # profile values
            profile = profiles.get(dat.get(ATTR_PROFILE))

            if profile:
                # *color, bright = profile
                *params[ATTR_XY_COLOR], params[ATTR_BRIGHTNESS] = profile

            if ATTR_BRIGHTNESS in dat:
                # We pass in the old value as the default parameter if parsing
                # of the new one goes wrong.
                params[ATTR_BRIGHTNESS] = util.convert(
                    dat.get(ATTR_BRIGHTNESS), int, params.get(ATTR_BRIGHTNESS))

            if ATTR_XY_COLOR in dat:
                try:
                    # xy_color should be a list containing 2 floats
                    xycolor = dat.get(ATTR_XY_COLOR)

                    # Without this check, a xycolor with value '99' would work
                    if not isinstance(xycolor, str):
                        params[ATTR_XY_COLOR] = [float(val) for val in xycolor]

                except (TypeError, ValueError):
                    # TypeError if xy_color is not iterable
                    # ValueError if value could not be converted to float
                    pass

            if ATTR_RGB_COLOR in dat:
                try:
                    # rgb_color should be a list containing 3 ints
                    rgb_color = dat.get(ATTR_RGB_COLOR)

                    if len(rgb_color) == 3:
                        params[ATTR_XY_COLOR] = \
                            util.color_RGB_to_xy(int(rgb_color[0]),
                                                 int(rgb_color[1]),
                                                 int(rgb_color[2]))

                except (TypeError, ValueError):
                    # TypeError if rgb_color is not iterable
                    # ValueError if not all values can be converted to int
                    pass

            for light in lights:
                # pylint: disable=star-args
                light.turn_on(**params)

        for light in lights:
            light.update_ha_state(hass, True)

    # Update light state every 30 seconds
    hass.track_time_change(update_lights_state, second=[0, 30])

    # Listen for light on and light off service calls
    hass.services.register(DOMAIN, SERVICE_TURN_ON,
                           handle_light_service)

    hass.services.register(DOMAIN, SERVICE_TURN_OFF,
                           handle_light_service)

    return True
예제 #15
0
def setup(hass, config):
    """ Listens for download events to download files. """

    logger = logging.getLogger(__name__)

    try:
        import requests
    except ImportError:
        logger.exception(("Failed to import requests. "
                          "Did you maybe not execute 'pip install requests'?"))

        return False

    if not util.validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger):
        return False

    download_path = config[DOMAIN][CONF_DOWNLOAD_DIR]

    if not os.path.isdir(download_path):

        logger.error(
            "Download path %s does not exist. File Downloader not active.",
            download_path)

        return False

    def download_file(service):
        """ Starts thread to download file specified in the url. """

        if ATTR_URL not in service.data:
            logger.error("Service called but 'url' parameter not specified.")
            return

        def do_download():
            """ Downloads the file. """
            try:
                url = service.data[ATTR_URL]

                subdir = service.data.get(ATTR_SUBDIR)

                if subdir:
                    subdir = util.sanitize_filename(subdir)

                final_path = None

                req = requests.get(url, stream=True, timeout=10)

                if req.status_code == 200:
                    filename = None

                    if 'content-disposition' in req.headers:
                        match = re.findall(r"filename=(\S+)",
                                           req.headers['content-disposition'])

                        if len(match) > 0:
                            filename = match[0].strip("'\" ")

                    if not filename:
                        filename = os.path.basename(
                            url).strip()

                    if not filename:
                        filename = "ha_download"

                    # Remove stuff to ruin paths
                    filename = util.sanitize_filename(filename)

                    # Do we want to download to subdir, create if needed
                    if subdir:
                        subdir_path = os.path.join(download_path, subdir)

                        # Ensure subdir exist
                        if not os.path.isdir(subdir_path):
                            os.makedirs(subdir_path)

                        final_path = os.path.join(subdir_path, filename)

                    else:
                        final_path = os.path.join(download_path, filename)

                    path, ext = os.path.splitext(final_path)

                    # If file exist append a number.
                    # We test filename, filename_2..
                    tries = 1
                    final_path = path + ext
                    while os.path.isfile(final_path):
                        tries += 1

                        final_path = "{}_{}.{}".format(path, tries, ext)

                    logger.info("%s -> %s", url, final_path)

                    with open(final_path, 'wb') as fil:
                        for chunk in req.iter_content(1024):
                            fil.write(chunk)

                    logger.info("Downloading of %s done", url)

            except requests.exceptions.ConnectionError:
                logger.exception("ConnectionError occured for %s", url)

                # Remove file if we started downloading but failed
                if final_path and os.path.isfile(final_path):
                    os.remove(final_path)

        threading.Thread(target=do_download).start()

    hass.services.register(DOMAIN, SERVICE_DOWNLOAD_FILE,
                           download_file)

    return True
예제 #16
0
def setup(hass, config):
    """ Exposes light control via statemachine and services. """

    if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, _LOGGER):
        return False

    light_type = config[DOMAIN][ha.CONF_TYPE]

    light_init = get_component('light.{}'.format(light_type))

    if light_init is None:
        _LOGGER.error("Unknown light type specified: %s", light_type)

        return False

    lights = light_init.get_lights(hass, config[DOMAIN])

    if len(lights) == 0:
        _LOGGER.error("No lights found")
        return False

    ent_to_light = {}

    no_name_count = 1

    for light in lights:
        name = light.get_name()

        if name is None:
            name = "Light #{}".format(no_name_count)
            no_name_count += 1

        entity_id = util.ensure_unique_string(
            ENTITY_ID_FORMAT.format(util.slugify(name)),
            list(ent_to_light.keys()))

        light.entity_id = entity_id
        ent_to_light[entity_id] = light

    # Load built-in profiles and custom profiles
    profile_paths = [
        os.path.join(os.path.dirname(__file__), LIGHT_PROFILES_FILE),
        hass.get_config_path(LIGHT_PROFILES_FILE)
    ]
    profiles = {}

    for profile_path in profile_paths:

        if os.path.isfile(profile_path):
            with open(profile_path) as inp:
                reader = csv.reader(inp)

                # Skip the header
                next(reader, None)

                try:
                    for profile_id, color_x, color_y, brightness in reader:
                        profiles[profile_id] = (float(color_x), float(color_y),
                                                int(brightness))

                except ValueError:
                    # ValueError if not 4 values per row
                    # ValueError if convert to float/int failed
                    _LOGGER.error("Error parsing light profiles from %s",
                                  profile_path)

                    return False

    # pylint: disable=unused-argument
    def update_lights_state(now):
        """ Update the states of all the lights. """
        for light in lights:
            light.update_ha_state(hass)

    update_lights_state(None)

    # Track all lights in a group
    group.setup_group(hass, GROUP_NAME_ALL_LIGHTS, ent_to_light.keys(), False)

    def handle_light_service(service):
        """ Hande a turn light on or off service call. """
        # Get and validate data
        dat = service.data

        # Convert the entity ids to valid light ids
        lights = [
            ent_to_light[entity_id]
            for entity_id in extract_entity_ids(hass, service)
            if entity_id in ent_to_light
        ]

        if not lights:
            lights = list(ent_to_light.values())

        transition = util.convert(dat.get(ATTR_TRANSITION), int)

        if service.service == SERVICE_TURN_OFF:
            for light in lights:
                light.turn_off(transition=transition)

        else:
            # Processing extra data for turn light on request

            # We process the profile first so that we get the desired
            # behavior that extra service data attributes overwrite
            # profile values
            profile = profiles.get(dat.get(ATTR_PROFILE))

            if profile:
                *color, bright = profile
            else:
                color, bright = None, None

            if ATTR_BRIGHTNESS in dat:
                bright = util.convert(dat.get(ATTR_BRIGHTNESS), int)

            if ATTR_XY_COLOR in dat:
                try:
                    # xy_color should be a list containing 2 floats
                    xy_color = dat.get(ATTR_XY_COLOR)

                    if len(xy_color) == 2:
                        color = [float(val) for val in xy_color]

                except (TypeError, ValueError):
                    # TypeError if xy_color is not iterable
                    # ValueError if value could not be converted to float
                    pass

            if ATTR_RGB_COLOR in dat:
                try:
                    # rgb_color should be a list containing 3 ints
                    rgb_color = dat.get(ATTR_RGB_COLOR)

                    if len(rgb_color) == 3:
                        color = util.color_RGB_to_xy(int(rgb_color[0]),
                                                     int(rgb_color[1]),
                                                     int(rgb_color[2]))

                except (TypeError, ValueError):
                    # TypeError if rgb_color is not iterable
                    # ValueError if not all values can be converted to int
                    pass

            for light in lights:
                light.turn_on(transition=transition,
                              brightness=bright,
                              xy_color=color)

        for light in lights:
            light.update_ha_state(hass, True)

    # Update light state every 30 seconds
    hass.track_time_change(update_lights_state, second=[0, 30])

    # Listen for light on and light off service calls
    hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_light_service)

    hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_light_service)

    return True
예제 #17
0
def setup(hass, config):
    """ Exposes light control via statemachine and services. """

    logger = logging.getLogger(__name__)

    if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, logger):
        return False

    light_type = config[DOMAIN][ha.CONF_TYPE]

    if light_type == 'hue':
        light_init = HueLightControl

    else:
        logger.error("Found unknown light type: {}".format(light_type))

        return False

    light_control = light_init(hass, config[DOMAIN])

    ent_to_light = {}
    light_to_ent = {}

    def _update_light_state(light_id, light_state):
        """ Update statemachine based on the LightState passed in. """
        name = light_control.get_name(light_id) or "Unknown Light"

        try:
            entity_id = light_to_ent[light_id]
        except KeyError:
            # We have not seen this light before, set it up

            # Create entity id
            logger.info("Found new light {}".format(name))

            entity_id = util.ensure_unique_string(
                ENTITY_ID_FORMAT.format(util.slugify(name)),
                list(ent_to_light.keys()))

            ent_to_light[entity_id] = light_id
            light_to_ent[light_id] = entity_id

        state_attr = {ATTR_FRIENDLY_NAME: name}

        if light_state.on:
            state = STATE_ON

            if light_state.brightness:
                state_attr[ATTR_BRIGHTNESS] = light_state.brightness

            if light_state.color:
                state_attr[ATTR_XY_COLOR] = light_state.color

        else:
            state = STATE_OFF

        hass.states.set(entity_id, state, state_attr)

    def update_light_state(light_id):
        """ Update the state of specified light. """
        _update_light_state(light_id, light_control.get(light_id))

    # pylint: disable=unused-argument
    def update_lights_state(time, force_reload=False):
        """ Update the state of all the lights. """

        # First time this method gets called, force_reload should be True
        if force_reload or \
           datetime.now() - update_lights_state.last_updated > \
           MIN_TIME_BETWEEN_SCANS:

            logger.info("Updating light status")
            update_lights_state.last_updated = datetime.now()

            for light_id, light_state in light_control.gets().items():
                _update_light_state(light_id, light_state)

    # Update light state and discover lights for tracking the group
    update_lights_state(None, True)

    if len(ent_to_light) == 0:
        logger.error("No lights found")
        return False

    # Track all lights in a group
    group.setup_group(
        hass, GROUP_NAME_ALL_LIGHTS, light_to_ent.values(), False)

    # Load built-in profiles and custom profiles
    profile_paths = [os.path.join(os.path.dirname(__file__),
                                  LIGHT_PROFILES_FILE),
                     hass.get_config_path(LIGHT_PROFILES_FILE)]
    profiles = {}

    for profile_path in profile_paths:

        if os.path.isfile(profile_path):
            with open(profile_path) as inp:
                reader = csv.reader(inp)

                # Skip the header
                next(reader, None)

                try:
                    for profile_id, color_x, color_y, brightness in reader:
                        profiles[profile_id] = (float(color_x), float(color_y),
                                                int(brightness))

                except ValueError:
                    # ValueError if not 4 values per row
                    # ValueError if convert to float/int failed
                    logger.error(
                        "Error parsing light profiles from {}".format(
                            profile_path))

                    return False

    def handle_light_service(service):
        """ Hande a turn light on or off service call. """
        # Get and validate data
        dat = service.data

        # Convert the entity ids to valid light ids
        light_ids = [ent_to_light[entity_id] for entity_id
                     in extract_entity_ids(hass, service)
                     if entity_id in ent_to_light]

        if not light_ids:
            light_ids = list(ent_to_light.values())

        transition = util.convert(dat.get(ATTR_TRANSITION), int)

        if service.service == SERVICE_TURN_OFF:
            light_control.turn_light_off(light_ids, transition)

        else:
            # Processing extra data for turn light on request

            # We process the profile first so that we get the desired
            # behavior that extra service data attributes overwrite
            # profile values
            profile = profiles.get(dat.get(ATTR_PROFILE))

            if profile:
                *color, bright = profile
            else:
                color, bright = None, None

            if ATTR_BRIGHTNESS in dat:
                bright = util.convert(dat.get(ATTR_BRIGHTNESS), int)

            if ATTR_XY_COLOR in dat:
                try:
                    # xy_color should be a list containing 2 floats
                    xy_color = dat.get(ATTR_XY_COLOR)

                    if len(xy_color) == 2:
                        color = [float(val) for val in xy_color]

                except (TypeError, ValueError):
                    # TypeError if xy_color is not iterable
                    # ValueError if value could not be converted to float
                    pass

            if ATTR_RGB_COLOR in dat:
                try:
                    # rgb_color should be a list containing 3 ints
                    rgb_color = dat.get(ATTR_RGB_COLOR)

                    if len(rgb_color) == 3:
                        color = util.color_RGB_to_xy(int(rgb_color[0]),
                                                     int(rgb_color[1]),
                                                     int(rgb_color[2]))

                except (TypeError, ValueError):
                    # TypeError if rgb_color is not iterable
                    # ValueError if not all values can be converted to int
                    pass

            light_control.turn_light_on(light_ids, transition, bright, color)

        # Update state of lights touched. If there was only 1 light selected
        # then just update that light else update all
        if len(light_ids) == 1:
            update_light_state(light_ids[0])
        else:
            update_lights_state(None, True)

    # Update light state every 30 seconds
    hass.track_time_change(update_lights_state, second=[0, 30])

    # Listen for light on and light off service calls
    hass.services.register(DOMAIN, SERVICE_TURN_ON,
                           handle_light_service)

    hass.services.register(DOMAIN, SERVICE_TURN_OFF,
                           handle_light_service)

    return True
예제 #18
0
def setup(hass, config):
    """ Track states and offer events for switches. """
    logger = logging.getLogger(__name__)

    if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, logger):
        return False

    switch_type = config[DOMAIN][ha.CONF_TYPE]

    if switch_type == 'wemo':
        switch_init = get_wemo_switches

    else:
        logger.error("Unknown switch type specified: %s", switch_type)

        return False

    switches = switch_init(config[DOMAIN])

    if len(switches) == 0:
        logger.error("No switches found")
        return False

    # Setup a dict mapping entity IDs to devices
    ent_to_switch = {}

    no_name_count = 1

    for switch in switches:
        name = switch.get_name()

        if name is None:
            name = "Switch #{}".format(no_name_count)
            no_name_count += 1

        entity_id = util.ensure_unique_string(
            ENTITY_ID_FORMAT.format(util.slugify(name)),
            list(ent_to_switch.keys()))

        switch.entity_id = entity_id
        ent_to_switch[entity_id] = switch

    # pylint: disable=unused-argument
    def update_states(time, force_reload=False):
        """ Update states of all switches. """

        # First time this method gets called, force_reload should be True
        if force_reload or \
           datetime.now() - update_states.last_updated > \
           MIN_TIME_BETWEEN_SCANS:

            logger.info("Updating switch states")
            update_states.last_updated = datetime.now()

            for switch in switches:
                switch.update_ha_state(hass)

    update_states(None, True)

    def handle_switch_service(service):
        """ Handles calls to the switch services. """
        devices = [ent_to_switch[entity_id] for entity_id
                   in extract_entity_ids(hass, service)
                   if entity_id in ent_to_switch]

        if not devices:
            devices = switches

        for switch in devices:
            if service.service == SERVICE_TURN_ON:
                switch.turn_on()
            else:
                switch.turn_off()

            switch.update_ha_state(hass)

    # Track all wemos in a group
    group.setup_group(hass, GROUP_NAME_ALL_SWITCHES,
                      ent_to_switch.keys(), False)

    # Update state every 30 seconds
    hass.track_time_change(update_states, second=[0, 30])

    hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service)

    hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service)

    return True
예제 #19
0
def setup(hass, config):
    """ Tracks the state of the sun. """
    logger = logging.getLogger(__name__)

    if not util.validate_config(
            config, {ha.DOMAIN: [ha.CONF_LATITUDE, ha.CONF_LONGITUDE]},
            logger):
        return False

    try:
        import ephem
    except ImportError:
        logger.exception("Error while importing dependency ephem.")
        return False

    sun = ephem.Sun()  # pylint: disable=no-member

    latitude = config[ha.DOMAIN][ha.CONF_LATITUDE]
    longitude = config[ha.DOMAIN][ha.CONF_LONGITUDE]

    # Validate latitude and longitude
    observer = ephem.Observer()

    errors = []

    try:
        observer.lat = latitude  # pylint: disable=assigning-non-slot
    except ValueError:
        errors.append("invalid value for latitude given: {}".format(latitude))

    try:
        observer.long = longitude  # pylint: disable=assigning-non-slot
    except ValueError:
        errors.append("invalid value for latitude given: {}".format(latitude))

    if errors:
        logger.error("Error setting up: %s", ", ".join(errors))
        return False

    def update_sun_state(now):
        """ Method to update the current state of the sun and
            set time of next setting and rising. """
        utc_offset = datetime.utcnow() - datetime.now()
        utc_now = now + utc_offset

        observer = ephem.Observer()
        observer.lat = latitude  # pylint: disable=assigning-non-slot
        observer.long = longitude  # pylint: disable=assigning-non-slot

        next_rising_dt = ephem.localtime(
            observer.next_rising(sun, start=utc_now))
        next_setting_dt = ephem.localtime(
            observer.next_setting(sun, start=utc_now))

        if next_rising_dt > next_setting_dt:
            new_state = STATE_ABOVE_HORIZON
            next_change = next_setting_dt

        else:
            new_state = STATE_BELOW_HORIZON
            next_change = next_rising_dt

        logger.info("%s. Next change: %s", new_state,
                    next_change.strftime("%H:%M"))

        state_attributes = {
            STATE_ATTR_NEXT_RISING: util.datetime_to_str(next_rising_dt),
            STATE_ATTR_NEXT_SETTING: util.datetime_to_str(next_setting_dt)
        }

        hass.states.set(ENTITY_ID, new_state, state_attributes)

        # +1 second so Ephem will report it has set
        hass.track_point_in_time(update_sun_state,
                                 next_change + timedelta(seconds=1))

    update_sun_state(datetime.now())

    return True