コード例 #1
0
ファイル: test_exceptions.py プロジェクト: Claret-Srl/core
def test_conditionerror_format():
    """Test ConditionError stringifiers."""
    error1 = ConditionErrorMessage("test", "A test error")
    assert str(error1) == "In 'test' condition: A test error"

    error2 = ConditionErrorMessage("test", "Another error")
    assert str(error2) == "In 'test' condition: Another error"

    error_pos1 = ConditionErrorIndex("box", index=0, total=2, error=error1)
    assert (str(error_pos1) == """In 'box' (item 1 of 2):
  In 'test' condition: A test error""")

    error_pos2 = ConditionErrorIndex("box", index=1, total=2, error=error2)
    assert (str(error_pos2) == """In 'box' (item 2 of 2):
  In 'test' condition: Another error""")

    error_container1 = ConditionErrorContainer("box",
                                               errors=[error_pos1, error_pos2])
    assert (str(error_container1) == """In 'box' (item 1 of 2):
  In 'test' condition: A test error
In 'box' (item 2 of 2):
  In 'test' condition: Another error""")

    error_pos3 = ConditionErrorIndex("box", index=0, total=1, error=error1)
    assert (str(error_pos3) == """In 'box':
  In 'test' condition: A test error""")
コード例 #2
0
def time(
    hass: HomeAssistant,
    before: dt_util.dt.time | str | None = None,
    after: dt_util.dt.time | str | None = None,
    weekday: None | str | Container[str] = None,
) -> bool:
    """Test if local time condition matches.

    Handle the fact that time is continuous and we may be testing for
    a period that crosses midnight. In that case it is easier to test
    for the opposite. "(23:59 <= now < 00:01)" would be the same as
    "not (00:01 <= now < 23:59)".
    """
    now = dt_util.now()
    now_time = now.time()

    if after is None:
        after = dt_util.dt.time(0)
    elif isinstance(after, str):
        after_entity = hass.states.get(after)
        if not after_entity:
            raise ConditionErrorMessage("time", f"unknown 'after' entity {after}")
        after = dt_util.dt.time(
            after_entity.attributes.get("hour", 23),
            after_entity.attributes.get("minute", 59),
            after_entity.attributes.get("second", 59),
        )

    if before is None:
        before = dt_util.dt.time(23, 59, 59, 999999)
    elif isinstance(before, str):
        before_entity = hass.states.get(before)
        if not before_entity:
            raise ConditionErrorMessage("time", f"unknown 'before' entity {before}")
        before = dt_util.dt.time(
            before_entity.attributes.get("hour", 23),
            before_entity.attributes.get("minute", 59),
            before_entity.attributes.get("second", 59),
            999999,
        )

    if after < before:
        if not after <= now_time < before:
            return False
    else:
        if before <= now_time < after:
            return False

    if weekday is not None:
        now_weekday = WEEKDAYS[now.weekday()]

        if (
            isinstance(weekday, str)
            and weekday != now_weekday
            or now_weekday not in weekday
        ):
            return False

    return True
コード例 #3
0
ファイル: condition.py プロジェクト: vog31/core
def state(
    hass: HomeAssistant,
    entity: Union[None, str, State],
    req_state: Any,
    for_period: Optional[timedelta] = None,
    attribute: Optional[str] = None,
) -> bool:
    """Test if state matches requirements.

    Async friendly.
    """
    if entity is None:
        raise ConditionErrorMessage("state", "no entity specified")

    if isinstance(entity, str):
        entity_id = entity
        entity = hass.states.get(entity)

        if entity is None:
            raise ConditionErrorMessage("state", f"unknown entity {entity_id}")
    else:
        entity_id = entity.entity_id

    if attribute is not None and attribute not in entity.attributes:
        raise ConditionErrorMessage(
            "state",
            f"attribute '{attribute}' (of entity {entity_id}) does not exist")

    assert isinstance(entity, State)

    if attribute is None:
        value: Any = entity.state
    else:
        value = entity.attributes.get(attribute)

    if not isinstance(req_state, list):
        req_state = [req_state]

    is_state = False
    for req_state_value in req_state:
        state_value = req_state_value
        if (isinstance(req_state_value, str)
                and INPUT_ENTITY_ID.match(req_state_value) is not None):
            state_entity = hass.states.get(req_state_value)
            if not state_entity:
                raise ConditionErrorMessage(
                    "state",
                    f"the 'state' entity {req_state_value} is unavailable")
            state_value = state_entity.state
        is_state = value == state_value
        if is_state:
            break

    if for_period is None or not is_state:
        return is_state

    return dt_util.utcnow() - for_period > entity.last_changed
