def create_timers(start_times):
    """Creates Timers to transition the time of day based on the passed in list
    of DateTime Item names. If an Item is dated with yesterday, the Item is
    updated to today. The ETOD_ITEM is commanded to the current time of day if
    it's not already the correct state.
    Arguments:
        - start_times: list of names for DateTime Items containing the start
        times for each time period
    """

    now = DateTime().now()
    most_recent_time = now.minusDays(1)
    most_recent_state = items[ETOD_ITEM]

    for time in start_times:

        item_time = DateTime(str(items[time]))
        trigger_time = to_today(items[time])

        # Update the Item with today's date if it was for yesterday.
        if item_time.isBefore(trigger_time):
            log.debug("Item {} is yesterday, updating to today".format(time))
            events.postUpdate(time, str(trigger_time))

        # Get the etod state from the metadata.
        state = get_value(time, NAMESPACE)

        # If it's in the past but after most_recent, update most_recent.
        if trigger_time.isBefore(now) and trigger_time.isAfter(
                most_recent_time):
            log.debug(
                "NOW:    {} start time {} is in the past but after {}".format(
                    state, trigger_time, most_recent_time))
            most_recent_time = trigger_time
            most_recent_state = get_value(time, NAMESPACE)

        # If it's in the future, schedule a Timer.
        elif trigger_time.isAfter(now):
            log.debug("FUTURE: {} Scheduleing Timer for {}".format(
                state, trigger_time))
            timers.check(state,
                         trigger_time,
                         function=lambda st=state: etod_transition(st))

        # If it's in the past but not after most_recent_time we can ignore it.
        else:
            log.debug(
                "PAST:   {} start time of {} is before now {} and before {}".
                format(state, trigger_time, now, most_recent_time))

    log.info("Created {} timers.".format(len(timers.timers)))
    log.info("The current time of day is {}".format(most_recent_state))
    send_command_if_different(ETOD_ITEM, most_recent_state)
Exemple #2
0
def update_group(target, only_if_scene_parent=False, scene=None, parent_scene=None):
    if str(get_value(target.name, META_NAME_EOS)).lower() in META_STRING_FALSE:
        if config.log_trace:
            log.debug(
                "Skipping update for group '{name}' as it is disabled".format(
                    name=target.name
                )
            )
        return
    elif config.log_trace:
        log.debug("Processing update for group '{name}'".format(name=target.name))

    scene = scene or str(get_scene_item(target).state).lower()
    if scene == SCENE_PARENT:
        scene = parent_scene or scene
    elif only_if_scene_parent:
        return

    for light_item in get_light_items(target):
        try:
            update_light(light_item, scene=scene)
        except:
            continue

    for group_item in get_group_items(target):
        update_group(group_item, only_if_scene_parent, parent_scene=scene)
Exemple #3
0
    def get_child_area_group_items(self):  #gets all child area groups
        child_groups = []
        for child_item in self.item.members:
            if metadata.get_value(child_item.name,
                                  'OccupancySettings') is not None:
                #if MetadataRegistry.get(MetadataKey('OccupancySettings',child_item.name)):
                child_groups.append(child_item)

        return child_groups
Exemple #4
0
 def _gen_triggers_for_group(group):
     if str(get_value(group.name,
                      META_NAME_EOS)).lower() in META_STRING_FALSE:
         log.info("Found group '{group}' but it is disabled".format(
             name=group.name))
     else:
         log.debug("Scanning group '{group}'".format(group=group.name))
         itemScene = get_scene_item(group)
         if itemScene:
             # add scene triggers
             when("Item {name} received command".format(
                 name=itemScene.name))(rule_scene_command)
             when("Item {name} changed".format(
                 name=itemScene.name))(rule_scene_changed)
             log.debug(
                 "Added triggers for scene item '{name}' in '{group}'".
                 format(name=itemScene.name, group=group.name))
             # gen triggers for Level and Motion sources in metadata
             _gen_triggers_for_sources(
                 get_metadata(group.name,
                              META_NAME_EOS).get("configuration", {}))
             # add lights triggers
             for light in get_light_items(group):
                 if (str(get_value(light.name, META_NAME_EOS)).lower()
                         in META_STRING_FALSE):
                     log.info(
                         "Found light '{name}' in '{group}' but it is disabled"
                         .format(name=light.name, group=group.name))
                 else:
                     _gen_triggers_for_sources(
                         get_metadata(light.name, META_NAME_EOS).get(
                             "configuration", {}))
                     when("Item {name} received update".format(
                         name=light.name))(rule_light_update)
                     log.debug(
                         "Added light received update trigger for '{name}' in '{group}'"
                         .format(name=light.name, group=group.name))
             # recurse into groups
             for group in get_group_items(group):
                 _gen_triggers_for_group(group)
         else:
             log.warn(
                 "Group '{group}' will be ignored because it has no scene item"
                 .format(group=group.name))
