Пример #1
0
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
    """Logbook setup."""
    hass.data[DOMAIN] = {}

    @callback
    def log_message(service: ServiceCall) -> None:
        """Handle sending notification message service calls."""
        message = service.data[ATTR_MESSAGE]
        name = service.data[ATTR_NAME]
        domain = service.data.get(ATTR_DOMAIN)
        entity_id = service.data.get(ATTR_ENTITY_ID)

        if entity_id is None and domain is None:
            # If there is no entity_id or
            # domain, the event will get filtered
            # away so we use the "logbook" domain
            domain = DOMAIN

        message.hass = hass
        message = message.async_render(parse_result=False)
        async_log_entry(hass, name, message, domain, entity_id,
                        service.context)

    frontend.async_register_built_in_panel(hass, "logbook", "logbook",
                                           "hass:format-list-bulleted-type")

    recorder_conf = config.get(RECORDER_DOMAIN, {})
    logbook_conf = config.get(DOMAIN, {})
    recorder_filter = extract_include_exclude_filter_conf(recorder_conf)
    logbook_filter = extract_include_exclude_filter_conf(logbook_conf)
    merged_filter = merge_include_exclude_filters(recorder_filter,
                                                  logbook_filter)

    possible_merged_entities_filter = convert_include_exclude_filter(
        merged_filter)
    if not possible_merged_entities_filter.empty_filter:
        filters = sqlalchemy_filter_from_include_exclude_conf(merged_filter)
        entities_filter = possible_merged_entities_filter
    else:
        filters = None
        entities_filter = None
    hass.data[LOGBOOK_FILTERS] = filters
    hass.data[LOGBOOK_ENTITIES_FILTER] = entities_filter
    websocket_api.async_setup(hass)
    rest_api.async_setup(hass, config, filters, entities_filter)
    hass.services.async_register(DOMAIN,
                                 "log",
                                 log_message,
                                 schema=LOG_MESSAGE_SCHEMA)

    await async_process_integration_platforms(hass, DOMAIN,
                                              _process_logbook_platform)

    return True
Пример #2
0
async def test_included_and_excluded_simple_case_no_domains(
        hass, recorder_mock):
    """Test filters with included and excluded without domains."""
    filter_accept = {"sensor.kitchen4", "switch.kitchen"}
    filter_reject = {
        "light.any",
        "switch.other",
        "cover.any",
        "sensor.weather5",
        "light.kitchen",
    }
    conf = {
        CONF_INCLUDE: {
            CONF_ENTITY_GLOBS: ["sensor.kitchen*"],
            CONF_ENTITIES: ["switch.kitchen"],
        },
        CONF_EXCLUDE: {
            CONF_ENTITY_GLOBS: ["sensor.weather*"],
            CONF_ENTITIES: ["light.kitchen"],
        },
    }

    extracted_filter = extract_include_exclude_filter_conf(conf)
    entity_filter = convert_include_exclude_filter(extracted_filter)
    sqlalchemy_filter = sqlalchemy_filter_from_include_exclude_conf(
        extracted_filter)
    assert sqlalchemy_filter is not None

    for entity_id in filter_accept:
        assert entity_filter(entity_id) is True

    for entity_id in filter_reject:
        assert entity_filter(entity_id) is False

    assert not entity_filter.explicitly_included("light.any")
    assert not entity_filter.explicitly_included("switch.other")
    assert entity_filter.explicitly_included("sensor.kitchen4")
    assert entity_filter.explicitly_included("switch.kitchen")

    assert not entity_filter.explicitly_excluded("light.any")
    assert not entity_filter.explicitly_excluded("switch.other")
    assert entity_filter.explicitly_excluded("sensor.weather5")
    assert entity_filter.explicitly_excluded("light.kitchen")

    (
        filtered_states_entity_ids,
        filtered_events_entity_ids,
    ) = await _async_get_states_and_events_with_filter(
        hass, sqlalchemy_filter, filter_accept | filter_reject)

    assert filtered_states_entity_ids == filter_accept
    assert not filtered_states_entity_ids.intersection(filter_reject)

    assert filtered_events_entity_ids == filter_accept
    assert not filtered_events_entity_ids.intersection(filter_reject)