コード例 #4
0
    def if_in_zone(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
        """Test if condition."""
        errors = []

        all_ok = True
        for entity_id in entity_ids:
            entity_ok = False
            for zone_entity_id in zone_entity_ids:
                try:
                    if zone(hass, zone_entity_id, entity_id):
                        entity_ok = True
                except ConditionErrorMessage as ex:
                    errors.append(
                        ConditionErrorMessage(
                            "zone",
                            f"error matching {entity_id} with {zone_entity_id}: {ex.message}",
                        )
                    )

            if not entity_ok:
                all_ok = False

        # Raise the errors only if no definitive result was found
        if errors and not all_ok:
            raise ConditionErrorContainer("zone", errors=errors)

        return all_ok
コード例 #5
0
def zone(
    hass: HomeAssistant,
    zone_ent: None | str | State,
    entity: None | str | State,
) -> bool:
    """Test if zone-condition matches.

    Async friendly.
    """
    if zone_ent is None:
        raise ConditionErrorMessage("zone", "no zone specified")

    if isinstance(zone_ent, str):
        zone_ent_id = zone_ent

        if (zone_ent := hass.states.get(zone_ent)) is None:
            raise ConditionErrorMessage("zone", f"unknown zone {zone_ent_id}")
コード例 #6
0
ファイル: condition.py プロジェクト: nickovs/home-assistant
def async_numeric_state(  # noqa: C901
    hass: HomeAssistant,
    entity: None | str | State,
    below: float | str | None = None,
    above: float | str | None = None,
    value_template: Template | None = None,
    variables: TemplateVarsType = None,
    attribute: str | None = None,
) -> bool:
    """Test a numeric state condition."""
    if entity is None:
        raise ConditionErrorMessage("numeric_state", "no entity specified")

    if isinstance(entity, str):
        entity_id = entity

        if (entity := hass.states.get(entity)) is None:
            raise ConditionErrorMessage("numeric_state", f"unknown entity {entity_id}")
コード例 #7
0
def state(
    hass: HomeAssistant,
    entity: None | str | State,
    req_state: Any,
    for_period: timedelta | None = None,
    attribute: str | None = None,
) -> bool:
    """Test if state matches requirements.

    Async friendly.
    """
    if entity is None:
        raise ConditionErrorMessage("state", "no entity specified")

    if isinstance(entity, str):
        entity_id = entity

        if (entity := hass.states.get(entity)) is None:
            raise ConditionErrorMessage("state", f"unknown entity {entity_id}")
コード例 #8
0
def async_template(hass: HomeAssistant,
                   value_template: Template,
                   variables: TemplateVarsType = None) -> bool:
    """Test if template condition matches."""
    try:
        value: str = value_template.async_render(variables, parse_result=False)
    except TemplateError as ex:
        raise ConditionErrorMessage("template", str(ex)) from ex

    return value.lower() == "true"
コード例 #9
0
def async_template(
    hass: HomeAssistant,
    value_template: Template,
    variables: TemplateVarsType = None,
    trace_result: bool = True,
) -> bool:
    """Test if template condition matches."""
    try:
        info = value_template.async_render_to_info(variables, parse_result=False)
        value = info.result()
    except TemplateError as ex:
        raise ConditionErrorMessage("template", str(ex)) from ex

    result = value.lower() == "true"
    if trace_result:
        condition_trace_set_result(result, entities=list(info.entities))
    return result
コード例 #10
0
def time(
    hass: HomeAssistant,
    before: dt_time | str | None = None,
    after: dt_time | str | None = None,
    weekday: None | str | Container[str] = None,
) -> bool:
    """Test if local time condition matches.

    Handle the fact that time is continuous and we may be testing for
    a period that crosses midnight. In that case it is easier to test
    for the opposite. "(23:59 <= now < 00:01)" would be the same as
    "not (00:01 <= now < 23:59)".
    """
    now = dt_util.now()
    now_time = now.time()

    if after is None:
        after = dt_time(0)
    elif isinstance(after, str):
        if not (after_entity := hass.states.get(after)):
            raise ConditionErrorMessage("time",
                                        f"unknown 'after' entity {after}")
        if after_entity.domain == "input_datetime":
            after = dt_time(
                after_entity.attributes.get("hour", 23),
                after_entity.attributes.get("minute", 59),
                after_entity.attributes.get("second", 59),
            )
        elif after_entity.attributes.get(
                ATTR_DEVICE_CLASS
        ) == SensorDeviceClass.TIMESTAMP and after_entity.state not in (
                STATE_UNAVAILABLE,
                STATE_UNKNOWN,
        ):
            after_datetime = dt_util.parse_datetime(after_entity.state)
            if after_datetime is None:
                return False
            after = dt_util.as_local(after_datetime).time()
        else:
            return False
コード例 #11
0
def zone(
    hass: HomeAssistant,
    zone_ent: None | str | State,
    entity: None | str | State,
) -> bool:
    """Test if zone-condition matches.

    Async friendly.
    """
    if zone_ent is None:
        raise ConditionErrorMessage("zone", "no zone specified")

    if isinstance(zone_ent, str):
        zone_ent_id = zone_ent
        zone_ent = hass.states.get(zone_ent)

        if zone_ent is None:
            raise ConditionErrorMessage("zone", f"unknown zone {zone_ent_id}")

    if entity is None:
        raise ConditionErrorMessage("zone", "no entity specified")

    if isinstance(entity, str):
        entity_id = entity
        entity = hass.states.get(entity)

        if entity is None:
            raise ConditionErrorMessage("zone", f"unknown entity {entity_id}")
    else:
        entity_id = entity.entity_id

    latitude = entity.attributes.get(ATTR_LATITUDE)
    longitude = entity.attributes.get(ATTR_LONGITUDE)

    if latitude is None:
        raise ConditionErrorMessage(
            "zone", f"entity {entity_id} has no 'latitude' attribute"
        )

    if longitude is None:
        raise ConditionErrorMessage(
            "zone", f"entity {entity_id} has no 'longitude' attribute"
        )

    return zone_cmp.in_zone(
        zone_ent, latitude, longitude, entity.attributes.get(ATTR_GPS_ACCURACY, 0)
    )
コード例 #12
0
def async_numeric_state(  # noqa: C901
    hass: HomeAssistant,
    entity: None | str | State,
    below: float | str | None = None,
    above: float | str | None = None,
    value_template: Template | None = None,
    variables: TemplateVarsType = None,
    attribute: str | None = None,
) -> bool:
    """Test a numeric state condition."""
    if entity is None:
        raise ConditionErrorMessage("numeric_state", "no entity specified")

    if isinstance(entity, str):
        entity_id = entity
        entity = hass.states.get(entity)

        if entity is None:
            raise ConditionErrorMessage("numeric_state", f"unknown entity {entity_id}")
    else:
        entity_id = entity.entity_id

    if attribute is not None and attribute not in entity.attributes:
        raise ConditionErrorMessage(
            "numeric_state",
            f"attribute '{attribute}' (of entity {entity_id}) does not exist",
        )

    value: Any = None
    if value_template is None:
        if attribute is None:
            value = entity.state
        else:
            value = entity.attributes.get(attribute)
    else:
        variables = dict(variables or {})
        variables["state"] = entity
        try:
            value = value_template.async_render(variables)
        except TemplateError as ex:
            raise ConditionErrorMessage(
                "numeric_state", f"template error: {ex}"
            ) from ex

    # Known states that never match the numeric condition
    if value in (STATE_UNAVAILABLE, STATE_UNKNOWN):
        return False

    try:
        fvalue = float(value)
    except (ValueError, TypeError) as ex:
        raise ConditionErrorMessage(
            "numeric_state",
            f"entity {entity_id} state '{value}' cannot be processed as a number",
        ) from ex

    if below is not None:
        if isinstance(below, str):
            below_entity = hass.states.get(below)
            if not below_entity:
                raise ConditionErrorMessage(
                    "numeric_state", f"unknown 'below' entity {below}"
                )
            if below_entity.state in (
                STATE_UNAVAILABLE,
                STATE_UNKNOWN,
            ):
                return False
            try:
                if fvalue >= float(below_entity.state):
                    condition_trace_set_result(
                        False,
                        state=fvalue,
                        wanted_state_below=float(below_entity.state),
                    )
                    return False
            except (ValueError, TypeError) as ex:
                raise ConditionErrorMessage(
                    "numeric_state",
                    f"the 'below' entity {below} state '{below_entity.state}' cannot be processed as a number",
                ) from ex
        elif fvalue >= below:
            condition_trace_set_result(False, state=fvalue, wanted_state_below=below)
            return False

    if above is not None:
        if isinstance(above, str):
            above_entity = hass.states.get(above)
            if not above_entity:
                raise ConditionErrorMessage(
                    "numeric_state", f"unknown 'above' entity {above}"
                )
            if above_entity.state in (
                STATE_UNAVAILABLE,
                STATE_UNKNOWN,
            ):
                return False
            try:
                if fvalue <= float(above_entity.state):
                    condition_trace_set_result(
                        False,
                        state=fvalue,
                        wanted_state_above=float(above_entity.state),
                    )
                    return False
            except (ValueError, TypeError) as ex:
                raise ConditionErrorMessage(
                    "numeric_state",
                    f"the 'above' entity {above} state '{above_entity.state}' cannot be processed as a number",
                ) from ex
        elif fvalue <= above:
            condition_trace_set_result(False, state=fvalue, wanted_state_above=above)
            return False

    condition_trace_set_result(True, state=fvalue)
    return True
コード例 #13
0
def time(
    hass: HomeAssistant,
    before: dt_util.dt.time | str | None = None,
    after: dt_util.dt.time | str | None = None,
    weekday: None | str | Container[str] = None,
) -> bool:
    """Test if local time condition matches.

    Handle the fact that time is continuous and we may be testing for
    a period that crosses midnight. In that case it is easier to test
    for the opposite. "(23:59 <= now < 00:01)" would be the same as
    "not (00:01 <= now < 23:59)".
    """
    now = dt_util.now()
    now_time = now.time()

    if after is None:
        after = dt_util.dt.time(0)
    elif isinstance(after, str):
        after_entity = hass.states.get(after)
        if not after_entity:
            raise ConditionErrorMessage("time",
                                        f"unknown 'after' entity {after}")
        if after_entity.domain == "input_datetime":
            after = dt_util.dt.time(
                after_entity.attributes.get("hour", 23),
                after_entity.attributes.get("minute", 59),
                after_entity.attributes.get("second", 59),
            )
        elif after_entity.attributes.get(
                ATTR_DEVICE_CLASS
        ) == DEVICE_CLASS_TIMESTAMP and after_entity.state not in (
                STATE_UNAVAILABLE,
                STATE_UNKNOWN,
        ):
            after_datetime = dt_util.parse_datetime(after_entity.state)
            if after_datetime is None:
                return False
            after = dt_util.as_local(after_datetime).time()
        else:
            return False

    if before is None:
        before = dt_util.dt.time(23, 59, 59, 999999)
    elif isinstance(before, str):
        before_entity = hass.states.get(before)
        if not before_entity:
            raise ConditionErrorMessage("time",
                                        f"unknown 'before' entity {before}")
        if before_entity.domain == "input_datetime":
            before = dt_util.dt.time(
                before_entity.attributes.get("hour", 23),
                before_entity.attributes.get("minute", 59),
                before_entity.attributes.get("second", 59),
            )
        elif before_entity.attributes.get(
                ATTR_DEVICE_CLASS
        ) == DEVICE_CLASS_TIMESTAMP and before_entity.state not in (
                STATE_UNAVAILABLE,
                STATE_UNKNOWN,
        ):
            before_timedatime = dt_util.parse_datetime(before_entity.state)
            if before_timedatime is None:
                return False
            before = dt_util.as_local(before_timedatime).time()
        else:
            return False

    if after < before:
        condition_trace_update_result(after=after,
                                      now_time=now_time,
                                      before=before)
        if not after <= now_time < before:
            return False
    else:
        condition_trace_update_result(after=after,
                                      now_time=now_time,
                                      before=before)
        if before <= now_time < after:
            return False

    if weekday is not None:
        now_weekday = WEEKDAYS[now.weekday()]

        condition_trace_update_result(weekday=weekday, now_weekday=now_weekday)
        if (isinstance(weekday, str) and weekday != now_weekday
                or now_weekday not in weekday):
            return False

    return True
コード例 #14
0
        )
        return False

    value: Any = None
    if value_template is None:
        if attribute is None:
            value = entity.state
        else:
            value = entity.attributes.get(attribute)
    else:
        variables = dict(variables or {})
        variables["state"] = entity
        try:
            value = value_template.async_render(variables)
        except TemplateError as ex:
            raise ConditionErrorMessage("numeric_state",
                                        f"template error: {ex}") from ex

    # Known states or attribute values that never match the numeric condition
    if value in (None, STATE_UNAVAILABLE, STATE_UNKNOWN):
        condition_trace_set_result(
            False,
            message=f"value '{value}' is non-numeric and treated as False",
        )
        return False

    try:
        fvalue = float(value)
    except (ValueError, TypeError) as ex:
        raise ConditionErrorMessage(
            "numeric_state",
            f"entity {entity_id} state '{value}' cannot be processed as a number",
コード例 #15
0
ファイル: condition.py プロジェクト: vog31/core
def async_numeric_state(
    hass: HomeAssistant,
    entity: Union[None, str, State],
    below: Optional[Union[float, str]] = None,
    above: Optional[Union[float, str]] = None,
    value_template: Optional[Template] = None,
    variables: TemplateVarsType = None,
    attribute: Optional[str] = None,
) -> bool:
    """Test a numeric state condition."""
    if entity is None:
        raise ConditionErrorMessage("numeric_state", "no entity specified")

    if isinstance(entity, str):
        entity_id = entity
        entity = hass.states.get(entity)

        if entity is None:
            raise ConditionErrorMessage("numeric_state",
                                        f"unknown entity {entity_id}")
    else:
        entity_id = entity.entity_id

    if attribute is not None and attribute not in entity.attributes:
        raise ConditionErrorMessage(
            "numeric_state",
            f"attribute '{attribute}' (of entity {entity_id}) does not exist",
        )

    value: Any = None
    if value_template is None:
        if attribute is None:
            value = entity.state
        else:
            value = entity.attributes.get(attribute)
    else:
        variables = dict(variables or {})
        variables["state"] = entity
        try:
            value = value_template.async_render(variables)
        except TemplateError as ex:
            raise ConditionErrorMessage("numeric_state",
                                        f"template error: {ex}") from ex

    if value in (STATE_UNAVAILABLE, STATE_UNKNOWN):
        raise ConditionErrorMessage("numeric_state",
                                    f"state of {entity_id} is unavailable")

    try:
        fvalue = float(value)
    except (ValueError, TypeError) as ex:
        raise ConditionErrorMessage(
            "numeric_state",
            f"entity {entity_id} state '{value}' cannot be processed as a number",
        ) from ex

    if below is not None:
        if isinstance(below, str):
            below_entity = hass.states.get(below)
            if not below_entity or below_entity.state in (
                    STATE_UNAVAILABLE,
                    STATE_UNKNOWN,
            ):
                raise ConditionErrorMessage(
                    "numeric_state",
                    f"the 'below' entity {below} is unavailable")
            try:
                if fvalue >= float(below_entity.state):
                    return False
            except (ValueError, TypeError) as ex:
                raise ConditionErrorMessage(
                    "numeric_state",
                    f"the 'below' entity {below} state '{below_entity.state}' cannot be processed as a number",
                ) from ex
        elif fvalue >= below:
            return False

    if above is not None:
        if isinstance(above, str):
            above_entity = hass.states.get(above)
            if not above_entity or above_entity.state in (
                    STATE_UNAVAILABLE,
                    STATE_UNKNOWN,
            ):
                raise ConditionErrorMessage(
                    "numeric_state",
                    f"the 'above' entity {above} is unavailable")
            try:
                if fvalue <= float(above_entity.state):
                    return False
            except (ValueError, TypeError) as ex:
                raise ConditionErrorMessage(
                    "numeric_state",
                    f"the 'above' entity {above} state '{above_entity.state}' cannot be processed as a number",
                ) from ex
        elif fvalue <= above:
            return False

    return True