1212 lines
44 KiB
Python
1212 lines
44 KiB
Python
"""Test the Config flow for the Bayesian integration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from types import MappingProxyType
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
import voluptuous as vol
|
|
|
|
from homeassistant import config_entries
|
|
from homeassistant.components.bayesian.config_flow import (
|
|
OBSERVATION_SELECTOR,
|
|
USER,
|
|
ObservationTypes,
|
|
OptionsFlowSteps,
|
|
)
|
|
from homeassistant.components.bayesian.const import (
|
|
CONF_P_GIVEN_F,
|
|
CONF_P_GIVEN_T,
|
|
CONF_PRIOR,
|
|
CONF_PROBABILITY_THRESHOLD,
|
|
CONF_TO_STATE,
|
|
DOMAIN,
|
|
)
|
|
from homeassistant.config_entries import (
|
|
ConfigEntry,
|
|
ConfigSubentry,
|
|
ConfigSubentryDataWithId,
|
|
)
|
|
from homeassistant.const import (
|
|
CONF_ABOVE,
|
|
CONF_BELOW,
|
|
CONF_DEVICE_CLASS,
|
|
CONF_ENTITY_ID,
|
|
CONF_NAME,
|
|
CONF_PLATFORM,
|
|
CONF_STATE,
|
|
CONF_VALUE_TEMPLATE,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.data_entry_flow import FlowResultType
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
|
|
async def test_config_flow_step_user(hass: HomeAssistant) -> None:
|
|
"""Test the config flow with an example."""
|
|
with patch(
|
|
"homeassistant.components.bayesian.async_setup_entry", return_value=True
|
|
):
|
|
# Open config flow
|
|
result0 = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result0["step_id"] == USER
|
|
assert result0["type"] is FlowResultType.FORM
|
|
assert (
|
|
result0["description_placeholders"]["url"]
|
|
== "https://www.home-assistant.io/integrations/bayesian/"
|
|
)
|
|
|
|
# Enter basic settings
|
|
result1 = await hass.config_entries.flow.async_configure(
|
|
result0["flow_id"],
|
|
{
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 50,
|
|
CONF_PRIOR: 15,
|
|
CONF_DEVICE_CLASS: "occupancy",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# We move on to the next step - the observation selector
|
|
assert result1["step_id"] == OBSERVATION_SELECTOR
|
|
assert result1["type"] is FlowResultType.MENU
|
|
assert result1["flow_id"] is not None
|
|
|
|
|
|
async def test_subentry_flow(hass: HomeAssistant) -> None:
|
|
"""Test the subentry flow with a full example."""
|
|
with patch(
|
|
"homeassistant.components.bayesian.async_setup_entry", return_value=True
|
|
) as mock_setup_entry:
|
|
# Set up the initial config entry as a mock to isolate testing of subentry flows
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
data={
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 50,
|
|
CONF_PRIOR: 15,
|
|
CONF_DEVICE_CLASS: "occupancy",
|
|
},
|
|
)
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
# Open subentry flow
|
|
result = await hass.config_entries.subentries.async_init(
|
|
(config_entry.entry_id, "observation"),
|
|
context={"source": config_entries.SOURCE_USER},
|
|
)
|
|
# Confirm the next page is the observation type selector
|
|
assert result["step_id"] == "user"
|
|
assert result["type"] is FlowResultType.MENU
|
|
assert result["flow_id"] is not None
|
|
|
|
# Set up a numeric state observation first
|
|
result = await hass.config_entries.subentries.async_configure(
|
|
result["flow_id"], {"next_step_id": str(ObservationTypes.NUMERIC_STATE)}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["step_id"] == str(ObservationTypes.NUMERIC_STATE)
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
# Set up a numeric range with only 'Above'
|
|
# Also indirectly tests the conversion of proabilities to fractions
|
|
result = await hass.config_entries.subentries.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.office_illuminance_lux",
|
|
CONF_ABOVE: 40,
|
|
CONF_P_GIVEN_T: 85,
|
|
CONF_P_GIVEN_F: 45,
|
|
CONF_NAME: "Office is bright",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# Open another subentry flow
|
|
result = await hass.config_entries.subentries.async_init(
|
|
(config_entry.entry_id, "observation"),
|
|
context={"source": config_entries.SOURCE_USER},
|
|
)
|
|
assert result["step_id"] == "user"
|
|
assert result["type"] is FlowResultType.MENU
|
|
assert result["flow_id"] is not None
|
|
|
|
# Add a state observation
|
|
result = await hass.config_entries.subentries.async_configure(
|
|
result["flow_id"], {"next_step_id": str(ObservationTypes.STATE)}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["step_id"] == str(ObservationTypes.STATE)
|
|
assert result["type"] is FlowResultType.FORM
|
|
result = await hass.config_entries.subentries.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.work_laptop",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 60,
|
|
CONF_P_GIVEN_F: 20,
|
|
CONF_NAME: "Work laptop on network",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# Open another subentry flow
|
|
result = await hass.config_entries.subentries.async_init(
|
|
(config_entry.entry_id, "observation"),
|
|
context={"source": config_entries.SOURCE_USER},
|
|
)
|
|
assert result["step_id"] == "user"
|
|
assert result["type"] is FlowResultType.MENU
|
|
assert result["flow_id"] is not None
|
|
|
|
# Lastly, add a template observation
|
|
result = await hass.config_entries.subentries.async_configure(
|
|
result["flow_id"], {"next_step_id": str(ObservationTypes.TEMPLATE)}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["step_id"] == str(ObservationTypes.TEMPLATE)
|
|
assert result["type"] is FlowResultType.FORM
|
|
result = await hass.config_entries.subentries.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_VALUE_TEMPLATE: """
|
|
{% set current_time = now().time() %}
|
|
{% set start_time = strptime("07:00", "%H:%M").time() %}
|
|
{% set end_time = strptime("18:30", "%H:%M").time() %}
|
|
{% if start_time <= current_time <= end_time %}
|
|
True
|
|
{% else %}
|
|
False
|
|
{% endif %}
|
|
""",
|
|
CONF_P_GIVEN_T: 45,
|
|
CONF_P_GIVEN_F: 5,
|
|
CONF_NAME: "Daylight hours",
|
|
},
|
|
)
|
|
|
|
observations = [
|
|
dict(subentry.data) for subentry in config_entry.subentries.values()
|
|
]
|
|
# assert config_entry["version"] == 1
|
|
assert observations == [
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
|
|
CONF_ENTITY_ID: "sensor.office_illuminance_lux",
|
|
CONF_ABOVE: 40,
|
|
CONF_P_GIVEN_T: 0.85,
|
|
CONF_P_GIVEN_F: 0.45,
|
|
CONF_NAME: "Office is bright",
|
|
},
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.STATE),
|
|
CONF_ENTITY_ID: "sensor.work_laptop",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 0.6,
|
|
CONF_P_GIVEN_F: 0.2,
|
|
CONF_NAME: "Work laptop on network",
|
|
},
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.TEMPLATE),
|
|
CONF_VALUE_TEMPLATE: '{% set current_time = now().time() %}\n{% set start_time = strptime("07:00", "%H:%M").time() %}\n{% set end_time = strptime("18:30", "%H:%M").time() %}\n{% if start_time <= current_time <= end_time %}\nTrue\n{% else %}\nFalse\n{% endif %}',
|
|
CONF_P_GIVEN_T: 0.45,
|
|
CONF_P_GIVEN_F: 0.05,
|
|
CONF_NAME: "Daylight hours",
|
|
},
|
|
]
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
async def test_single_state_observation(hass: HomeAssistant) -> None:
|
|
"""Test a Bayesian sensor with just one state observation added.
|
|
|
|
This test combines the config flow for a single state observation.
|
|
"""
|
|
|
|
with patch(
|
|
"homeassistant.components.bayesian.async_setup_entry", return_value=True
|
|
) as mock_setup_entry:
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["step_id"] == USER
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_NAME: "Anyone home",
|
|
CONF_PROBABILITY_THRESHOLD: 50,
|
|
CONF_PRIOR: 66,
|
|
CONF_DEVICE_CLASS: "occupancy",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# Confirm the next step is the menu
|
|
assert result["step_id"] == OBSERVATION_SELECTOR
|
|
assert result["type"] is FlowResultType.MENU
|
|
assert result["flow_id"] is not None
|
|
assert result["menu_options"] == ["state", "numeric_state", "template"]
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": str(ObservationTypes.STATE)}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["step_id"] == str(ObservationTypes.STATE)
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.kitchen_occupancy",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 40,
|
|
CONF_P_GIVEN_F: 0.5,
|
|
CONF_NAME: "Kitchen Motion",
|
|
},
|
|
)
|
|
|
|
assert result["step_id"] == OBSERVATION_SELECTOR
|
|
assert result["type"] is FlowResultType.MENU
|
|
assert result["flow_id"] is not None
|
|
assert result["menu_options"] == [
|
|
"state",
|
|
"numeric_state",
|
|
"template",
|
|
"finish",
|
|
]
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": "finish"}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
entry_id = result["result"].entry_id
|
|
config_entry = hass.config_entries.async_get_entry(entry_id)
|
|
assert config_entry is not None
|
|
assert type(config_entry) is ConfigEntry
|
|
assert config_entry.version == 1
|
|
assert config_entry.options == {
|
|
CONF_NAME: "Anyone home",
|
|
CONF_PROBABILITY_THRESHOLD: 0.5,
|
|
CONF_PRIOR: 0.66,
|
|
CONF_DEVICE_CLASS: "occupancy",
|
|
}
|
|
assert len(config_entry.subentries) == 1
|
|
assert list(config_entry.subentries.values())[0].data == {
|
|
CONF_PLATFORM: CONF_STATE,
|
|
CONF_ENTITY_ID: "sensor.kitchen_occupancy",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 0.4,
|
|
CONF_P_GIVEN_F: 0.005,
|
|
CONF_NAME: "Kitchen Motion",
|
|
}
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
async def test_single_numeric_state_observation(hass: HomeAssistant) -> None:
|
|
"""Test a Bayesian sensor with just one numeric_state observation added.
|
|
|
|
Combines the config flow and the options flow for a single numeric_state observation.
|
|
"""
|
|
|
|
with patch(
|
|
"homeassistant.components.bayesian.async_setup_entry", return_value=True
|
|
) as mock_setup_entry:
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["step_id"] == USER
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_NAME: "Nice day",
|
|
CONF_PROBABILITY_THRESHOLD: 51,
|
|
CONF_PRIOR: 20,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# Confirm the next step is the menu
|
|
assert result["step_id"] == OBSERVATION_SELECTOR
|
|
assert result["type"] is FlowResultType.MENU
|
|
assert result["flow_id"] is not None
|
|
|
|
# select numeric state observation
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": str(ObservationTypes.NUMERIC_STATE)}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["step_id"] == str(ObservationTypes.NUMERIC_STATE)
|
|
assert result["type"] is FlowResultType.FORM
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.outside_temperature",
|
|
CONF_ABOVE: 20,
|
|
CONF_BELOW: 35,
|
|
CONF_P_GIVEN_T: 95,
|
|
CONF_P_GIVEN_F: 8,
|
|
CONF_NAME: "20 - 35 outside",
|
|
},
|
|
)
|
|
assert result["step_id"] == OBSERVATION_SELECTOR
|
|
assert result["type"] is FlowResultType.MENU
|
|
assert result["flow_id"] is not None
|
|
assert result["menu_options"] == [
|
|
"state",
|
|
"numeric_state",
|
|
"template",
|
|
"finish",
|
|
]
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": "finish"}
|
|
)
|
|
await hass.async_block_till_done()
|
|
config_entry = result["result"]
|
|
assert config_entry.options == {
|
|
CONF_NAME: "Nice day",
|
|
CONF_PROBABILITY_THRESHOLD: 0.51,
|
|
CONF_PRIOR: 0.2,
|
|
}
|
|
assert len(config_entry.subentries) == 1
|
|
assert list(config_entry.subentries.values())[0].data == {
|
|
CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
|
|
CONF_ENTITY_ID: "sensor.outside_temperature",
|
|
CONF_ABOVE: 20,
|
|
CONF_BELOW: 35,
|
|
CONF_P_GIVEN_T: 0.95,
|
|
CONF_P_GIVEN_F: 0.08,
|
|
CONF_NAME: "20 - 35 outside",
|
|
}
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
async def test_multi_numeric_state_observation(hass: HomeAssistant) -> None:
|
|
"""Test a Bayesian sensor with just more than one numeric_state observation added.
|
|
|
|
Technically a subset of the tests in test_config_flow() but may help to
|
|
narrow down errors more quickly.
|
|
"""
|
|
|
|
with patch(
|
|
"homeassistant.components.bayesian.async_setup_entry", return_value=True
|
|
) as mock_setup_entry:
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["step_id"] == USER
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_NAME: "Nice day",
|
|
CONF_PROBABILITY_THRESHOLD: 51,
|
|
CONF_PRIOR: 20,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# Confirm the next step is the menu
|
|
assert result["step_id"] == OBSERVATION_SELECTOR
|
|
assert result["type"] is FlowResultType.MENU
|
|
assert result["flow_id"] is not None
|
|
|
|
# select numeric state observation
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": str(ObservationTypes.NUMERIC_STATE)}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["step_id"] == str(ObservationTypes.NUMERIC_STATE)
|
|
assert result["type"] is FlowResultType.FORM
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.outside_temperature",
|
|
CONF_ABOVE: 20,
|
|
CONF_BELOW: 35,
|
|
CONF_P_GIVEN_T: 95,
|
|
CONF_P_GIVEN_F: 8,
|
|
CONF_NAME: "20 - 35 outside",
|
|
},
|
|
)
|
|
|
|
# Confirm the next step is the menu
|
|
assert result["step_id"] == OBSERVATION_SELECTOR
|
|
assert result["type"] is FlowResultType.MENU
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": str(ObservationTypes.NUMERIC_STATE)}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# This should fail as overlapping ranges for the same entity are not allowed
|
|
current_step = result["step_id"]
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.outside_temperature",
|
|
CONF_ABOVE: 30,
|
|
CONF_BELOW: 40,
|
|
CONF_P_GIVEN_T: 95,
|
|
CONF_P_GIVEN_F: 8,
|
|
CONF_NAME: "30 - 40 outside",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["errors"] == {"base": "overlapping_ranges"}
|
|
assert result["step_id"] == current_step
|
|
|
|
# This should fail as above should always be less than below
|
|
current_step = result["step_id"]
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.outside_temperature",
|
|
CONF_ABOVE: 40,
|
|
CONF_BELOW: 35,
|
|
CONF_P_GIVEN_T: 95,
|
|
CONF_P_GIVEN_F: 8,
|
|
CONF_NAME: "35 - 40 outside",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["step_id"] == current_step
|
|
assert result["errors"] == {"base": "above_below"}
|
|
|
|
# This should work
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.outside_temperature",
|
|
CONF_ABOVE: 35,
|
|
CONF_BELOW: 40,
|
|
CONF_P_GIVEN_T: 70,
|
|
CONF_P_GIVEN_F: 20,
|
|
CONF_NAME: "35 - 40 outside",
|
|
},
|
|
)
|
|
assert result["step_id"] == OBSERVATION_SELECTOR
|
|
assert result["type"] is FlowResultType.MENU
|
|
assert result["flow_id"] is not None
|
|
assert result["menu_options"] == [
|
|
"state",
|
|
"numeric_state",
|
|
"template",
|
|
"finish",
|
|
]
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": "finish"}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
config_entry = result["result"]
|
|
assert config_entry.version == 1
|
|
assert config_entry.options == {
|
|
CONF_NAME: "Nice day",
|
|
CONF_PROBABILITY_THRESHOLD: 0.51,
|
|
CONF_PRIOR: 0.2,
|
|
}
|
|
observations = [
|
|
dict(subentry.data) for subentry in config_entry.subentries.values()
|
|
]
|
|
assert observations == [
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
|
|
CONF_ENTITY_ID: "sensor.outside_temperature",
|
|
CONF_ABOVE: 20.0,
|
|
CONF_BELOW: 35.0,
|
|
CONF_P_GIVEN_T: 0.95,
|
|
CONF_P_GIVEN_F: 0.08,
|
|
CONF_NAME: "20 - 35 outside",
|
|
},
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
|
|
CONF_ENTITY_ID: "sensor.outside_temperature",
|
|
CONF_ABOVE: 35.0,
|
|
CONF_BELOW: 40.0,
|
|
CONF_P_GIVEN_T: 0.7,
|
|
CONF_P_GIVEN_F: 0.2,
|
|
CONF_NAME: "35 - 40 outside",
|
|
},
|
|
]
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
async def test_single_template_observation(hass: HomeAssistant) -> None:
|
|
"""Test a Bayesian sensor with just one template observation added.
|
|
|
|
Technically a subset of the tests in test_config_flow() but may help to
|
|
narrow down errors more quickly.
|
|
"""
|
|
|
|
with patch(
|
|
"homeassistant.components.bayesian.async_setup_entry", return_value=True
|
|
) as mock_setup_entry:
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["step_id"] == USER
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_NAME: "Paulus Home",
|
|
CONF_PROBABILITY_THRESHOLD: 90,
|
|
CONF_PRIOR: 50,
|
|
CONF_DEVICE_CLASS: "occupancy",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# Confirm the next step is the menu
|
|
assert result["step_id"] == OBSERVATION_SELECTOR
|
|
assert result["type"] is FlowResultType.MENU
|
|
assert result["flow_id"] is not None
|
|
|
|
# Select template observation
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": str(ObservationTypes.TEMPLATE)}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["step_id"] == str(ObservationTypes.TEMPLATE)
|
|
assert result["type"] is FlowResultType.FORM
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_VALUE_TEMPLATE: "{{is_state('device_tracker.paulus','not_home') and ((as_timestamp(now()) - as_timestamp(states.device_tracker.paulus.last_changed)) > 300)}}",
|
|
CONF_P_GIVEN_T: 5,
|
|
CONF_P_GIVEN_F: 99,
|
|
CONF_NAME: "Not seen in last 5 minutes",
|
|
},
|
|
)
|
|
assert result["step_id"] == OBSERVATION_SELECTOR
|
|
assert result["type"] is FlowResultType.MENU
|
|
assert result["flow_id"] is not None
|
|
assert result["menu_options"] == [
|
|
"state",
|
|
"numeric_state",
|
|
"template",
|
|
"finish",
|
|
]
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": "finish"}
|
|
)
|
|
await hass.async_block_till_done()
|
|
config_entry = result["result"]
|
|
assert config_entry.version == 1
|
|
assert config_entry.options == {
|
|
CONF_NAME: "Paulus Home",
|
|
CONF_PROBABILITY_THRESHOLD: 0.9,
|
|
CONF_PRIOR: 0.5,
|
|
CONF_DEVICE_CLASS: "occupancy",
|
|
}
|
|
assert len(config_entry.subentries) == 1
|
|
assert list(config_entry.subentries.values())[0].data == {
|
|
CONF_PLATFORM: str(ObservationTypes.TEMPLATE),
|
|
CONF_VALUE_TEMPLATE: "{{is_state('device_tracker.paulus','not_home') and ((as_timestamp(now()) - as_timestamp(states.device_tracker.paulus.last_changed)) > 300)}}",
|
|
CONF_P_GIVEN_T: 0.05,
|
|
CONF_P_GIVEN_F: 0.99,
|
|
CONF_NAME: "Not seen in last 5 minutes",
|
|
}
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
async def test_basic_options(hass: HomeAssistant) -> None:
|
|
"""Test reconfiguring the basic options using an options flow."""
|
|
config_entry = MockConfigEntry(
|
|
data={},
|
|
domain=DOMAIN,
|
|
options={
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 0.5,
|
|
CONF_PRIOR: 0.15,
|
|
CONF_DEVICE_CLASS: "occupancy",
|
|
},
|
|
subentries_data=[
|
|
ConfigSubentryDataWithId(
|
|
data=MappingProxyType(
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
|
|
CONF_ENTITY_ID: "sensor.office_illuminance_lux",
|
|
CONF_ABOVE: 40,
|
|
CONF_P_GIVEN_T: 0.85,
|
|
CONF_P_GIVEN_F: 0.45,
|
|
CONF_NAME: "Office is bright",
|
|
}
|
|
),
|
|
subentry_id="01JXCPHRM64Y84GQC58P5EKVHY",
|
|
subentry_type="observation",
|
|
title="Office is bright",
|
|
unique_id=None,
|
|
)
|
|
],
|
|
title="Office occupied",
|
|
)
|
|
# Setup the mock config entry
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
# Give the sensor a real value
|
|
hass.states.async_set("sensor.office_illuminance_lux", 50)
|
|
|
|
# Start the options flow
|
|
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
|
|
|
# Confirm the first page is the form for editing the basic options
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == str(OptionsFlowSteps.INIT)
|
|
|
|
# Change all possible settings (name can be changed elsewhere in the UI)
|
|
await hass.config_entries.options.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_PROBABILITY_THRESHOLD: 49,
|
|
CONF_PRIOR: 14,
|
|
CONF_DEVICE_CLASS: "presence",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# Confirm the changes stuck
|
|
assert hass.config_entries.async_get_entry(config_entry.entry_id).options == {
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 0.49,
|
|
CONF_PRIOR: 0.14,
|
|
CONF_DEVICE_CLASS: "presence",
|
|
}
|
|
assert config_entry.subentries == {
|
|
"01JXCPHRM64Y84GQC58P5EKVHY": ConfigSubentry(
|
|
data=MappingProxyType(
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
|
|
CONF_ENTITY_ID: "sensor.office_illuminance_lux",
|
|
CONF_ABOVE: 40,
|
|
CONF_P_GIVEN_T: 0.85,
|
|
CONF_P_GIVEN_F: 0.45,
|
|
CONF_NAME: "Office is bright",
|
|
}
|
|
),
|
|
subentry_id="01JXCPHRM64Y84GQC58P5EKVHY",
|
|
subentry_type="observation",
|
|
title="Office is bright",
|
|
unique_id=None,
|
|
)
|
|
}
|
|
|
|
|
|
async def test_reconfiguring_observations(hass: HomeAssistant) -> None:
|
|
"""Test editing observations through options flow, once of each of the 3 types."""
|
|
# Setup the config entry
|
|
config_entry = MockConfigEntry(
|
|
data={},
|
|
domain=DOMAIN,
|
|
options={
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 0.5,
|
|
CONF_PRIOR: 0.15,
|
|
CONF_DEVICE_CLASS: "occupancy",
|
|
},
|
|
subentries_data=[
|
|
ConfigSubentryDataWithId(
|
|
data=MappingProxyType(
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
|
|
CONF_ENTITY_ID: "sensor.office_illuminance_lux",
|
|
CONF_ABOVE: 40,
|
|
CONF_P_GIVEN_T: 0.85,
|
|
CONF_P_GIVEN_F: 0.45,
|
|
CONF_NAME: "Office is bright",
|
|
}
|
|
),
|
|
subentry_id="01JXCPHRM64Y84GQC58P5EKVHY",
|
|
subentry_type="observation",
|
|
title="Office is bright",
|
|
unique_id=None,
|
|
),
|
|
ConfigSubentryDataWithId(
|
|
data=MappingProxyType(
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.STATE),
|
|
CONF_ENTITY_ID: "sensor.work_laptop",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 0.6,
|
|
CONF_P_GIVEN_F: 0.2,
|
|
CONF_NAME: "Work laptop on network",
|
|
},
|
|
),
|
|
subentry_id="13TCPHRM64Y84GQC58P5EKTHF",
|
|
subentry_type="observation",
|
|
title="Work laptop on network",
|
|
unique_id=None,
|
|
),
|
|
ConfigSubentryDataWithId(
|
|
data=MappingProxyType(
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.TEMPLATE),
|
|
CONF_VALUE_TEMPLATE: '{% set current_time = now().time() %}\n{% set start_time = strptime("07:00", "%H:%M").time() %}\n{% set end_time = strptime("18:30", "%H:%M").time() %}\n{% if start_time <= current_time <= end_time %}\nTrue\n{% else %}\nFalse\n{% endif %}',
|
|
CONF_P_GIVEN_T: 0.45,
|
|
CONF_P_GIVEN_F: 0.05,
|
|
CONF_NAME: "Daylight hours",
|
|
}
|
|
),
|
|
subentry_id="27TCPHRM64Y84GQC58P5EIES",
|
|
subentry_type="observation",
|
|
title="Daylight hours",
|
|
unique_id=None,
|
|
),
|
|
],
|
|
title="Office occupied",
|
|
)
|
|
|
|
# Set up the mock entry
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
hass.states.async_set("sensor.office_illuminance_lux", 50)
|
|
|
|
# select a subentry for reconfiguration
|
|
result = await config_entry.start_subentry_reconfigure_flow(
|
|
hass, subentry_id="13TCPHRM64Y84GQC58P5EKTHF"
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# confirm the first page is the form for editing the observation
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
assert result["description_placeholders"]["parent_sensor_name"] == "Office occupied"
|
|
assert result["description_placeholders"]["device_class_on"] == "Detected"
|
|
assert result["description_placeholders"]["device_class_off"] == "Clear"
|
|
|
|
# Edit all settings
|
|
await hass.config_entries.subentries.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.desktop",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 70,
|
|
CONF_P_GIVEN_F: 12,
|
|
CONF_NAME: "Desktop on network",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# Confirm the changes to the state config
|
|
assert hass.config_entries.async_get_entry(config_entry.entry_id).options == {
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 0.5,
|
|
CONF_PRIOR: 0.15,
|
|
CONF_DEVICE_CLASS: "occupancy",
|
|
}
|
|
observations = [
|
|
dict(subentry.data)
|
|
for subentry in hass.config_entries.async_get_entry(
|
|
config_entry.entry_id
|
|
).subentries.values()
|
|
]
|
|
assert observations == [
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
|
|
CONF_ENTITY_ID: "sensor.office_illuminance_lux",
|
|
CONF_ABOVE: 40,
|
|
CONF_P_GIVEN_T: 0.85,
|
|
CONF_P_GIVEN_F: 0.45,
|
|
CONF_NAME: "Office is bright",
|
|
},
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.STATE),
|
|
CONF_ENTITY_ID: "sensor.desktop",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 0.7,
|
|
CONF_P_GIVEN_F: 0.12,
|
|
CONF_NAME: "Desktop on network",
|
|
},
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.TEMPLATE),
|
|
CONF_VALUE_TEMPLATE: '{% set current_time = now().time() %}\n{% set start_time = strptime("07:00", "%H:%M").time() %}\n{% set end_time = strptime("18:30", "%H:%M").time() %}\n{% if start_time <= current_time <= end_time %}\nTrue\n{% else %}\nFalse\n{% endif %}',
|
|
CONF_P_GIVEN_T: 0.45,
|
|
CONF_P_GIVEN_F: 0.05,
|
|
CONF_NAME: "Daylight hours",
|
|
},
|
|
]
|
|
|
|
# Next test editing a numeric_state observation
|
|
# select the subentry for reconfiguration
|
|
result = await config_entry.start_subentry_reconfigure_flow(
|
|
hass, subentry_id="01JXCPHRM64Y84GQC58P5EKVHY"
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# confirm the first page is the form for editing the observation
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
# Test an invalid re-configuration
|
|
# This should fail as the probabilities are equal
|
|
current_step = result["step_id"]
|
|
result = await hass.config_entries.subentries.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.office_illuminance_lumens",
|
|
CONF_ABOVE: 2000,
|
|
CONF_P_GIVEN_T: 80,
|
|
CONF_P_GIVEN_F: 80,
|
|
CONF_NAME: "Office is bright",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["step_id"] == current_step
|
|
assert result["errors"] == {"base": "equal_probabilities"}
|
|
|
|
# This should work
|
|
result = await hass.config_entries.subentries.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.office_illuminance_lumens",
|
|
CONF_ABOVE: 2000,
|
|
CONF_P_GIVEN_T: 80,
|
|
CONF_P_GIVEN_F: 40,
|
|
CONF_NAME: "Office is bright",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert "errors" not in result
|
|
|
|
# Confirm the changes to the state config
|
|
assert hass.config_entries.async_get_entry(config_entry.entry_id).options == {
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 0.5,
|
|
CONF_PRIOR: 0.15,
|
|
CONF_DEVICE_CLASS: "occupancy",
|
|
}
|
|
observations = [
|
|
dict(subentry.data)
|
|
for subentry in hass.config_entries.async_get_entry(
|
|
config_entry.entry_id
|
|
).subentries.values()
|
|
]
|
|
assert observations == [
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
|
|
CONF_ENTITY_ID: "sensor.office_illuminance_lumens",
|
|
CONF_ABOVE: 2000,
|
|
CONF_P_GIVEN_T: 0.8,
|
|
CONF_P_GIVEN_F: 0.4,
|
|
CONF_NAME: "Office is bright",
|
|
},
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.STATE),
|
|
CONF_ENTITY_ID: "sensor.desktop",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 0.7,
|
|
CONF_P_GIVEN_F: 0.12,
|
|
CONF_NAME: "Desktop on network",
|
|
},
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.TEMPLATE),
|
|
CONF_VALUE_TEMPLATE: '{% set current_time = now().time() %}\n{% set start_time = strptime("07:00", "%H:%M").time() %}\n{% set end_time = strptime("18:30", "%H:%M").time() %}\n{% if start_time <= current_time <= end_time %}\nTrue\n{% else %}\nFalse\n{% endif %}',
|
|
CONF_P_GIVEN_T: 0.45,
|
|
CONF_P_GIVEN_F: 0.05,
|
|
CONF_NAME: "Daylight hours",
|
|
},
|
|
]
|
|
|
|
# Next test editing a template observation
|
|
# select the subentry for reconfiguration
|
|
result = await config_entry.start_subentry_reconfigure_flow(
|
|
hass, subentry_id="27TCPHRM64Y84GQC58P5EIES"
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# confirm the first page is the form for editing the observation
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reconfigure"
|
|
await hass.async_block_till_done()
|
|
|
|
await hass.config_entries.subentries.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_VALUE_TEMPLATE: """
|
|
{% set current_time = now().time() %}
|
|
{% set start_time = strptime("07:00", "%H:%M").time() %}
|
|
{% set end_time = strptime("17:30", "%H:%M").time() %}
|
|
{% if start_time <= current_time <= end_time %}
|
|
True
|
|
{% else %}
|
|
False
|
|
{% endif %}
|
|
""", # changed the end_time
|
|
CONF_P_GIVEN_T: 55,
|
|
CONF_P_GIVEN_F: 13,
|
|
CONF_NAME: "Office hours",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
# Confirm the changes to the state config
|
|
assert hass.config_entries.async_get_entry(config_entry.entry_id).options == {
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 0.5,
|
|
CONF_PRIOR: 0.15,
|
|
CONF_DEVICE_CLASS: "occupancy",
|
|
}
|
|
observations = [
|
|
dict(subentry.data)
|
|
for subentry in hass.config_entries.async_get_entry(
|
|
config_entry.entry_id
|
|
).subentries.values()
|
|
]
|
|
assert observations == [
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.NUMERIC_STATE),
|
|
CONF_ENTITY_ID: "sensor.office_illuminance_lumens",
|
|
CONF_ABOVE: 2000,
|
|
CONF_P_GIVEN_T: 0.8,
|
|
CONF_P_GIVEN_F: 0.4,
|
|
CONF_NAME: "Office is bright",
|
|
},
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.STATE),
|
|
CONF_ENTITY_ID: "sensor.desktop",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 0.7,
|
|
CONF_P_GIVEN_F: 0.12,
|
|
CONF_NAME: "Desktop on network",
|
|
},
|
|
{
|
|
CONF_PLATFORM: str(ObservationTypes.TEMPLATE),
|
|
CONF_VALUE_TEMPLATE: '{% set current_time = now().time() %}\n{% set start_time = strptime("07:00", "%H:%M").time() %}\n{% set end_time = strptime("17:30", "%H:%M").time() %}\n{% if start_time <= current_time <= end_time %}\nTrue\n{% else %}\nFalse\n{% endif %}',
|
|
CONF_P_GIVEN_T: 0.55,
|
|
CONF_P_GIVEN_F: 0.13,
|
|
CONF_NAME: "Office hours",
|
|
},
|
|
]
|
|
|
|
|
|
async def test_invalid_configs(hass: HomeAssistant) -> None:
|
|
"""Test that invalid configs are refused."""
|
|
with patch(
|
|
"homeassistant.components.bayesian.async_setup_entry", return_value=True
|
|
):
|
|
result0 = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result0["step_id"] == USER
|
|
assert result0["type"] is FlowResultType.FORM
|
|
|
|
# priors should never be Zero, because then the sensor can never return 'on'
|
|
with pytest.raises(vol.Invalid) as excinfo:
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result0["flow_id"],
|
|
{
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 50,
|
|
CONF_PRIOR: 0,
|
|
},
|
|
)
|
|
assert CONF_PRIOR in excinfo.value.path
|
|
assert excinfo.value.error_message == "extreme_prior_error"
|
|
|
|
# priors should never be 100% because then the sensor can never be 'off'
|
|
with pytest.raises(vol.Invalid) as excinfo:
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result0["flow_id"],
|
|
{
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 50,
|
|
CONF_PRIOR: 100,
|
|
},
|
|
)
|
|
assert CONF_PRIOR in excinfo.value.path
|
|
assert excinfo.value.error_message == "extreme_prior_error"
|
|
|
|
# Threshold should never be 100% because then the sensor can never be 'on'
|
|
with pytest.raises(vol.Invalid) as excinfo:
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result0["flow_id"],
|
|
{
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 100,
|
|
CONF_PRIOR: 50,
|
|
},
|
|
)
|
|
assert CONF_PROBABILITY_THRESHOLD in excinfo.value.path
|
|
assert excinfo.value.error_message == "extreme_threshold_error"
|
|
|
|
# Threshold should never be 0 because then the sensor can never be 'off'
|
|
with pytest.raises(vol.Invalid) as excinfo:
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result0["flow_id"],
|
|
{
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 0,
|
|
CONF_PRIOR: 50,
|
|
},
|
|
)
|
|
assert CONF_PROBABILITY_THRESHOLD in excinfo.value.path
|
|
assert excinfo.value.error_message == "extreme_threshold_error"
|
|
|
|
# Now lets submit a valid config so we can test the observation flows
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result0["flow_id"],
|
|
{
|
|
CONF_NAME: "Office occupied",
|
|
CONF_PROBABILITY_THRESHOLD: 50,
|
|
CONF_PRIOR: 30,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result.get("errors") is None
|
|
|
|
# Confirm the next step is the menu
|
|
assert result["step_id"] == OBSERVATION_SELECTOR
|
|
assert result["type"] is FlowResultType.MENU
|
|
assert result["flow_id"] is not None
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": str(ObservationTypes.STATE)}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["step_id"] == str(ObservationTypes.STATE)
|
|
assert result["type"] is FlowResultType.FORM
|
|
|
|
# Observations with a probability of 0 will create certainties
|
|
with pytest.raises(vol.Invalid) as excinfo:
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.work_laptop",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 0,
|
|
CONF_P_GIVEN_F: 60,
|
|
CONF_NAME: "Work laptop on network",
|
|
},
|
|
)
|
|
assert CONF_P_GIVEN_T in excinfo.value.path
|
|
assert excinfo.value.error_message == "extreme_prob_given_error"
|
|
|
|
# Observations with a probability of 1 will create certainties
|
|
with pytest.raises(vol.Invalid) as excinfo:
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.work_laptop",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 60,
|
|
CONF_P_GIVEN_F: 100,
|
|
CONF_NAME: "Work laptop on network",
|
|
},
|
|
)
|
|
assert CONF_P_GIVEN_F in excinfo.value.path
|
|
assert excinfo.value.error_message == "extreme_prob_given_error"
|
|
|
|
# Observations with equal probabilities have no effect
|
|
# Try with a ObservationTypes.STATE observation
|
|
current_step = result["step_id"]
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.work_laptop",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 60,
|
|
CONF_P_GIVEN_F: 60,
|
|
CONF_NAME: "Work laptop on network",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["step_id"] == current_step
|
|
assert result["errors"] == {"base": "equal_probabilities"}
|
|
|
|
# now submit a valid result
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.work_laptop",
|
|
CONF_TO_STATE: "on",
|
|
CONF_P_GIVEN_T: 60,
|
|
CONF_P_GIVEN_F: 70,
|
|
CONF_NAME: "Work laptop on network",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": str(ObservationTypes.NUMERIC_STATE)}
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
current_step = result["step_id"]
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.office_illuminance_lux",
|
|
CONF_ABOVE: 40,
|
|
CONF_P_GIVEN_T: 85,
|
|
CONF_P_GIVEN_F: 85,
|
|
CONF_NAME: "Office is bright",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["step_id"] == current_step
|
|
assert result["errors"] == {"base": "equal_probabilities"}
|
|
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_ENTITY_ID: "sensor.office_illuminance_lux",
|
|
CONF_ABOVE: 40,
|
|
CONF_P_GIVEN_T: 85,
|
|
CONF_P_GIVEN_F: 10,
|
|
CONF_NAME: "Office is bright",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
# Try with a ObservationTypes.TEMPLATE observation
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": str(ObservationTypes.TEMPLATE)}
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
current_step = result["step_id"]
|
|
result = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"],
|
|
{
|
|
CONF_VALUE_TEMPLATE: "{{ is_state('device_tracker.paulus', 'not_home') }}",
|
|
CONF_P_GIVEN_T: 50,
|
|
CONF_P_GIVEN_F: 50,
|
|
CONF_NAME: "Paulus not home",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert result["step_id"] == current_step
|
|
assert result["errors"] == {"base": "equal_probabilities"}
|