Exemple #5
0
    def setup_areas(self):
        items = itemRegistry.getItems()

        for item in items:
            if metadata.get_value(
                    item.name, "OccupancySettings"
            ) is not None:  # add any group with the metadata key OccupancySettings
                self.add_area(item)

        log.info('Found Areas: {}'.format(self.areas))
Exemple #6
0
    def get_occupancy_items(
            self):  # gets all items that cause occupancy events
        event_items = []
        for child_item in self.item.members:
            if metadata.get_value(child_item.name,
                                  'OccupancyEvent') is not None:
                #if MetadataRegistry.get(MetadataKey('OccupancyEvent',child_item.name)):
                event_items.append(child_item)

        return event_items
Exemple #7
0
    def get_parent_area_group(
            self):  # finds a parent area for areas list if there is one
        #names = list (group_name for group_name in self.item.getGroupNames () if "Area" in itemRegistry.getItem (group_name).getTags ())

        for group_name in self.item.getGroupNames():
            if metadata.get_value(group_name, 'OccupancySettings') is not None:
                #if MetadataRegistry.get(MetadataKey('OccupancySettings',group_name)):
                parent_area = self.area_list[group_name]
                return parent_area

        return None
Exemple #8
0
    def log_details(self, level, indent):  #logs all details about the area
        ot = self.occupancy_timeout or 'Vacant'
        log.warn(indent + 'Area: {}, Occupied until = {}, {}'.format(
            self.name, ot, (self.is_locked() and 'Locked' or 'Unlocked')))

        log.warn(indent + '  Occupancy Items:')
        occupancy_items = self.get_occupancy_items()

        for item in occupancy_items:
            event = metadata.get_value(item.name, "OccupancyEvent")
            #event=MetadataRegistry.get(MetadataKey("OccupancyEvent",item.name))
            log.warn(indent +
                     '    {} event settings {}'.format(item.name, event))
Exemple #9
0
    def get_group_area_item_for_item(
            self, item_name):  # finds the area that an item belongs to
        item = itemRegistry.getItem(item_name)

        for group_name in item.getGroupNames(
        ):  # find the group occupancy area item for this item
            if metadata.get_value(group_name, "OccupancySettings") is not None:
                area_item = itemRegistry.getItem(group_name)
                log.info('Item {} is in area {}'.format(
                    item.name, area_item.name))
                return area_item

        return None
