mirror of
https://github.com/home-assistant/core.git
synced 2025-07-30 00:27:19 +00:00
Add area motion entity ID
This commit is contained in:
parent
26796f87cd
commit
7bac640267
@ -44,6 +44,7 @@ def websocket_list_areas(
|
||||
vol.Optional("humidity_entity_id"): vol.Any(str, None),
|
||||
vol.Optional("icon"): str,
|
||||
vol.Optional("labels"): [str],
|
||||
vol.Optional("motion_entity_id"): vol.Any(str, None),
|
||||
vol.Required("name"): str,
|
||||
vol.Optional("picture"): vol.Any(str, None),
|
||||
vol.Optional("temperature_entity_id"): vol.Any(str, None),
|
||||
@ -112,6 +113,7 @@ def websocket_delete_area(
|
||||
vol.Optional("humidity_entity_id"): vol.Any(str, None),
|
||||
vol.Optional("icon"): vol.Any(str, None),
|
||||
vol.Optional("labels"): [str],
|
||||
vol.Optional("motion_entity_id"): vol.Any(str, None),
|
||||
vol.Optional("name"): str,
|
||||
vol.Optional("picture"): vol.Any(str, None),
|
||||
vol.Optional("temperature_entity_id"): vol.Any(str, None),
|
||||
|
@ -40,7 +40,7 @@ EVENT_AREA_REGISTRY_UPDATED: EventType[EventAreaRegistryUpdatedData] = EventType
|
||||
)
|
||||
STORAGE_KEY = "core.area_registry"
|
||||
STORAGE_VERSION_MAJOR = 1
|
||||
STORAGE_VERSION_MINOR = 8
|
||||
STORAGE_VERSION_MINOR = 9
|
||||
|
||||
|
||||
class _AreaStoreData(TypedDict):
|
||||
@ -52,6 +52,7 @@ class _AreaStoreData(TypedDict):
|
||||
icon: str | None
|
||||
id: str
|
||||
labels: list[str]
|
||||
motion_entity_id: str | None
|
||||
name: str
|
||||
picture: str | None
|
||||
temperature_entity_id: str | None
|
||||
@ -82,6 +83,7 @@ class AreaEntry(NormalizedNameBaseRegistryEntry):
|
||||
icon: str | None
|
||||
id: str
|
||||
labels: set[str] = field(default_factory=set)
|
||||
motion_entity_id: str | None
|
||||
picture: str | None
|
||||
temperature_entity_id: str | None
|
||||
_cache: dict[str, Any] = field(default_factory=dict, compare=False, init=False)
|
||||
@ -98,6 +100,7 @@ class AreaEntry(NormalizedNameBaseRegistryEntry):
|
||||
"humidity_entity_id": self.humidity_entity_id,
|
||||
"icon": self.icon,
|
||||
"labels": list(self.labels),
|
||||
"motion_entity_id": self.motion_entity_id,
|
||||
"name": self.name,
|
||||
"picture": self.picture,
|
||||
"temperature_entity_id": self.temperature_entity_id,
|
||||
@ -157,6 +160,11 @@ class AreaRegistryStore(Store[AreasRegistryStoreData]):
|
||||
area["humidity_entity_id"] = None
|
||||
area["temperature_entity_id"] = None
|
||||
|
||||
if old_minor_version < 9:
|
||||
# Version 1.9 adds motion_entity_id
|
||||
for area_data in old_data["areas"]:
|
||||
area_data["motion_entity_id"] = None
|
||||
|
||||
if old_major_version > 1:
|
||||
raise NotImplementedError
|
||||
return old_data # type: ignore[return-value]
|
||||
@ -278,6 +286,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
humidity_entity_id: str | None = None,
|
||||
icon: str | None = None,
|
||||
labels: set[str] | None = None,
|
||||
motion_entity_id: str | None = None,
|
||||
picture: str | None = None,
|
||||
temperature_entity_id: str | None = None,
|
||||
) -> AreaEntry:
|
||||
@ -293,6 +302,9 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
if humidity_entity_id is not None:
|
||||
_validate_humidity_entity(self.hass, humidity_entity_id)
|
||||
|
||||
if motion_entity_id is not None:
|
||||
_validate_motion_entity(self.hass, motion_entity_id)
|
||||
|
||||
if temperature_entity_id is not None:
|
||||
_validate_temperature_entity(self.hass, temperature_entity_id)
|
||||
|
||||
@ -303,6 +315,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
icon=icon,
|
||||
id=self._generate_id(name),
|
||||
labels=labels or set(),
|
||||
motion_entity_id=motion_entity_id,
|
||||
name=name,
|
||||
picture=picture,
|
||||
temperature_entity_id=temperature_entity_id,
|
||||
@ -345,6 +358,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
humidity_entity_id: str | None | UndefinedType = UNDEFINED,
|
||||
icon: str | None | UndefinedType = UNDEFINED,
|
||||
labels: set[str] | UndefinedType = UNDEFINED,
|
||||
motion_entity_id: str | None | UndefinedType = UNDEFINED,
|
||||
name: str | UndefinedType = UNDEFINED,
|
||||
picture: str | None | UndefinedType = UNDEFINED,
|
||||
temperature_entity_id: str | None | UndefinedType = UNDEFINED,
|
||||
@ -357,6 +371,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
humidity_entity_id=humidity_entity_id,
|
||||
icon=icon,
|
||||
labels=labels,
|
||||
motion_entity_id=motion_entity_id,
|
||||
name=name,
|
||||
picture=picture,
|
||||
temperature_entity_id=temperature_entity_id,
|
||||
@ -381,6 +396,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
humidity_entity_id: str | None | UndefinedType = UNDEFINED,
|
||||
icon: str | None | UndefinedType = UNDEFINED,
|
||||
labels: set[str] | UndefinedType = UNDEFINED,
|
||||
motion_entity_id: str | None | UndefinedType = UNDEFINED,
|
||||
name: str | UndefinedType = UNDEFINED,
|
||||
picture: str | None | UndefinedType = UNDEFINED,
|
||||
temperature_entity_id: str | None | UndefinedType = UNDEFINED,
|
||||
@ -396,6 +412,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
("humidity_entity_id", humidity_entity_id),
|
||||
("icon", icon),
|
||||
("labels", labels),
|
||||
("motion_entity_id", motion_entity_id),
|
||||
("picture", picture),
|
||||
("temperature_entity_id", temperature_entity_id),
|
||||
)
|
||||
@ -405,6 +422,9 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
if "humidity_entity_id" in new_values and humidity_entity_id is not None:
|
||||
_validate_humidity_entity(self.hass, new_values["humidity_entity_id"])
|
||||
|
||||
if "motion_entity_id" in new_values and motion_entity_id is not None:
|
||||
_validate_motion_entity(self.hass, new_values["motion_entity_id"])
|
||||
|
||||
if "temperature_entity_id" in new_values and temperature_entity_id is not None:
|
||||
_validate_temperature_entity(self.hass, new_values["temperature_entity_id"])
|
||||
|
||||
@ -440,6 +460,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
icon=area["icon"],
|
||||
id=area["id"],
|
||||
labels=set(area["labels"]),
|
||||
motion_entity_id=area["motion_entity_id"],
|
||||
name=area["name"],
|
||||
picture=area["picture"],
|
||||
temperature_entity_id=area["temperature_entity_id"],
|
||||
@ -462,6 +483,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
"icon": entry.icon,
|
||||
"id": entry.id,
|
||||
"labels": list(entry.labels),
|
||||
"motion_entity_id": entry.motion_entity_id,
|
||||
"name": entry.name,
|
||||
"picture": entry.picture,
|
||||
"temperature_entity_id": entry.temperature_entity_id,
|
||||
@ -569,3 +591,18 @@ def _validate_humidity_entity(hass: HomeAssistant, entity_id: str) -> None:
|
||||
or state.attributes.get(ATTR_DEVICE_CLASS) != SensorDeviceClass.HUMIDITY
|
||||
):
|
||||
raise ValueError(f"Entity {entity_id} is not a humidity sensor")
|
||||
|
||||
|
||||
def _validate_motion_entity(hass: HomeAssistant, entity_id: str) -> None:
|
||||
"""Validate motion entity."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
|
||||
if not (state := hass.states.get(entity_id)):
|
||||
raise ValueError(f"Entity {entity_id} does not exist")
|
||||
|
||||
if (
|
||||
state.domain != "binary_sensor"
|
||||
or state.attributes.get(ATTR_DEVICE_CLASS) != BinarySensorDeviceClass.MOTION
|
||||
):
|
||||
raise ValueError(f"Entity {entity_id} is not a motion binary_sensor")
|
||||
|
@ -6,6 +6,7 @@ from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from pytest_unordered import unordered
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.config import area_registry
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.const import (
|
||||
@ -50,6 +51,13 @@ async def mock_temperature_humidity_entity(hass: HomeAssistant) -> None:
|
||||
ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE,
|
||||
},
|
||||
)
|
||||
hass.states.async_set(
|
||||
"binary_sensor.mock_motion",
|
||||
"off",
|
||||
{
|
||||
ATTR_DEVICE_CLASS: BinarySensorDeviceClass.MOTION,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def test_list_areas(
|
||||
@ -72,6 +80,7 @@ async def test_list_areas(
|
||||
humidity_entity_id="sensor.mock_humidity",
|
||||
icon="mdi:garage",
|
||||
labels={"label_1", "label_2"},
|
||||
motion_entity_id="binary_sensor.mock_motion",
|
||||
picture="/image/example.png",
|
||||
temperature_entity_id="sensor.mock_temperature",
|
||||
)
|
||||
@ -92,6 +101,7 @@ async def test_list_areas(
|
||||
"name": "mock 1",
|
||||
"picture": None,
|
||||
"temperature_entity_id": None,
|
||||
"motion_entity_id": None,
|
||||
},
|
||||
{
|
||||
"aliases": unordered(["alias_1", "alias_2"]),
|
||||
@ -102,6 +112,7 @@ async def test_list_areas(
|
||||
"icon": "mdi:garage",
|
||||
"labels": unordered(["label_1", "label_2"]),
|
||||
"modified_at": created_area2.timestamp(),
|
||||
"motion_entity_id": "binary_sensor.mock_motion",
|
||||
"name": "mock 2",
|
||||
"picture": "/image/example.png",
|
||||
"temperature_entity_id": "sensor.mock_temperature",
|
||||
@ -135,6 +146,7 @@ async def test_create_area(
|
||||
"modified_at": utcnow().timestamp(),
|
||||
"temperature_entity_id": None,
|
||||
"humidity_entity_id": None,
|
||||
"motion_entity_id": None,
|
||||
}
|
||||
assert len(area_registry.areas) == 1
|
||||
|
||||
@ -149,6 +161,7 @@ async def test_create_area(
|
||||
"picture": "/image/example.png",
|
||||
"temperature_entity_id": "sensor.mock_temperature",
|
||||
"humidity_entity_id": "sensor.mock_humidity",
|
||||
"motion_entity_id": "binary_sensor.mock_motion",
|
||||
"type": "config/area_registry/create",
|
||||
}
|
||||
)
|
||||
@ -168,6 +181,7 @@ async def test_create_area(
|
||||
"modified_at": utcnow().timestamp(),
|
||||
"temperature_entity_id": "sensor.mock_temperature",
|
||||
"humidity_entity_id": "sensor.mock_humidity",
|
||||
"motion_entity_id": "binary_sensor.mock_motion",
|
||||
}
|
||||
assert len(area_registry.areas) == 2
|
||||
|
||||
@ -246,6 +260,7 @@ async def test_update_area(
|
||||
"humidity_entity_id": "sensor.mock_humidity",
|
||||
"icon": "mdi:garage",
|
||||
"labels": ["label_1", "label_2"],
|
||||
"motion_entity_id": "binary_sensor.mock_motion",
|
||||
"name": "mock 2",
|
||||
"picture": "/image/example.png",
|
||||
"temperature_entity_id": "sensor.mock_temperature",
|
||||
@ -261,6 +276,7 @@ async def test_update_area(
|
||||
"humidity_entity_id": "sensor.mock_humidity",
|
||||
"icon": "mdi:garage",
|
||||
"labels": unordered(["label_1", "label_2"]),
|
||||
"motion_entity_id": "binary_sensor.mock_motion",
|
||||
"name": "mock 2",
|
||||
"picture": "/image/example.png",
|
||||
"temperature_entity_id": "sensor.mock_temperature",
|
||||
@ -281,6 +297,7 @@ async def test_update_area(
|
||||
"humidity_entity_id": None,
|
||||
"icon": None,
|
||||
"labels": [],
|
||||
"motion_entity_id": None,
|
||||
"picture": None,
|
||||
"temperature_entity_id": None,
|
||||
}
|
||||
@ -298,6 +315,7 @@ async def test_update_area(
|
||||
"picture": None,
|
||||
"temperature_entity_id": None,
|
||||
"humidity_entity_id": None,
|
||||
"motion_entity_id": None,
|
||||
"created_at": created_at.timestamp(),
|
||||
"modified_at": modified_at.timestamp(),
|
||||
}
|
||||
|
@ -44,6 +44,13 @@ async def mock_temperature_humidity_entity(hass: HomeAssistant) -> None:
|
||||
ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE,
|
||||
},
|
||||
)
|
||||
hass.states.async_set(
|
||||
"binary_sensor.mock_motion",
|
||||
"off",
|
||||
{
|
||||
ATTR_DEVICE_CLASS: "motion",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def test_list_areas(area_registry: ar.AreaRegistry) -> None:
|
||||
@ -79,6 +86,7 @@ async def test_create_area(
|
||||
modified_at=utcnow(),
|
||||
temperature_entity_id=None,
|
||||
humidity_entity_id=None,
|
||||
motion_entity_id=None,
|
||||
)
|
||||
assert len(area_registry.areas) == 1
|
||||
|
||||
@ -100,6 +108,7 @@ async def test_create_area(
|
||||
picture="/image/example.png",
|
||||
temperature_entity_id="sensor.mock_temperature",
|
||||
humidity_entity_id="sensor.mock_humidity",
|
||||
motion_entity_id="binary_sensor.mock_motion",
|
||||
)
|
||||
|
||||
assert area2 == ar.AreaEntry(
|
||||
@ -114,6 +123,7 @@ async def test_create_area(
|
||||
modified_at=utcnow(),
|
||||
temperature_entity_id="sensor.mock_temperature",
|
||||
humidity_entity_id="sensor.mock_humidity",
|
||||
motion_entity_id="binary_sensor.mock_motion",
|
||||
)
|
||||
assert len(area_registry.areas) == 2
|
||||
assert area.created_at != area2.created_at
|
||||
@ -222,6 +232,7 @@ async def test_update_area(
|
||||
picture="/image/example.png",
|
||||
temperature_entity_id="sensor.mock_temperature",
|
||||
humidity_entity_id="sensor.mock_humidity",
|
||||
motion_entity_id="binary_sensor.mock_motion",
|
||||
)
|
||||
|
||||
assert updated_area != area
|
||||
@ -237,6 +248,7 @@ async def test_update_area(
|
||||
modified_at=modified_at,
|
||||
temperature_entity_id="sensor.mock_temperature",
|
||||
humidity_entity_id="sensor.mock_humidity",
|
||||
motion_entity_id="binary_sensor.mock_motion",
|
||||
)
|
||||
assert len(area_registry.areas) == 1
|
||||
|
||||
@ -341,6 +353,18 @@ async def test_update_area_with_normalized_name_already_in_use(
|
||||
{"humidity_entity_id": "sensor.random"},
|
||||
"Entity sensor.random is not a humidity sensor",
|
||||
),
|
||||
(
|
||||
{"motion_entity_id": "sensor.invalid"},
|
||||
"Entity sensor.invalid does not exist",
|
||||
),
|
||||
(
|
||||
{"motion_entity_id": "light.kitchen"},
|
||||
"Entity light.kitchen is not a motion binary_sensor",
|
||||
),
|
||||
(
|
||||
{"motion_entity_id": "binary_sensor.random"},
|
||||
"Entity binary_sensor.random is not a motion binary_sensor",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_update_area_entity_validation(
|
||||
@ -354,6 +378,7 @@ async def test_update_area_entity_validation(
|
||||
area = area_registry.async_create("mock")
|
||||
hass.states.async_set("light.kitchen", "on", {})
|
||||
hass.states.async_set("sensor.random", "3", {})
|
||||
hass.states.async_set("binary_sensor.random", "off", {})
|
||||
|
||||
with pytest.raises(ValueError) as e_info:
|
||||
area_registry.async_update(area.id, **create_kwargs)
|
||||
@ -406,6 +431,7 @@ async def test_loading_area_from_storage(
|
||||
"modified_at": modified_at.isoformat(),
|
||||
"temperature_entity_id": "sensor.mock_temperature",
|
||||
"humidity_entity_id": "sensor.mock_humidity",
|
||||
"motion_entity_id": "binary_sensor.mock_motion",
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -428,6 +454,7 @@ async def test_loading_area_from_storage(
|
||||
modified_at=modified_at,
|
||||
temperature_entity_id="sensor.mock_temperature",
|
||||
humidity_entity_id="sensor.mock_humidity",
|
||||
motion_entity_id="binary_sensor.mock_motion",
|
||||
)
|
||||
|
||||
|
||||
@ -468,6 +495,7 @@ async def test_migration_from_1_1(
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"temperature_entity_id": None,
|
||||
"humidity_entity_id": None,
|
||||
"motion_entity_id": None,
|
||||
}
|
||||
]
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user