Пример #3
0
async def test_included_and_excluded_complex_case(hass, recorder_mock):
    """Test filters with included and excluded with a complex filter."""
    filter_accept = {"light.any", "sensor.kitchen_4", "switch.kitchen"}
    filter_reject = {
        "camera.one",
        "notify.any",
        "automation.update_readme",
        "automation.update_utilities_cost",
        "binary_sensor.iss",
    }
    conf = {
        CONF_INCLUDE: {
            CONF_ENTITIES: ["group.trackers"],
        },
        CONF_EXCLUDE: {
            CONF_ENTITIES: [
                "automation.update_readme",
                "automation.update_utilities_cost",
                "binary_sensor.iss",
            ],
            CONF_DOMAINS: [
                "camera",
                "group",
                "media_player",
                "notify",
                "scene",
                "sun",
                "zone",
            ],
        },
    }

    extracted_filter = extract_include_exclude_filter_conf(conf)
    entity_filter = convert_include_exclude_filter(extracted_filter)
    sqlalchemy_filter = sqlalchemy_filter_from_include_exclude_conf(
        extracted_filter)
    assert sqlalchemy_filter is not None

    for entity_id in filter_accept:
        assert entity_filter(entity_id) is True

    for entity_id in filter_reject:
        assert entity_filter(entity_id) is False

    (
        filtered_states_entity_ids,
        filtered_events_entity_ids,
    ) = await _async_get_states_and_events_with_filter(
        hass, sqlalchemy_filter, filter_accept | filter_reject)

    assert filtered_states_entity_ids == filter_accept
    assert not filtered_states_entity_ids.intersection(filter_reject)

    assert filtered_events_entity_ids == filter_accept
    assert not filtered_events_entity_ids.intersection(filter_reject)
Пример #4
0
def test_merge_include_exclude_filters():
    """Test we can merge two filters together."""
    include_exclude_filter_base = extract_include_exclude_filter_conf(
        SIMPLE_INCLUDE_EXCLUDE_FILTER
    )
    include_filter_add = extract_include_exclude_filter_conf(
        SIMPLE_INCLUDE_FILTER_DIFFERENT_ENTITIES
    )
    merged_filter = merge_include_exclude_filters(
        include_exclude_filter_base, include_filter_add
    )
    assert merged_filter == {
        CONF_EXCLUDE: {
            CONF_DOMAINS: {"homeassistant"},
            CONF_ENTITIES: {"sensor.one"},
            CONF_ENTITY_GLOBS: {"climate.*"},
        },
        CONF_INCLUDE: {
            CONF_DOMAINS: {"other", "homeassistant"},
            CONF_ENTITIES: {"not_sensor.one", "sensor.one"},
            CONF_ENTITY_GLOBS: {"climate.*", "not_climate.*"},
        },
    }
Пример #5
0
async def test_same_entity_included_excluded_include_domain_wins(
        hass, recorder_mock):
    """Test filters with domain and entities and the include domain wins."""
    filter_accept = {
        "media_player.test2",
        "media_player.test3",
        "thermostat.test",
    }
    filter_reject = {
        "thermostat.test2",
        "zone.home",
        "script.can_cancel_this_one",
    }
    conf = {
        CONF_INCLUDE: {
            CONF_DOMAINS: ["media_player"],
            CONF_ENTITIES: ["thermostat.test"],
        },
        CONF_EXCLUDE: {
            CONF_DOMAINS: ["thermostat"],
            CONF_ENTITIES: ["media_player.test"],
        },
    }

    extracted_filter = extract_include_exclude_filter_conf(conf)
    entity_filter = convert_include_exclude_filter(extracted_filter)
    sqlalchemy_filter = sqlalchemy_filter_from_include_exclude_conf(
        extracted_filter)
    assert sqlalchemy_filter is not None

    for entity_id in filter_accept:
        assert entity_filter(entity_id) is True

    for entity_id in filter_reject:
        assert entity_filter(entity_id) is False

    (
        filtered_states_entity_ids,
        filtered_events_entity_ids,
    ) = await _async_get_states_and_events_with_filter(
        hass, sqlalchemy_filter, filter_accept | filter_reject)

    assert filtered_states_entity_ids == filter_accept
    assert not filtered_states_entity_ids.intersection(filter_reject)

    assert filtered_events_entity_ids == filter_accept
    assert not filtered_events_entity_ids.intersection(filter_reject)
