2019-01-28 23:52:42 +00:00
|
|
|
"""Provide a way to connect devices to one physical location."""
|
2019-03-27 14:06:20 +00:00
|
|
|
from asyncio import Event
|
2019-01-28 23:52:42 +00:00
|
|
|
from collections import OrderedDict
|
2019-12-09 15:42:10 +00:00
|
|
|
import logging
|
2020-07-22 15:06:37 +00:00
|
|
|
from typing import Dict, Iterable, List, MutableMapping, Optional, cast
|
2019-01-28 23:52:42 +00:00
|
|
|
|
|
|
|
import attr
|
|
|
|
|
|
|
|
from homeassistant.core import callback
|
|
|
|
from homeassistant.loader import bind_hass
|
2020-08-24 15:21:30 +00:00
|
|
|
import homeassistant.util.uuid as uuid_util
|
2019-03-27 14:06:20 +00:00
|
|
|
|
2019-02-07 21:34:14 +00:00
|
|
|
from .typing import HomeAssistantType
|
2019-01-28 23:52:42 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
DATA_REGISTRY = "area_registry"
|
|
|
|
EVENT_AREA_REGISTRY_UPDATED = "area_registry_updated"
|
|
|
|
STORAGE_KEY = "core.area_registry"
|
2019-01-28 23:52:42 +00:00
|
|
|
STORAGE_VERSION = 1
|
|
|
|
SAVE_DELAY = 10
|
|
|
|
|
|
|
|
|
|
|
|
@attr.s(slots=True, frozen=True)
|
|
|
|
class AreaEntry:
|
|
|
|
"""Area Registry Entry."""
|
|
|
|
|
2020-07-14 17:30:30 +00:00
|
|
|
name: Optional[str] = attr.ib(default=None)
|
2020-08-24 15:21:30 +00:00
|
|
|
id: str = attr.ib(factory=uuid_util.uuid_v1mc_hex)
|
2019-01-28 23:52:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
class AreaRegistry:
|
|
|
|
"""Class to hold a registry of areas."""
|
|
|
|
|
2019-02-07 21:34:14 +00:00
|
|
|
def __init__(self, hass: HomeAssistantType) -> None:
|
2019-01-28 23:52:42 +00:00
|
|
|
"""Initialize the area registry."""
|
|
|
|
self.hass = hass
|
2019-09-04 03:36:04 +00:00
|
|
|
self.areas: MutableMapping[str, AreaEntry] = {}
|
2019-01-28 23:52:42 +00:00
|
|
|
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
|
|
|
|
2019-03-04 17:51:12 +00:00
|
|
|
@callback
|
|
|
|
def async_get_area(self, area_id: str) -> Optional[AreaEntry]:
|
|
|
|
"""Get all areas."""
|
|
|
|
return self.areas.get(area_id)
|
|
|
|
|
2019-01-28 23:52:42 +00:00
|
|
|
@callback
|
2019-02-07 21:34:14 +00:00
|
|
|
def async_list_areas(self) -> Iterable[AreaEntry]:
|
2019-01-28 23:52:42 +00:00
|
|
|
"""Get all areas."""
|
|
|
|
return self.areas.values()
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_create(self, name: str) -> AreaEntry:
|
|
|
|
"""Create a new area."""
|
|
|
|
if self._async_is_registered(name):
|
2019-07-31 19:25:30 +00:00
|
|
|
raise ValueError("Name is already in use")
|
2019-01-28 23:52:42 +00:00
|
|
|
|
|
|
|
area = AreaEntry()
|
|
|
|
self.areas[area.id] = area
|
|
|
|
|
2019-05-08 03:04:57 +00:00
|
|
|
created = self._async_update(area.id, name=name)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
self.hass.bus.async_fire(
|
|
|
|
EVENT_AREA_REGISTRY_UPDATED, {"action": "create", "area_id": created.id}
|
|
|
|
)
|
2019-05-08 03:04:57 +00:00
|
|
|
|
|
|
|
return created
|
2019-01-28 23:52:42 +00:00
|
|
|
|
|
|
|
async def async_delete(self, area_id: str) -> None:
|
|
|
|
"""Delete area."""
|
2019-07-31 19:25:30 +00:00
|
|
|
device_registry = await self.hass.helpers.device_registry.async_get_registry()
|
2019-01-28 23:52:42 +00:00
|
|
|
device_registry.async_clear_area_id(area_id)
|
|
|
|
|
|
|
|
del self.areas[area_id]
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
self.hass.bus.async_fire(
|
|
|
|
EVENT_AREA_REGISTRY_UPDATED, {"action": "remove", "area_id": area_id}
|
|
|
|
)
|
2019-05-08 03:04:57 +00:00
|
|
|
|
2019-01-28 23:52:42 +00:00
|
|
|
self.async_schedule_save()
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_update(self, area_id: str, name: str) -> AreaEntry:
|
2019-05-08 03:04:57 +00:00
|
|
|
"""Update name of area."""
|
|
|
|
updated = self._async_update(area_id, name)
|
2019-07-31 19:25:30 +00:00
|
|
|
self.hass.bus.async_fire(
|
|
|
|
EVENT_AREA_REGISTRY_UPDATED, {"action": "update", "area_id": area_id}
|
|
|
|
)
|
2019-05-08 03:04:57 +00:00
|
|
|
return updated
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def _async_update(self, area_id: str, name: str) -> AreaEntry:
|
2019-01-28 23:52:42 +00:00
|
|
|
"""Update name of area."""
|
|
|
|
old = self.areas[area_id]
|
|
|
|
|
|
|
|
changes = {}
|
|
|
|
|
|
|
|
if name == old.name:
|
|
|
|
return old
|
|
|
|
|
|
|
|
if self._async_is_registered(name):
|
2019-07-31 19:25:30 +00:00
|
|
|
raise ValueError("Name is already in use")
|
2019-02-27 21:10:40 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
changes["name"] = name
|
2019-01-28 23:52:42 +00:00
|
|
|
|
|
|
|
new = self.areas[area_id] = attr.evolve(old, **changes)
|
|
|
|
self.async_schedule_save()
|
|
|
|
return new
|
|
|
|
|
|
|
|
@callback
|
2019-02-07 21:34:14 +00:00
|
|
|
def _async_is_registered(self, name: str) -> Optional[AreaEntry]:
|
2019-01-28 23:52:42 +00:00
|
|
|
"""Check if a name is currently registered."""
|
|
|
|
for area in self.areas.values():
|
|
|
|
if name == area.name:
|
|
|
|
return area
|
2019-02-07 21:34:14 +00:00
|
|
|
return None
|
2019-01-28 23:52:42 +00:00
|
|
|
|
|
|
|
async def async_load(self) -> None:
|
|
|
|
"""Load the area registry."""
|
|
|
|
data = await self._store.async_load()
|
|
|
|
|
2019-09-04 03:36:04 +00:00
|
|
|
areas: MutableMapping[str, AreaEntry] = OrderedDict()
|
2019-01-28 23:52:42 +00:00
|
|
|
|
|
|
|
if data is not None:
|
2019-07-31 19:25:30 +00:00
|
|
|
for area in data["areas"]:
|
|
|
|
areas[area["id"]] = AreaEntry(name=area["name"], id=area["id"])
|
2019-01-28 23:52:42 +00:00
|
|
|
|
|
|
|
self.areas = areas
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_schedule_save(self) -> None:
|
|
|
|
"""Schedule saving the area registry."""
|
|
|
|
self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
|
|
|
|
|
|
|
|
@callback
|
2020-07-22 15:06:37 +00:00
|
|
|
def _data_to_save(self) -> Dict[str, List[Dict[str, Optional[str]]]]:
|
2019-01-28 23:52:42 +00:00
|
|
|
"""Return data of area registry to store in a file."""
|
|
|
|
data = {}
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
data["areas"] = [
|
|
|
|
{"name": entry.name, "id": entry.id} for entry in self.areas.values()
|
2019-01-28 23:52:42 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
@bind_hass
|
2019-02-07 21:34:14 +00:00
|
|
|
async def async_get_registry(hass: HomeAssistantType) -> AreaRegistry:
|
2019-01-28 23:52:42 +00:00
|
|
|
"""Return area registry instance."""
|
2019-03-27 14:06:20 +00:00
|
|
|
reg_or_evt = hass.data.get(DATA_REGISTRY)
|
|
|
|
|
|
|
|
if not reg_or_evt:
|
|
|
|
evt = hass.data[DATA_REGISTRY] = Event()
|
|
|
|
|
|
|
|
reg = AreaRegistry(hass)
|
|
|
|
await reg.async_load()
|
2019-01-28 23:52:42 +00:00
|
|
|
|
2019-03-27 14:06:20 +00:00
|
|
|
hass.data[DATA_REGISTRY] = reg
|
|
|
|
evt.set()
|
|
|
|
return reg
|
2019-01-28 23:52:42 +00:00
|
|
|
|
2019-03-27 14:06:20 +00:00
|
|
|
if isinstance(reg_or_evt, Event):
|
|
|
|
evt = reg_or_evt
|
|
|
|
await evt.wait()
|
|
|
|
return cast(AreaRegistry, hass.data.get(DATA_REGISTRY))
|
2019-01-28 23:52:42 +00:00
|
|
|
|
2019-03-27 14:06:20 +00:00
|
|
|
return cast(AreaRegistry, reg_or_evt)
|