Exemple #10
0
def rotary_dimmer_handler(event):
    item_dimmer = get_value(event.itemName, 'controls')
    if not item_dimmer:
        rotary_dimmer_handler.log.warn("No 'controls' metadata for {}".format(
            event.itemName))
        return

    if item_dimmer not in items:
        rotary_dimmer_handler.log.warn(
            "Controlled item {} for {} is not found".format(
                item_dimmer, event.itemName))
        return

    value = event.itemState.toString()

    # Use a larger delta when the knob is being turned faster
    if value in ('rotate_left', 'rotate_right'):
        # Lookup table of (rotate action time_delta threshold, dimmer delta to use)
        delta_table = ((0.25, 10), (0.7, 5), (1, 2))
        current_time = time.time()
        time_delta = current_time - last_update.get(event.itemName, 0)
        last_update[event.itemName] = current_time
        for threshold, delta in delta_table:
            if time_delta < threshold:
                break
        else:
            delta = 1

        rotary_dimmer_handler.log.debug('timedelta: {}, delta: {}'.format(
            time_delta, delta))

        if value == 'rotate_right':
            if items[item_dimmer] < PercentType(100):
                sendCommand(item_dimmer,
                            min(100, items[item_dimmer].floatValue() + delta))

        elif value == 'rotate_left':
            if items[item_dimmer] > PercentType(0):
                sendCommand(item_dimmer,
                            max(0, items[item_dimmer].floatValue() - delta))

    if value == 'play_pause':  # single click
        sendCommand(item_dimmer, '2')

    elif value == 'skip_forward':  # double click
        sendCommand(item_dimmer, '50')

    elif value == 'skip_backward':  # triple click
        sendCommand(item_dimmer, '100')
def item_init(event):
    """Rule that triggers at System started and populates Items with an initial
    value. The initialization value is defined in metadata with three key/values.
        - value: value to send update the Item to
        - override: optional boolean value to override the state of the Item
        even if it isn't NULL, defaults to "False".
        - clear: optional boolean value to delete the metadata once the Item is
        updated. Defaults to "False".
    For example:
        - { init="ON"[override="true", clear="true"] }: Initialize
            the Switch Item to ON whether or not it already has a value and then
        delete the metadata.
        - { init="123,45,67" }: Initialize the Color Item to the value only if
        it is NULL or UNDEF.
    Limitations:
        - The clear option only works for Items not created through .items
            files. For Items defined in .items files, you must manually remove
            the metadata or else it will get reloaded next time the file is
            loaded.
    """

    item_init.log.info("Initializing Items")
    for item_name in [i for i in items if get_metadata(i, "init")]:
        value = get_value(item_name, "init")

        # Always update if override is True
        if get_key_value(item_name, "init", "override") == "True":
            post_update_if_different(item_name, value)
            item_init.log.info(
                "Overriding current value {} of {} to {}".format(
                    items[item_name], item_name, value))

        # If not overridden, only update if the Item is currently NULL or UNDEF.
        elif isinstance(items[item_name], UnDefType):
            item_init.log.info("Initializing {} to {}".format(
                item_name, value))
            postUpdate(item_name, value)

        # Delete the metadata now that the Item is initialized.
        if get_key_value(item_name, "init", "clear") == "true":
            item_init.log.info(
                "Removing init metadata from {}".format(item_name))
            remove_metadata(item_name, "init")
def generate_triggers(namespace, check_config, event, logger):
    """Generates a trigger for all Items with namespace metadata that passes
    check_config. The event is defined in event.

    Arguments:
        - namespace: the metadata namespace to look for
        - check_config: function to call to check the validity of the metadata
        - event: rule trigger event (e.g. changed)
        - logger: used to log out errors and informational statement
    Returns:
        A list of rule trigger strings.
    """

    triggers = []
    for i in [i for i in scope.items if get_value(i, namespace)]:
        if check_config(i, logger):
            triggers.append("Item {} {}".format(i, event))

    return triggers
Exemple #13
0
def update_light(item, scene=None):
    """
    Sends commands to lights based on scene.
    """
    if str(get_value(item.name, META_NAME_EOS)).lower() in META_STRING_FALSE:
        if config.log_trace:
            log.debug(
                "Skipping update for light '{name}' as it is disabled".format(
                    name=item.name
                )
            )
        return
    elif config.log_trace:
        log.debug("Processing update for light '{name}'".format(name=item.name))

    scene = scene if scene and scene != SCENE_PARENT else get_scene_for_item(item)
    if config.log_trace:
        log.debug(
            "Got scene '{scene}' for item '{name}'".format(scene=scene, name=item.name)
        )

    if scene != SCENE_MANUAL:
        newState = get_state_for_scene(item, scene)
        if sendCommandCheckFirst(item.name, newState, floatPrecision=3):
            log.debug(
                "Light '{name}' scene is '{scene}', sent command '{command}'".format(
                    name=item.name, scene=scene, command=newState
                )
            )
        else:
            log.debug(
                "Light '{name}' scene is '{scene}', state is already '{command}'".format(
                    name=item.name, scene=scene, command=newState
                )
            )
    else:
        log.debug(
            "Light '{name}' scene is '{scene}', no action taken".format(
                name=item.name, scene=scene
            )
        )