Пример #6
0
async def test_specificly_included_entity_always_wins(hass, recorder_mock):
    """Test specificlly included entity always wins."""
    filter_accept = {
        "media_player.test2",
        "media_player.test3",
        "thermostat.test",
        "binary_sensor.specific_include",
    }
    filter_reject = {
        "binary_sensor.test2",
        "binary_sensor.home",
        "binary_sensor.can_cancel_this_one",
    }
    conf = {
        CONF_INCLUDE: {
            CONF_ENTITIES: ["binary_sensor.specific_include"],
        },
        CONF_EXCLUDE: {
            CONF_DOMAINS: ["binary_sensor"],
            CONF_ENTITY_GLOBS: ["binary_sensor.*"],
        },
    }

    extracted_filter = extract_include_exclude_filter_conf(conf)
    entity_filter = convert_include_exclude_filter(extracted_filter)
    sqlalchemy_filter = sqlalchemy_filter_from_include_exclude_conf(
        extracted_filter)
    assert sqlalchemy_filter is not None

    for entity_id in filter_accept:
        assert entity_filter(entity_id) is True

    for entity_id in filter_reject:
        assert entity_filter(entity_id) is False

    (
        filtered_states_entity_ids,
        filtered_events_entity_ids,
    ) = await _async_get_states_and_events_with_filter(
        hass, sqlalchemy_filter, filter_accept | filter_reject)

    assert filtered_states_entity_ids == filter_accept
    assert not filtered_states_entity_ids.intersection(filter_reject)

    assert filtered_events_entity_ids == filter_accept
    assert not filtered_events_entity_ids.intersection(filter_reject)
Пример #7
0
async def test_included_and_excluded_simple_case_no_globs(hass, recorder_mock):
    """Test filters with included and excluded without globs."""
    filter_accept = {"switch.bla", "sensor.blu", "sensor.keep"}
    filter_reject = {"sensor.bli"}
    conf = {
        CONF_INCLUDE: {
            CONF_DOMAINS: ["sensor", "homeassistant"],
            CONF_ENTITIES: ["switch.bla"],
        },
        CONF_EXCLUDE: {
            CONF_DOMAINS: ["switch"],
            CONF_ENTITIES: ["sensor.bli"],
        },
    }

    extracted_filter = extract_include_exclude_filter_conf(conf)
    entity_filter = convert_include_exclude_filter(extracted_filter)
    sqlalchemy_filter = sqlalchemy_filter_from_include_exclude_conf(
        extracted_filter)
    assert sqlalchemy_filter is not None

    for entity_id in filter_accept:
        assert entity_filter(entity_id) is True

    for entity_id in filter_reject:
        assert entity_filter(entity_id) is False

    (
        filtered_states_entity_ids,
        filtered_events_entity_ids,
    ) = await _async_get_states_and_events_with_filter(
        hass, sqlalchemy_filter, filter_accept | filter_reject)

    assert filtered_states_entity_ids == filter_accept
    assert not filtered_states_entity_ids.intersection(filter_reject)

    assert filtered_events_entity_ids == filter_accept
    assert not filtered_events_entity_ids.intersection(filter_reject)
Пример #8
0
async def test_specificly_included_entity_always_wins_over_glob(
        hass, recorder_mock):
    """Test specificlly included entity always wins over a glob."""
    filter_accept = {
        "sensor.apc900va_status",
        "sensor.apc900va_battery_charge",
        "sensor.apc900va_battery_runtime",
        "sensor.apc900va_load",
        "sensor.energy_x",
    }
    filter_reject = {
        "sensor.apc900va_not_included",
    }
    conf = {
        CONF_EXCLUDE: {
            CONF_DOMAINS: [
                "updater",
                "camera",
                "group",
                "media_player",
                "script",
                "sun",
                "automation",
                "zone",
                "weblink",
                "scene",
                "calendar",
                "weather",
                "remote",
                "notify",
                "switch",
                "shell_command",
                "media_player",
            ],
            CONF_ENTITY_GLOBS: ["sensor.apc900va_*"],
        },
        CONF_INCLUDE: {
            CONF_DOMAINS: [
                "binary_sensor",
                "climate",
                "device_tracker",
                "input_boolean",
                "sensor",
            ],
            CONF_ENTITY_GLOBS: ["sensor.energy_*"],
            CONF_ENTITIES: [
                "sensor.apc900va_status",
                "sensor.apc900va_battery_charge",
                "sensor.apc900va_battery_runtime",
                "sensor.apc900va_load",
            ],
        },
    }
    extracted_filter = extract_include_exclude_filter_conf(conf)
    entity_filter = convert_include_exclude_filter(extracted_filter)
    sqlalchemy_filter = sqlalchemy_filter_from_include_exclude_conf(
        extracted_filter)
    assert sqlalchemy_filter is not None

    for entity_id in filter_accept:
        assert entity_filter(entity_id) is True

    for entity_id in filter_reject:
        assert entity_filter(entity_id) is False

    (
        filtered_states_entity_ids,
        filtered_events_entity_ids,
    ) = await _async_get_states_and_events_with_filter(
        hass, sqlalchemy_filter, filter_accept | filter_reject)

    assert filtered_states_entity_ids == filter_accept
    assert not filtered_states_entity_ids.intersection(filter_reject)

    assert filtered_events_entity_ids == filter_accept
    assert not filtered_events_entity_ids.intersection(filter_reject)
Пример #9
0
def test_extract_include_exclude_filter_conf():
    """Test we can extract a filter from configuration without altering it."""
    include_filter = extract_include_exclude_filter_conf(SIMPLE_INCLUDE_FILTER)
    assert include_filter == {
        CONF_EXCLUDE: {
            CONF_DOMAINS: set(),
            CONF_ENTITIES: set(),
            CONF_ENTITY_GLOBS: set(),
        },
        CONF_INCLUDE: {
            CONF_DOMAINS: {"homeassistant"},
            CONF_ENTITIES: {"sensor.one"},
            CONF_ENTITY_GLOBS: {"climate.*"},
        },
    }

    exclude_filter = extract_include_exclude_filter_conf(SIMPLE_EXCLUDE_FILTER)
    assert exclude_filter == {
        CONF_INCLUDE: {
            CONF_DOMAINS: set(),
            CONF_ENTITIES: set(),
            CONF_ENTITY_GLOBS: set(),
        },
        CONF_EXCLUDE: {
            CONF_DOMAINS: {"homeassistant"},
            CONF_ENTITIES: {"sensor.one"},
            CONF_ENTITY_GLOBS: {"climate.*"},
        },
    }

    include_exclude_filter = extract_include_exclude_filter_conf(
        SIMPLE_INCLUDE_EXCLUDE_FILTER
    )
    assert include_exclude_filter == {
        CONF_INCLUDE: {
            CONF_DOMAINS: {"homeassistant"},
            CONF_ENTITIES: {"sensor.one"},
            CONF_ENTITY_GLOBS: {"climate.*"},
        },
        CONF_EXCLUDE: {
            CONF_DOMAINS: {"homeassistant"},
            CONF_ENTITIES: {"sensor.one"},
            CONF_ENTITY_GLOBS: {"climate.*"},
        },
    }

    include_exclude_filter[CONF_EXCLUDE][CONF_ENTITIES] = {"cover.altered"}
    # verify it really is a copy
    assert SIMPLE_INCLUDE_EXCLUDE_FILTER[CONF_EXCLUDE][CONF_ENTITIES] != {
        "cover.altered"
    }
    empty_include_filter = extract_include_exclude_filter_conf(EMPTY_INCLUDE_FILTER)
    assert empty_include_filter == {
        CONF_EXCLUDE: {
            CONF_DOMAINS: set(),
            CONF_ENTITIES: set(),
            CONF_ENTITY_GLOBS: set(),
        },
        CONF_INCLUDE: {
            CONF_DOMAINS: set(),
            CONF_ENTITIES: set(),
            CONF_ENTITY_GLOBS: set(),
        },
    }