Exemple #14
0
def get_config(item_name, log):
    """Parses the config string to validate it's correctness and completeness.
    At a minimum it verifies the proxy Item exists, the timeout exists and is
    parsable.
    Arguments:
      item_name: the name of an Item to get the debounce metadata from
    Returns:
      An Item metadata Object or None if there is no such metadata or the
      metadata is malformed.
    """

    error = False
    cfg = get_metadata(item_name, "debounce")
    if not cfg:
        log.error("Item {} has no debounce metadata!".format(item_name))
        error = True
    elif not cfg.value or cfg.value not in items:
        log.error("Proxy Item {} for Item {} does not exist!".format(
            cfg.value, item_name))
        error = True
    elif not "timeout" in cfg.configuration:
        log.error("Debounce metadata for Item {} does not include a "
                  "timeout property!".format(item_name))
        error = True
    elif not parse_duration(cfg.configuration["timeout"]):
        log.error("timeout property for Item {} is invalid!".format(item_name))
        error = True

    if error:
        log.error(
            "Debounce config on {} is not valid: {}"
            "\nExpected format is : debounce=\"ProxyItem\"[timeout=\"duration\", states=\"State1,State2\", command=\"True\"]"
            "\nwhere:"
            "\n  ProxyItem: name of the Item that will be commanded or updated after the debounce"
            "\n  timeout: required parameter with the duration of the format 'xd xh xm xs' where each field is optional and x is a number, 2s would be 2 seconds, 0.5s would be 500 msec"
            "\n  states: optional, list all the states that are debounced; when not present all states are debounced; states not in the list go directly to the proxy"
            "\n  command: optional, when True the proxy will be commanded; when False proxy will be updated, defaults to False"
            .format(item_name, get_value(item_name, "expire")))
        return None
    else:
        return cfg
Exemple #15
0
    def set_area_vacant(self, reason):
        log.warn("Set Area {} to vacant, reason {}".format(self.name, reason))

        if not self.is_locked():  # check if area locked before proceeding
            self.update_group_OS_item('OFF')
            self.cancel_timer()  # cancel any running timers

            # when area is vacant, force child areas to vacant
            for child_item in self.item.members:
                #if "Area" in child_item.getTags():
                if metadata.get_value(child_item.name,
                                      'OccupancySettings') is not None:
                    #if MetadataRegistry.get(MetadataKey('OccupancySettings',child_item.name)):
                    area = self.area_list[child_item.name]
                    log.info(
                        "Propagating occupancy state to child area {}".format(
                            child_item.name))
                    area.set_area_vacant('Parent Vacant')

        else:  # note we cannot really fix this here, user needs to not set an area vacant if it is locked ** may need to change this behavior
            log.info('Area {} is locked.'.format(self.name))
Exemple #16
0
def update_scene(item, scene=None):
    """
    Updates all lights and subgroups, and propagates scene change to children
    with ``follow_parent`` set to ``True``.
    """
    scene = scene or str(item.state).lower()
    log.info(
        "Changing '{group}' scene to '{scene}'".format(
            group=get_item_eos_group(item).name, scene=item.state
        )
    )

    for light_item in get_light_items(get_item_eos_group(item)):
        try:
            update_light(light_item, scene=scene)
        except:
            continue

    for group_item in get_group_items(get_item_eos_group(item)):
        if (
            str(get_value(group_item.name, META_NAME_EOS)).lower()
            not in META_STRING_FALSE
        ):
            # set children to "parent" scene unless following is turned off
            if resolve_type(
                get_metadata(group_item.name, META_NAME_EOS)
                .get("configuration", {})
                .get(META_KEY_FOLLOW_PARENT, True)
            ):
                log.debug(
                    "Commanding '{group}' scene to 'parent'".format(
                        group=group_item.name
                    )
                )
                sendCommand(get_scene_item(group_item).name, SCENE_PARENT)
            else:
                update_group(group_item, True)
def get_config(i, log):
    """Parses the expire metadata and generates a dict with the values. If the
    config is not valid, None is returned.

    The expected format is:

    expire="<duration>[,[command=|state=]<new state>]"

        - <duration>: a time duration of the format described in parse_time.
        - [,]: if supplying more than just the duration, a comma is required
        here.
        - [command=|state=]: an optional definition of the type of of event to
        send to this Item when it expires. If not supplied it defaults to
        "state=".
        - [<new state>]: an optional state that the Item get's updated (state)
        or commanded (command) to when the time expires. Use '' to represent the
        empty String (differs from Expire1 Binding). Use 'UNDEF' or 'NULL' to
        represent the String rather than the state.

    Examples (taken from the Expire1 Binding docs):
        - expire="1h,command=STOP" (send the STOP command after one hour)
        - expire="5m,state=0"      (update state to 0 after five minutes)
        - expire="3m12s,Hello"     (update state to Hello after three minutes
                                    and 12 seconds)
        - expire="2h"              (update state to UNDEF 2 hours after the last
                                    value)
    Unique to this implementation:
        - expire="5s,state=''"      (update a String Item to the empty String)
        - expire="5s,state=UNDEF"   (for String Items, expires to UNDEF, not the
                                     string, "UNDEF")
        - expire="5s,state='UNDEF'" (for String Items, expires to the String
                                     "UNDEF")
    Argument:
        - i : name of the Item to process the metadata
        - log : logs warning explaining why a config is invalid
    Returns:
        None if the config is invalid.
        A dict with:
            - time : Duration string as defined in time_utils.parse_duration
            - type : either "state" or "command"
            - state : the Expire state
    """

    cfg = get_value(i, "expire")

    # Separate the time from the command/update.
    if cfg:
        cfg = cfg.split(",")
    else:
        log.error(
            "Invalid expire config: Item {} does not have an expire config".
            format(i))
        return None

    # Check that the time part parses.
    if not parse_duration(cfg[0], log):
        log.error(
            "Invalid expire config: Item {} does not have a valid duration string: {}"
            .format(i, cfg[0]))
        return None

    # Default to state updates and UNDEF.
    event = "state"
    state = "UNDEF"

    # If there is more than one element in the split cfg, the user has overridden
    # the defaults. Parse them out.
    if len(cfg) > 1:
        event_str = cfg[1].split("=")

        # If there is an "=" that means we have both the event type and the state
        # defined. Otherwise we only have the state.
        #
        # Preserve any white space until we know whether or not this is for a
        # String Item.
        if len(event_str) > 1:
            event = event_str[0].strip().lower()
            state = event_str[1]
        else:
            state = event_str[0]

        # Convert "UNDEF" and "NULL" to their actual types. This let's us handle
        # using the String "`UNDEF`" and "`NULL`".
        if state.strip() in special_types:
            state = special_types[state]
        # If not a special type, remove any single quotes around the state.
        else:
            state = state.strip("'")

        # Handle the case where the state is empty.
        state = state if str(state).strip() != "" else UNDEF

        # Force the state to a StringType if this is for a String type Item.
        # This is required to set the string "UNDEF" and "NULL".
        if ir.getItem(i).type == "String":
            if isinstance(state, basestring):
                state = StringType(state)
        # Finally, strip whitespace for non String items.
        elif isinstance(state, basestring):
            state = state.strip()

    # Make sure the event is valid
    if event not in ["state", "command"]:
        log.error(
            "Invalid expire config: Unrecognized action '{}' for item '{}'".
            format(event, i))
        return None

    if isinstance(state, UnDefType) and event == "command":
        log.error("Invalid expire config: Cannot command Item {} to {}".format(
            i, state))
        return None

    # Return the dict as a dict
    return {"time": cfg[0], "type": event, "state": state}