mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 04:37:06 +00:00
Migrate zone to use collection helpers (#30774)
This commit is contained in:
parent
4c27d6b9aa
commit
0fba9e44ed
@ -271,8 +271,17 @@ async def async_handle_waypoint(hass, name_base, waypoint):
|
|||||||
return
|
return
|
||||||
|
|
||||||
zone = zone_comp.Zone(
|
zone = zone_comp.Zone(
|
||||||
hass, pretty_name, lat, lon, rad, zone_comp.ICON_IMPORT, False
|
{
|
||||||
|
zone_comp.CONF_NAME: pretty_name,
|
||||||
|
zone_comp.CONF_LATITUDE: lat,
|
||||||
|
zone_comp.CONF_LONGITUDE: lon,
|
||||||
|
zone_comp.CONF_RADIUS: rad,
|
||||||
|
zone_comp.CONF_ICON: zone_comp.ICON_IMPORT,
|
||||||
|
zone_comp.CONF_PASSIVE: False,
|
||||||
|
},
|
||||||
|
False,
|
||||||
)
|
)
|
||||||
|
zone.hass = hass
|
||||||
zone.entity_id = entity_id
|
zone.entity_id = entity_id
|
||||||
await zone.async_update_ha_state()
|
await zone.async_update_ha_state()
|
||||||
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "\u0418\u043c\u0435\u0442\u043e \u0432\u0435\u0447\u0435 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "\u0418\u043a\u043e\u043d\u0430",
|
|
||||||
"latitude": "\u0428\u0438\u0440\u0438\u043d\u0430",
|
|
||||||
"longitude": "\u0414\u044a\u043b\u0436\u0438\u043d\u0430",
|
|
||||||
"name": "\u0418\u043c\u0435",
|
|
||||||
"passive": "\u041f\u0430\u0441\u0438\u0432\u043d\u0430",
|
|
||||||
"radius": "\u0420\u0430\u0434\u0438\u0443\u0441"
|
|
||||||
},
|
|
||||||
"title": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0442\u0435 \u043d\u0430 \u0437\u043e\u043d\u0430\u0442\u0430"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "\u0417\u043e\u043d\u0430"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "El nom ja existeix"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Icona",
|
|
||||||
"latitude": "Latitud",
|
|
||||||
"longitude": "Longitud",
|
|
||||||
"name": "Nom",
|
|
||||||
"passive": "Passiu",
|
|
||||||
"radius": "Radi"
|
|
||||||
},
|
|
||||||
"title": "Definici\u00f3 dels par\u00e0metres de la zona"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zona"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "N\u00e1zev ji\u017e existuje"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ikona",
|
|
||||||
"latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka",
|
|
||||||
"longitude": "Zem\u011bpisn\u00e1 d\u00e9lka",
|
|
||||||
"name": "N\u00e1zev",
|
|
||||||
"passive": "Pasivn\u00ed",
|
|
||||||
"radius": "Polom\u011br"
|
|
||||||
},
|
|
||||||
"title": "Definujte parametry z\u00f3ny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Z\u00f3na"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Enw eisoes yn bodoli"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Eicon",
|
|
||||||
"latitude": "Lledred",
|
|
||||||
"longitude": "Hydred",
|
|
||||||
"name": "Enw",
|
|
||||||
"passive": "Goddefol",
|
|
||||||
"radius": "Radiws"
|
|
||||||
},
|
|
||||||
"title": "Ddiffinio paramedrau parth"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Parth"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Navnet findes allerede"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ikon",
|
|
||||||
"latitude": "Breddegrad",
|
|
||||||
"longitude": "L\u00e6ngdegrad",
|
|
||||||
"name": "Navn",
|
|
||||||
"passive": "Passiv",
|
|
||||||
"radius": "Radius"
|
|
||||||
},
|
|
||||||
"title": "Definer zoneparametre"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zone"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Name existiert bereits"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Symbol",
|
|
||||||
"latitude": "Breitengrad",
|
|
||||||
"longitude": "L\u00e4ngengrad",
|
|
||||||
"name": "Name",
|
|
||||||
"passive": "Passiv",
|
|
||||||
"radius": "Radius"
|
|
||||||
},
|
|
||||||
"title": "Definiere die Zonenparameter"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zone"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Name already exists"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Icon",
|
|
||||||
"latitude": "Latitude",
|
|
||||||
"longitude": "Longitude",
|
|
||||||
"name": "Name",
|
|
||||||
"passive": "Passive",
|
|
||||||
"radius": "Radius"
|
|
||||||
},
|
|
||||||
"title": "Define zone parameters"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zone"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "El nombre ya existe"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Icono",
|
|
||||||
"latitude": "Latitud",
|
|
||||||
"longitude": "Longitud",
|
|
||||||
"name": "Nombre",
|
|
||||||
"passive": "Pasivo",
|
|
||||||
"radius": "Radio"
|
|
||||||
},
|
|
||||||
"title": "Definir par\u00e1metros de zona"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zona"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "El nombre ya existe"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Icono",
|
|
||||||
"latitude": "Latitud",
|
|
||||||
"longitude": "Longitud",
|
|
||||||
"name": "Nombre",
|
|
||||||
"passive": "Pasivo",
|
|
||||||
"radius": "Radio"
|
|
||||||
},
|
|
||||||
"title": "Definir par\u00e1metros de la zona"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zona"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ikoon",
|
|
||||||
"latitude": "Laius",
|
|
||||||
"longitude": "Pikkus",
|
|
||||||
"name": "Nimi",
|
|
||||||
"radius": "Raadius"
|
|
||||||
},
|
|
||||||
"title": "M\u00e4\u00e4ra tsooni parameetrid"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Ce nom est d\u00e9j\u00e0 utilis\u00e9"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ic\u00f4ne",
|
|
||||||
"latitude": "Latitude",
|
|
||||||
"longitude": "Longitude",
|
|
||||||
"name": "Nom",
|
|
||||||
"passive": "Passif",
|
|
||||||
"radius": "Rayon"
|
|
||||||
},
|
|
||||||
"title": "D\u00e9finir les param\u00e8tres de la zone"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zone"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "\u05d4\u05e9\u05dd \u05db\u05d1\u05e8 \u05e7\u05d9\u05d9\u05dd"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "\u05e1\u05de\u05dc",
|
|
||||||
"latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1",
|
|
||||||
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da",
|
|
||||||
"name": "\u05e9\u05dd",
|
|
||||||
"passive": "\u05e4\u05e1\u05d9\u05d1\u05d9",
|
|
||||||
"radius": "\u05e8\u05d3\u05d9\u05d5\u05e1"
|
|
||||||
},
|
|
||||||
"title": "\u05d4\u05d2\u05d3\u05e8 \u05e4\u05e8\u05de\u05d8\u05e8\u05d9\u05dd \u05e9\u05dc \u05d0\u05d6\u05d5\u05e8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "\u05d0\u05d6\u05d5\u05e8"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Ime ve\u0107 postoji"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ikona",
|
|
||||||
"latitude": "Zemljopisna \u0161irina",
|
|
||||||
"longitude": "Zemljopisna du\u017eina",
|
|
||||||
"name": "Ime",
|
|
||||||
"passive": "Pasivno",
|
|
||||||
"radius": "Radijus"
|
|
||||||
},
|
|
||||||
"title": "Definirajte parametre zone"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zona"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "A n\u00e9v m\u00e1r l\u00e9tezik"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ikon",
|
|
||||||
"latitude": "Sz\u00e9less\u00e9g",
|
|
||||||
"longitude": "Hossz\u00fas\u00e1g",
|
|
||||||
"name": "N\u00e9v",
|
|
||||||
"passive": "Passz\u00edv",
|
|
||||||
"radius": "Sug\u00e1r"
|
|
||||||
},
|
|
||||||
"title": "Z\u00f3na param\u00e9terek megad\u00e1sa"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Z\u00f3na"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Nama sudah ada"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ikon",
|
|
||||||
"latitude": "Lintang",
|
|
||||||
"longitude": "Garis bujur",
|
|
||||||
"name": "Nama",
|
|
||||||
"passive": "Pasif",
|
|
||||||
"radius": "Radius"
|
|
||||||
},
|
|
||||||
"title": "Tentukan parameter zona"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zona"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Il nome \u00e8 gi\u00e0 esistente"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Icona",
|
|
||||||
"latitude": "Latitudine",
|
|
||||||
"longitude": "Longitudine",
|
|
||||||
"name": "Nome",
|
|
||||||
"passive": "Passiva",
|
|
||||||
"radius": "Raggio"
|
|
||||||
},
|
|
||||||
"title": "Imposta i parametri della zona"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zona"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"latitude": "\u7def\u5ea6",
|
|
||||||
"longitude": "\u7d4c\u5ea6",
|
|
||||||
"name": "\u540d\u524d"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "\uc774\ub984\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "\uc544\uc774\ucf58",
|
|
||||||
"latitude": "\uc704\ub3c4",
|
|
||||||
"longitude": "\uacbd\ub3c4",
|
|
||||||
"name": "\uc774\ub984",
|
|
||||||
"passive": "\uc790\ub3d9\ud654 \uc804\uc6a9",
|
|
||||||
"radius": "\ubc18\uacbd"
|
|
||||||
},
|
|
||||||
"title": "\uad6c\uc5ed \uc124\uc815"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "\uad6c\uc5ed"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Numm g\u00ebtt et schonn"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ikone",
|
|
||||||
"latitude": "Breedegrad",
|
|
||||||
"longitude": "L\u00e4ngegrad",
|
|
||||||
"name": "Numm",
|
|
||||||
"passive": "Passif",
|
|
||||||
"radius": "Radius"
|
|
||||||
},
|
|
||||||
"title": "D\u00e9fin\u00e9iert Zone Parameter"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zone"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Naam bestaat al"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Pictogram",
|
|
||||||
"latitude": "Breedtegraad",
|
|
||||||
"longitude": "Lengtegraad",
|
|
||||||
"name": "Naam",
|
|
||||||
"passive": "Passief",
|
|
||||||
"radius": "Straal"
|
|
||||||
},
|
|
||||||
"title": "Definieer zone parameters"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zone"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Namnet eksisterar allereie"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ikon",
|
|
||||||
"latitude": "Breiddegrad",
|
|
||||||
"longitude": "Lengdegrad",
|
|
||||||
"name": "Namn",
|
|
||||||
"passive": "Passiv",
|
|
||||||
"radius": "Radius"
|
|
||||||
},
|
|
||||||
"title": "Definer soneparameterar"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Sone"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Navnet eksisterer allerede"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ikon",
|
|
||||||
"latitude": "Breddegrad",
|
|
||||||
"longitude": "Lengdegrad",
|
|
||||||
"name": "Navn",
|
|
||||||
"passive": "Passiv",
|
|
||||||
"radius": "Radius"
|
|
||||||
},
|
|
||||||
"title": "Definer sone parametere"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Sone"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Nazwa ju\u017c istnieje"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ikona",
|
|
||||||
"latitude": "Szeroko\u015b\u0107 geograficzna",
|
|
||||||
"longitude": "D\u0142ugo\u015b\u0107 geograficzna",
|
|
||||||
"name": "Nazwa",
|
|
||||||
"passive": "Pasywnie",
|
|
||||||
"radius": "Promie\u0144"
|
|
||||||
},
|
|
||||||
"title": "Zdefiniuj parametry strefy"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Strefa"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "O nome j\u00e1 existe"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "\u00cdcone",
|
|
||||||
"latitude": "Latitude",
|
|
||||||
"longitude": "Longitude",
|
|
||||||
"name": "Nome",
|
|
||||||
"passive": "Passivo",
|
|
||||||
"radius": "Raio"
|
|
||||||
},
|
|
||||||
"title": "Definir par\u00e2metros da zona"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zona"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Nome j\u00e1 existente"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "\u00cdcone",
|
|
||||||
"latitude": "Latitude",
|
|
||||||
"longitude": "Longitude",
|
|
||||||
"name": "Nome",
|
|
||||||
"passive": "Passivo",
|
|
||||||
"radius": "Raio"
|
|
||||||
},
|
|
||||||
"title": "Definir os par\u00e2metros da zona"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zona"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "\u042d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f."
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "\u0417\u043d\u0430\u0447\u043e\u043a",
|
|
||||||
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
|
|
||||||
"longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430",
|
|
||||||
"name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435",
|
|
||||||
"passive": "\u041f\u0430\u0441\u0441\u0438\u0432\u043d\u0430\u044f",
|
|
||||||
"radius": "\u0420\u0430\u0434\u0438\u0443\u0441"
|
|
||||||
},
|
|
||||||
"title": "\u0417\u043e\u043d\u0430"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "\u0417\u043e\u043d\u0430"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Ime \u017ee obstaja"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ikona",
|
|
||||||
"latitude": "Zemljepisna \u0161irina",
|
|
||||||
"longitude": "Zemljepisna dol\u017eina",
|
|
||||||
"name": "Ime",
|
|
||||||
"passive": "Pasivno",
|
|
||||||
"radius": "Radij"
|
|
||||||
},
|
|
||||||
"title": "Dolo\u010dite parametre obmo\u010dja"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Obmo\u010dje"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Namnet finns redan"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Ikon",
|
|
||||||
"latitude": "Latitud",
|
|
||||||
"longitude": "Longitud",
|
|
||||||
"name": "Namn",
|
|
||||||
"passive": "Passiv",
|
|
||||||
"radius": "Radie"
|
|
||||||
},
|
|
||||||
"title": "Definiera zonparametrar"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Zon"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "\u0e21\u0e35\u0e0a\u0e37\u0e48\u0e2d\u0e19\u0e35\u0e49\u0e2d\u0e22\u0e39\u0e48\u0e41\u0e25\u0e49\u0e27"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"latitude": "\u0e40\u0e2a\u0e49\u0e19\u0e23\u0e38\u0e49\u0e07",
|
|
||||||
"longitude": "\u0e40\u0e2a\u0e49\u0e19\u0e41\u0e27\u0e07",
|
|
||||||
"name": "\u0e0a\u0e37\u0e48\u0e2d"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "\u0e42\u0e0b\u0e19"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "\u0406\u043c'\u044f \u0432\u0436\u0435 \u0456\u0441\u043d\u0443\u0454"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "\u0406\u043a\u043e\u043d\u043a\u0430",
|
|
||||||
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
|
|
||||||
"longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430",
|
|
||||||
"name": "\u041d\u0430\u0437\u0432\u0430",
|
|
||||||
"passive": "\u041f\u0430\u0441\u0438\u0432\u043d\u0438\u0439",
|
|
||||||
"radius": "\u0420\u0430\u0434\u0456\u0443\u0441"
|
|
||||||
},
|
|
||||||
"title": "\u0412\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 \u0437\u043e\u043d\u0438"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "\u0417\u043e\u043d\u0430"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "T\u00ean \u0111\u00e3 t\u1ed3n t\u1ea1i"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "Bi\u1ec3u t\u01b0\u1ee3ng",
|
|
||||||
"latitude": "V\u0129 \u0111\u1ed9",
|
|
||||||
"longitude": "Kinh \u0111\u1ed9",
|
|
||||||
"name": "T\u00ean",
|
|
||||||
"passive": "Th\u1ee5 \u0111\u1ed9ng",
|
|
||||||
"radius": "B\u00e1n k\u00ednh"
|
|
||||||
},
|
|
||||||
"title": "X\u00e1c \u0111\u1ecbnh tham s\u1ed1 v\u00f9ng"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "V\u00f9ng"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "\u540d\u79f0\u5df2\u5b58\u5728"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "\u56fe\u6807",
|
|
||||||
"latitude": "\u7eac\u5ea6",
|
|
||||||
"longitude": "\u7ecf\u5ea6",
|
|
||||||
"name": "\u540d\u79f0",
|
|
||||||
"passive": "\u88ab\u52a8",
|
|
||||||
"radius": "\u534a\u5f84"
|
|
||||||
},
|
|
||||||
"title": "\u5b9a\u4e49\u533a\u57df\u76f8\u5173\u53d8\u91cf"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "\u533a\u57df"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"error": {
|
|
||||||
"name_exists": "\u8a72\u540d\u7a31\u5df2\u5b58\u5728"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"data": {
|
|
||||||
"icon": "\u5716\u793a",
|
|
||||||
"latitude": "\u7def\u5ea6",
|
|
||||||
"longitude": "\u7d93\u5ea6",
|
|
||||||
"name": "\u540d\u7a31",
|
|
||||||
"passive": "\u88ab\u52d5",
|
|
||||||
"radius": "\u534a\u5f91"
|
|
||||||
},
|
|
||||||
"title": "\u5b9a\u7fa9\u5340\u57df\u53c3\u6578"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "\u5340\u57df"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +1,41 @@
|
|||||||
"""Support for the definition of zones."""
|
"""Support for the definition of zones."""
|
||||||
import logging
|
import logging
|
||||||
from typing import Set, cast
|
from typing import Dict, List, Optional, cast
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
ATTR_EDITABLE,
|
||||||
|
ATTR_HIDDEN,
|
||||||
ATTR_LATITUDE,
|
ATTR_LATITUDE,
|
||||||
ATTR_LONGITUDE,
|
ATTR_LONGITUDE,
|
||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
|
CONF_ID,
|
||||||
CONF_LATITUDE,
|
CONF_LATITUDE,
|
||||||
CONF_LONGITUDE,
|
CONF_LONGITUDE,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_RADIUS,
|
CONF_RADIUS,
|
||||||
EVENT_CORE_CONFIG_UPDATE,
|
EVENT_CORE_CONFIG_UPDATE,
|
||||||
|
SERVICE_RELOAD,
|
||||||
|
)
|
||||||
|
from homeassistant.core import Event, HomeAssistant, ServiceCall, State, callback
|
||||||
|
from homeassistant.helpers import (
|
||||||
|
collection,
|
||||||
|
config_validation as cv,
|
||||||
|
entity,
|
||||||
|
entity_component,
|
||||||
|
entity_registry,
|
||||||
|
service,
|
||||||
|
storage,
|
||||||
)
|
)
|
||||||
from homeassistant.core import State, callback
|
|
||||||
from homeassistant.helpers import config_per_platform
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
from homeassistant.helpers.entity import async_generate_entity_id
|
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
from homeassistant.util import slugify
|
|
||||||
from homeassistant.util.location import distance
|
from homeassistant.util.location import distance
|
||||||
|
|
||||||
from .config_flow import configured_zones
|
|
||||||
from .const import ATTR_PASSIVE, ATTR_RADIUS, CONF_PASSIVE, DOMAIN, HOME_ZONE
|
from .const import ATTR_PASSIVE, ATTR_RADIUS, CONF_PASSIVE, DOMAIN, HOME_ZONE
|
||||||
from .zone import Zone
|
|
||||||
|
|
||||||
# mypy: allow-untyped-calls, allow-untyped-defs
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_NAME = "Unnamed zone"
|
|
||||||
DEFAULT_PASSIVE = False
|
DEFAULT_PASSIVE = False
|
||||||
DEFAULT_RADIUS = 100
|
DEFAULT_RADIUS = 100
|
||||||
|
|
||||||
@ -40,29 +45,47 @@ ENTITY_ID_HOME = ENTITY_ID_FORMAT.format(HOME_ZONE)
|
|||||||
ICON_HOME = "mdi:home"
|
ICON_HOME = "mdi:home"
|
||||||
ICON_IMPORT = "mdi:import"
|
ICON_IMPORT = "mdi:import"
|
||||||
|
|
||||||
# The config that zone accepts is the same as if it has platforms.
|
CREATE_FIELDS = {
|
||||||
PLATFORM_SCHEMA = vol.Schema(
|
vol.Required(CONF_NAME): cv.string,
|
||||||
{
|
vol.Required(CONF_LATITUDE): cv.latitude,
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Required(CONF_LONGITUDE): cv.longitude,
|
||||||
vol.Required(CONF_LATITUDE): cv.latitude,
|
vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS): vol.Coerce(float),
|
||||||
vol.Required(CONF_LONGITUDE): cv.longitude,
|
vol.Optional(CONF_PASSIVE, default=DEFAULT_PASSIVE): cv.boolean,
|
||||||
vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS): vol.Coerce(float),
|
vol.Optional(CONF_ICON): cv.icon,
|
||||||
vol.Optional(CONF_PASSIVE, default=DEFAULT_PASSIVE): cv.boolean,
|
}
|
||||||
vol.Optional(CONF_ICON): cv.icon,
|
|
||||||
},
|
|
||||||
|
UPDATE_FIELDS = {
|
||||||
|
vol.Optional(CONF_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_LATITUDE): cv.latitude,
|
||||||
|
vol.Optional(CONF_LONGITUDE): cv.longitude,
|
||||||
|
vol.Optional(CONF_RADIUS): vol.Coerce(float),
|
||||||
|
vol.Optional(CONF_PASSIVE): cv.boolean,
|
||||||
|
vol.Optional(CONF_ICON): cv.icon,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
{vol.Optional(DOMAIN): vol.All(cv.ensure_list, [vol.Schema(CREATE_FIELDS)])},
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
RELOAD_SERVICE_SCHEMA = vol.Schema({})
|
||||||
|
STORAGE_KEY = DOMAIN
|
||||||
|
STORAGE_VERSION = 1
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
def async_active_zone(hass, latitude, longitude, radius=0):
|
def async_active_zone(
|
||||||
|
hass: HomeAssistant, latitude: float, longitude: float, radius: int = 0
|
||||||
|
) -> Optional[State]:
|
||||||
"""Find the active zone for given latitude, longitude.
|
"""Find the active zone for given latitude, longitude.
|
||||||
|
|
||||||
This method must be run in the event loop.
|
This method must be run in the event loop.
|
||||||
"""
|
"""
|
||||||
# Sort entity IDs so that we are deterministic if equal distance to 2 zones
|
# Sort entity IDs so that we are deterministic if equal distance to 2 zones
|
||||||
zones = (
|
zones = (
|
||||||
hass.states.get(entity_id)
|
cast(State, hass.states.get(entity_id))
|
||||||
for entity_id in sorted(hass.states.async_entity_ids(DOMAIN))
|
for entity_id in sorted(hass.states.async_entity_ids(DOMAIN))
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -80,6 +103,9 @@ def async_active_zone(hass, latitude, longitude, radius=0):
|
|||||||
zone.attributes[ATTR_LONGITUDE],
|
zone.attributes[ATTR_LONGITUDE],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if zone_dist is None:
|
||||||
|
continue
|
||||||
|
|
||||||
within_zone = zone_dist - radius < zone.attributes[ATTR_RADIUS]
|
within_zone = zone_dist - radius < zone.attributes[ATTR_RADIUS]
|
||||||
closer_zone = closest is None or zone_dist < min_dist # type: ignore
|
closer_zone = closest is None or zone_dist < min_dist # type: ignore
|
||||||
smaller_zone = (
|
smaller_zone = (
|
||||||
@ -95,79 +121,227 @@ def async_active_zone(hass, latitude, longitude, radius=0):
|
|||||||
return closest
|
return closest
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
def in_zone(zone: State, latitude: float, longitude: float, radius: float = 0) -> bool:
|
||||||
"""Set up configured zones as well as Home Assistant zone if necessary."""
|
"""Test if given latitude, longitude is in given zone.
|
||||||
hass.data[DOMAIN] = {}
|
|
||||||
entities: Set[str] = set()
|
|
||||||
zone_entries = configured_zones(hass)
|
|
||||||
for _, entry in config_per_platform(config, DOMAIN):
|
|
||||||
if slugify(entry[CONF_NAME]) not in zone_entries:
|
|
||||||
zone = Zone(
|
|
||||||
hass,
|
|
||||||
entry[CONF_NAME],
|
|
||||||
entry[CONF_LATITUDE],
|
|
||||||
entry[CONF_LONGITUDE],
|
|
||||||
entry.get(CONF_RADIUS),
|
|
||||||
entry.get(CONF_ICON),
|
|
||||||
entry.get(CONF_PASSIVE),
|
|
||||||
)
|
|
||||||
zone.entity_id = async_generate_entity_id(
|
|
||||||
ENTITY_ID_FORMAT, entry[CONF_NAME], entities
|
|
||||||
)
|
|
||||||
hass.async_create_task(zone.async_update_ha_state())
|
|
||||||
entities.add(zone.entity_id)
|
|
||||||
|
|
||||||
if ENTITY_ID_HOME in entities or HOME_ZONE in zone_entries:
|
Async friendly.
|
||||||
return True
|
"""
|
||||||
|
zone_dist = distance(
|
||||||
zone = Zone(
|
latitude,
|
||||||
hass,
|
longitude,
|
||||||
hass.config.location_name,
|
zone.attributes[ATTR_LATITUDE],
|
||||||
hass.config.latitude,
|
zone.attributes[ATTR_LONGITUDE],
|
||||||
hass.config.longitude,
|
|
||||||
DEFAULT_RADIUS,
|
|
||||||
ICON_HOME,
|
|
||||||
False,
|
|
||||||
)
|
)
|
||||||
zone.entity_id = ENTITY_ID_HOME
|
|
||||||
hass.async_create_task(zone.async_update_ha_state())
|
if zone_dist is None or zone.attributes[ATTR_RADIUS] is None:
|
||||||
|
return False
|
||||||
|
return zone_dist - radius < cast(float, zone.attributes[ATTR_RADIUS])
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneStorageCollection(collection.StorageCollection):
|
||||||
|
"""Zone collection stored in storage."""
|
||||||
|
|
||||||
|
CREATE_SCHEMA = vol.Schema(CREATE_FIELDS)
|
||||||
|
UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS)
|
||||||
|
|
||||||
|
async def _process_create_data(self, data: Dict) -> Dict:
|
||||||
|
"""Validate the config is valid."""
|
||||||
|
return cast(Dict, self.CREATE_SCHEMA(data))
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def core_config_updated(_):
|
def _get_suggested_id(self, info: Dict) -> str:
|
||||||
|
"""Suggest an ID based on the config."""
|
||||||
|
return cast(str, info[CONF_NAME])
|
||||||
|
|
||||||
|
async def _update_data(self, data: dict, update_data: Dict) -> Dict:
|
||||||
|
"""Return a new updated data object."""
|
||||||
|
update_data = self.UPDATE_SCHEMA(update_data)
|
||||||
|
return {**data, **update_data}
|
||||||
|
|
||||||
|
|
||||||
|
class IDLessCollection(collection.ObservableCollection):
|
||||||
|
"""A collection without IDs."""
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
|
||||||
|
async def async_load(self, data: List[dict]) -> None:
|
||||||
|
"""Load the collection. Overrides existing data."""
|
||||||
|
for item_id in list(self.data):
|
||||||
|
await self.notify_change(collection.CHANGE_REMOVED, item_id, None)
|
||||||
|
|
||||||
|
self.data.clear()
|
||||||
|
|
||||||
|
for item in data:
|
||||||
|
self.counter += 1
|
||||||
|
item_id = f"fakeid-{self.counter}"
|
||||||
|
|
||||||
|
self.data[item_id] = item
|
||||||
|
await self.notify_change(collection.CHANGE_ADDED, item_id, item)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: Dict) -> bool:
|
||||||
|
"""Set up configured zones as well as Home Assistant zone if necessary."""
|
||||||
|
component = entity_component.EntityComponent(_LOGGER, DOMAIN, hass)
|
||||||
|
id_manager = collection.IDManager()
|
||||||
|
|
||||||
|
yaml_collection = IDLessCollection(
|
||||||
|
logging.getLogger(f"{__name__}.yaml_collection"), id_manager
|
||||||
|
)
|
||||||
|
collection.attach_entity_component_collection(
|
||||||
|
component, yaml_collection, lambda conf: Zone(conf, False)
|
||||||
|
)
|
||||||
|
|
||||||
|
storage_collection = ZoneStorageCollection(
|
||||||
|
storage.Store(hass, STORAGE_VERSION, STORAGE_KEY),
|
||||||
|
logging.getLogger(f"{__name__}_storage_collection"),
|
||||||
|
id_manager,
|
||||||
|
)
|
||||||
|
collection.attach_entity_component_collection(
|
||||||
|
component, storage_collection, lambda conf: Zone(conf, True)
|
||||||
|
)
|
||||||
|
|
||||||
|
if DOMAIN in config:
|
||||||
|
await yaml_collection.async_load(config[DOMAIN])
|
||||||
|
|
||||||
|
await storage_collection.async_load()
|
||||||
|
|
||||||
|
collection.StorageCollectionWebsocket(
|
||||||
|
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
|
||||||
|
).async_setup(hass)
|
||||||
|
|
||||||
|
async def _collection_changed(
|
||||||
|
change_type: str, item_id: str, config: Optional[Dict]
|
||||||
|
) -> None:
|
||||||
|
"""Handle a collection change: clean up entity registry on removals."""
|
||||||
|
if change_type != collection.CHANGE_REMOVED:
|
||||||
|
return
|
||||||
|
|
||||||
|
ent_reg = await entity_registry.async_get_registry(hass)
|
||||||
|
ent_reg.async_remove(
|
||||||
|
cast(str, ent_reg.async_get_entity_id(DOMAIN, DOMAIN, item_id))
|
||||||
|
)
|
||||||
|
|
||||||
|
storage_collection.async_add_listener(_collection_changed)
|
||||||
|
|
||||||
|
async def reload_service_handler(service_call: ServiceCall) -> None:
|
||||||
|
"""Remove all zones and load new ones from config."""
|
||||||
|
conf = await component.async_prepare_reload(skip_reset=True)
|
||||||
|
if conf is None:
|
||||||
|
return
|
||||||
|
await yaml_collection.async_load(conf[DOMAIN])
|
||||||
|
|
||||||
|
service.async_register_admin_service(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_RELOAD,
|
||||||
|
reload_service_handler,
|
||||||
|
schema=RELOAD_SERVICE_SCHEMA,
|
||||||
|
)
|
||||||
|
|
||||||
|
if component.get_entity("zone.home"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
home_zone = Zone(_home_conf(hass), True,)
|
||||||
|
home_zone.entity_id = ENTITY_ID_HOME
|
||||||
|
await component.async_add_entities([home_zone]) # type: ignore
|
||||||
|
|
||||||
|
async def core_config_updated(_: Event) -> None:
|
||||||
"""Handle core config updated."""
|
"""Handle core config updated."""
|
||||||
zone.name = hass.config.location_name
|
await home_zone.async_update_config(_home_conf(hass))
|
||||||
zone.latitude = hass.config.latitude
|
|
||||||
zone.longitude = hass.config.longitude
|
|
||||||
zone.async_write_ha_state()
|
|
||||||
|
|
||||||
hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, core_config_updated)
|
hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, core_config_updated)
|
||||||
|
|
||||||
|
hass.data[DOMAIN] = storage_collection
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry):
|
@callback
|
||||||
|
def _home_conf(hass: HomeAssistant) -> Dict:
|
||||||
|
"""Return the home zone config."""
|
||||||
|
return {
|
||||||
|
CONF_NAME: hass.config.location_name,
|
||||||
|
CONF_LATITUDE: hass.config.latitude,
|
||||||
|
CONF_LONGITUDE: hass.config.longitude,
|
||||||
|
CONF_RADIUS: DEFAULT_RADIUS,
|
||||||
|
CONF_ICON: ICON_HOME,
|
||||||
|
CONF_PASSIVE: False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, config_entry: config_entries.ConfigEntry
|
||||||
|
) -> bool:
|
||||||
"""Set up zone as config entry."""
|
"""Set up zone as config entry."""
|
||||||
entry = config_entry.data
|
storage_collection = cast(ZoneStorageCollection, hass.data[DOMAIN])
|
||||||
name = entry[CONF_NAME]
|
|
||||||
zone = Zone(
|
data = dict(config_entry.data)
|
||||||
hass,
|
data.setdefault(CONF_PASSIVE, DEFAULT_PASSIVE)
|
||||||
name,
|
data.setdefault(CONF_RADIUS, DEFAULT_RADIUS)
|
||||||
entry[CONF_LATITUDE],
|
|
||||||
entry[CONF_LONGITUDE],
|
await storage_collection.async_create_item(data)
|
||||||
entry.get(CONF_RADIUS, DEFAULT_RADIUS),
|
|
||||||
entry.get(CONF_ICON),
|
hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id))
|
||||||
entry.get(CONF_PASSIVE, DEFAULT_PASSIVE),
|
|
||||||
)
|
|
||||||
zone.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, name, None, hass)
|
|
||||||
hass.async_create_task(zone.async_update_ha_state())
|
|
||||||
hass.data[DOMAIN][slugify(name)] = zone
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass, config_entry):
|
async def async_unload_entry(
|
||||||
"""Unload a config entry."""
|
hass: HomeAssistant, config_entry: config_entries.ConfigEntry
|
||||||
zones = hass.data[DOMAIN]
|
) -> bool:
|
||||||
name = slugify(config_entry.data[CONF_NAME])
|
"""Will be called once we remove it."""
|
||||||
zone = zones.pop(name)
|
|
||||||
await zone.async_remove()
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Zone(entity.Entity):
|
||||||
|
"""Representation of a Zone."""
|
||||||
|
|
||||||
|
def __init__(self, config: Dict, editable: bool):
|
||||||
|
"""Initialize the zone."""
|
||||||
|
self._config = config
|
||||||
|
self._editable = editable
|
||||||
|
self._attrs: Optional[Dict] = None
|
||||||
|
self._generate_attrs()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> str:
|
||||||
|
"""Return the state property really does nothing for a zone."""
|
||||||
|
return "zoning"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return name."""
|
||||||
|
return cast(str, self._config[CONF_NAME])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> Optional[str]:
|
||||||
|
"""Return unique ID."""
|
||||||
|
return self._config.get(CONF_ID)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self) -> Optional[str]:
|
||||||
|
"""Return the icon if any."""
|
||||||
|
return self._config.get(CONF_ICON)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state_attributes(self) -> Optional[Dict]:
|
||||||
|
"""Return the state attributes of the zone."""
|
||||||
|
return self._attrs
|
||||||
|
|
||||||
|
async def async_update_config(self, config: Dict) -> None:
|
||||||
|
"""Handle when the config is updated."""
|
||||||
|
self._config = config
|
||||||
|
self._generate_attrs()
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _generate_attrs(self) -> None:
|
||||||
|
"""Generate new attrs based on config."""
|
||||||
|
self._attrs = {
|
||||||
|
ATTR_HIDDEN: True,
|
||||||
|
ATTR_LATITUDE: self._config[CONF_LATITUDE],
|
||||||
|
ATTR_LONGITUDE: self._config[CONF_LONGITUDE],
|
||||||
|
ATTR_RADIUS: self._config[CONF_RADIUS],
|
||||||
|
ATTR_PASSIVE: self._config[CONF_PASSIVE],
|
||||||
|
ATTR_EDITABLE: self._editable,
|
||||||
|
}
|
||||||
|
@ -1,75 +1,13 @@
|
|||||||
"""Config flow to configure zone component."""
|
"""Config flow to configure zone component.
|
||||||
|
|
||||||
from typing import Set
|
|
||||||
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
|
This is no longer in use. This file is around so that existing
|
||||||
|
config entries will remain to be loaded and then automatically
|
||||||
|
migrated to the storage collection.
|
||||||
|
"""
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import (
|
|
||||||
CONF_ICON,
|
|
||||||
CONF_LATITUDE,
|
|
||||||
CONF_LONGITUDE,
|
|
||||||
CONF_NAME,
|
|
||||||
CONF_RADIUS,
|
|
||||||
)
|
|
||||||
from homeassistant.core import callback
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
|
||||||
from homeassistant.util import slugify
|
|
||||||
|
|
||||||
from .const import CONF_PASSIVE, DOMAIN, HOME_ZONE
|
from .const import DOMAIN # noqa # pylint:disable=unused-import
|
||||||
|
|
||||||
# mypy: allow-untyped-defs, no-check-untyped-defs
|
|
||||||
|
|
||||||
|
|
||||||
@callback
|
class ZoneConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
def configured_zones(hass: HomeAssistantType) -> Set[str]:
|
"""Stub zone config flow class."""
|
||||||
"""Return a set of the configured zones."""
|
|
||||||
return set(
|
|
||||||
(slugify(entry.data[CONF_NAME]))
|
|
||||||
for entry in (
|
|
||||||
hass.config_entries.async_entries(DOMAIN) if hass.config_entries else []
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@config_entries.HANDLERS.register(DOMAIN)
|
|
||||||
class ZoneFlowHandler(config_entries.ConfigFlow):
|
|
||||||
"""Zone config flow."""
|
|
||||||
|
|
||||||
VERSION = 1
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Initialize zone configuration flow."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
|
||||||
"""Handle a flow initialized by the user."""
|
|
||||||
return await self.async_step_init(user_input)
|
|
||||||
|
|
||||||
async def async_step_init(self, user_input=None):
|
|
||||||
"""Handle a flow start."""
|
|
||||||
errors = {}
|
|
||||||
|
|
||||||
if user_input is not None:
|
|
||||||
name = slugify(user_input[CONF_NAME])
|
|
||||||
if name not in configured_zones(self.hass) and name != HOME_ZONE:
|
|
||||||
return self.async_create_entry(
|
|
||||||
title=user_input[CONF_NAME], data=user_input
|
|
||||||
)
|
|
||||||
errors["base"] = "name_exists"
|
|
||||||
|
|
||||||
return self.async_show_form(
|
|
||||||
step_id="init",
|
|
||||||
data_schema=vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Required(CONF_NAME): str,
|
|
||||||
vol.Required(CONF_LATITUDE): cv.latitude,
|
|
||||||
vol.Required(CONF_LONGITUDE): cv.longitude,
|
|
||||||
vol.Optional(CONF_RADIUS): vol.Coerce(float),
|
|
||||||
vol.Optional(CONF_ICON): str,
|
|
||||||
vol.Optional(CONF_PASSIVE): bool,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
errors=errors,
|
|
||||||
)
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "zone",
|
"domain": "zone",
|
||||||
"name": "Zone",
|
"name": "Zone",
|
||||||
"config_flow": true,
|
"config_flow": false,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/zone",
|
"documentation": "https://www.home-assistant.io/integrations/zone",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"title": "Zone",
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"title": "Define zone parameters",
|
|
||||||
"data": {
|
|
||||||
"name": "Name",
|
|
||||||
"latitude": "Latitude",
|
|
||||||
"longitude": "Longitude",
|
|
||||||
"radius": "Radius",
|
|
||||||
"passive": "Passive",
|
|
||||||
"icon": "Icon"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"name_exists": "Name already exists"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
"""Zone entity and functionality."""
|
|
||||||
|
|
||||||
from typing import cast
|
|
||||||
|
|
||||||
from homeassistant.const import ATTR_HIDDEN, ATTR_LATITUDE, ATTR_LONGITUDE
|
|
||||||
from homeassistant.core import State
|
|
||||||
from homeassistant.helpers.entity import Entity
|
|
||||||
from homeassistant.util.location import distance
|
|
||||||
|
|
||||||
from .const import ATTR_PASSIVE, ATTR_RADIUS
|
|
||||||
|
|
||||||
STATE = "zoning"
|
|
||||||
|
|
||||||
|
|
||||||
# mypy: allow-untyped-defs
|
|
||||||
|
|
||||||
|
|
||||||
def in_zone(zone: State, latitude: float, longitude: float, radius: float = 0) -> bool:
|
|
||||||
"""Test if given latitude, longitude is in given zone.
|
|
||||||
|
|
||||||
Async friendly.
|
|
||||||
"""
|
|
||||||
zone_dist = distance(
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
zone.attributes[ATTR_LATITUDE],
|
|
||||||
zone.attributes[ATTR_LONGITUDE],
|
|
||||||
)
|
|
||||||
|
|
||||||
if zone_dist is None or zone.attributes[ATTR_RADIUS] is None:
|
|
||||||
return False
|
|
||||||
return zone_dist - radius < cast(float, zone.attributes[ATTR_RADIUS])
|
|
||||||
|
|
||||||
|
|
||||||
class Zone(Entity):
|
|
||||||
"""Representation of a Zone."""
|
|
||||||
|
|
||||||
name = None
|
|
||||||
|
|
||||||
def __init__(self, hass, name, latitude, longitude, radius, icon, passive):
|
|
||||||
"""Initialize the zone."""
|
|
||||||
self.hass = hass
|
|
||||||
self.name = name
|
|
||||||
self.latitude = latitude
|
|
||||||
self.longitude = longitude
|
|
||||||
self._radius = radius
|
|
||||||
self._icon = icon
|
|
||||||
self._passive = passive
|
|
||||||
|
|
||||||
@property
|
|
||||||
def state(self):
|
|
||||||
"""Return the state property really does nothing for a zone."""
|
|
||||||
return STATE
|
|
||||||
|
|
||||||
@property
|
|
||||||
def icon(self):
|
|
||||||
"""Return the icon if any."""
|
|
||||||
return self._icon
|
|
||||||
|
|
||||||
@property
|
|
||||||
def state_attributes(self):
|
|
||||||
"""Return the state attributes of the zone."""
|
|
||||||
data = {
|
|
||||||
ATTR_HIDDEN: True,
|
|
||||||
ATTR_LATITUDE: self.latitude,
|
|
||||||
ATTR_LONGITUDE: self.longitude,
|
|
||||||
ATTR_RADIUS: self._radius,
|
|
||||||
}
|
|
||||||
if self._passive:
|
|
||||||
data[ATTR_PASSIVE] = self._passive
|
|
||||||
return data
|
|
@ -98,6 +98,5 @@ FLOWS = [
|
|||||||
"wled",
|
"wled",
|
||||||
"wwlln",
|
"wwlln",
|
||||||
"zha",
|
"zha",
|
||||||
"zone",
|
|
||||||
"zwave"
|
"zwave"
|
||||||
]
|
]
|
||||||
|
@ -114,7 +114,7 @@ class ObservableCollection(ABC):
|
|||||||
|
|
||||||
|
|
||||||
class YamlCollection(ObservableCollection):
|
class YamlCollection(ObservableCollection):
|
||||||
"""Offer a fake CRUD interface on top of static YAML."""
|
"""Offer a collection based on static data."""
|
||||||
|
|
||||||
async def async_load(self, data: List[dict]) -> None:
|
async def async_load(self, data: List[dict]) -> None:
|
||||||
"""Load the YAML collection. Overrides existing data."""
|
"""Load the YAML collection. Overrides existing data."""
|
||||||
@ -133,7 +133,7 @@ class YamlCollection(ObservableCollection):
|
|||||||
event = CHANGE_ADDED
|
event = CHANGE_ADDED
|
||||||
|
|
||||||
self.data[item_id] = item
|
self.data[item_id] = item
|
||||||
await self.notify_change(event, item[CONF_ID], item)
|
await self.notify_change(event, item_id, item)
|
||||||
|
|
||||||
for item_id in old_ids:
|
for item_id in old_ids:
|
||||||
self.data.pop(item_id)
|
self.data.pop(item_id)
|
||||||
@ -246,7 +246,7 @@ def attach_entity_component_collection(
|
|||||||
"""Handle a collection change."""
|
"""Handle a collection change."""
|
||||||
if change_type == CHANGE_ADDED:
|
if change_type == CHANGE_ADDED:
|
||||||
entity = create_entity(cast(dict, config))
|
entity = create_entity(cast(dict, config))
|
||||||
await entity_component.async_add_entities([entity])
|
await entity_component.async_add_entities([entity]) # type: ignore
|
||||||
entities[item_id] = entity
|
entities[item_id] = entity
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -473,7 +473,7 @@ def zone(
|
|||||||
if latitude is None or longitude is None:
|
if latitude is None or longitude is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return zone_cmp.zone.in_zone(
|
return zone_cmp.in_zone(
|
||||||
zone_ent, latitude, longitude, entity.attributes.get(ATTR_GPS_ACCURACY, 0)
|
zone_ent, latitude, longitude, entity.attributes.get(ATTR_GPS_ACCURACY, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -284,7 +284,7 @@ class Entity(ABC):
|
|||||||
self._async_write_ha_state()
|
self._async_write_ha_state()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_write_ha_state(self):
|
def async_write_ha_state(self) -> None:
|
||||||
"""Write the state to the state machine."""
|
"""Write the state to the state machine."""
|
||||||
if self.hass is None:
|
if self.hass is None:
|
||||||
raise RuntimeError(f"Attribute hass is None for {self}")
|
raise RuntimeError(f"Attribute hass is None for {self}")
|
||||||
@ -294,7 +294,7 @@ class Entity(ABC):
|
|||||||
f"No entity id specified for entity {self.name}"
|
f"No entity id specified for entity {self.name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
self._async_write_ha_state()
|
self._async_write_ha_state() # type: ignore
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_write_ha_state(self):
|
def _async_write_ha_state(self):
|
||||||
|
@ -3,6 +3,8 @@ import asyncio
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import logging
|
import logging
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import Dict, Optional, cast
|
||||||
|
|
||||||
from homeassistant import config as conf_util
|
from homeassistant import config as conf_util
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -13,6 +15,7 @@ from homeassistant.helpers import (
|
|||||||
config_per_platform,
|
config_per_platform,
|
||||||
config_validation as cv,
|
config_validation as cv,
|
||||||
discovery,
|
discovery,
|
||||||
|
entity,
|
||||||
service,
|
service,
|
||||||
)
|
)
|
||||||
from homeassistant.loader import async_get_integration, bind_hass
|
from homeassistant.loader import async_get_integration, bind_hass
|
||||||
@ -38,15 +41,15 @@ async def async_update_entity(hass: HomeAssistant, entity_id: str) -> None:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
entity = entity_comp.get_entity(entity_id)
|
entity_obj = entity_comp.get_entity(entity_id)
|
||||||
|
|
||||||
if entity is None:
|
if entity_obj is None:
|
||||||
logging.getLogger(__name__).warning(
|
logging.getLogger(__name__).warning(
|
||||||
"Forced update failed. Entity %s not found.", entity_id
|
"Forced update failed. Entity %s not found.", entity_id
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await entity.async_update_ha_state(True)
|
await entity_obj.async_update_ha_state(True)
|
||||||
|
|
||||||
|
|
||||||
class EntityComponent:
|
class EntityComponent:
|
||||||
@ -59,7 +62,13 @@ class EntityComponent:
|
|||||||
- Listen for discovery events for platforms related to the domain.
|
- Listen for discovery events for platforms related to the domain.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, logger, domain, hass, scan_interval=DEFAULT_SCAN_INTERVAL):
|
def __init__(
|
||||||
|
self,
|
||||||
|
logger: logging.Logger,
|
||||||
|
domain: str,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
scan_interval: timedelta = DEFAULT_SCAN_INTERVAL,
|
||||||
|
):
|
||||||
"""Initialize an entity component."""
|
"""Initialize an entity component."""
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
@ -68,7 +77,9 @@ class EntityComponent:
|
|||||||
|
|
||||||
self.config = None
|
self.config = None
|
||||||
|
|
||||||
self._platforms = {domain: self._async_init_entity_platform(domain, None)}
|
self._platforms: Dict[str, EntityPlatform] = {
|
||||||
|
domain: self._async_init_entity_platform(domain, None)
|
||||||
|
}
|
||||||
self.async_add_entities = self._platforms[domain].async_add_entities
|
self.async_add_entities = self._platforms[domain].async_add_entities
|
||||||
self.add_entities = self._platforms[domain].add_entities
|
self.add_entities = self._platforms[domain].add_entities
|
||||||
|
|
||||||
@ -81,12 +92,12 @@ class EntityComponent:
|
|||||||
platform.entities.values() for platform in self._platforms.values()
|
platform.entities.values() for platform in self._platforms.values()
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_entity(self, entity_id):
|
def get_entity(self, entity_id: str) -> Optional[entity.Entity]:
|
||||||
"""Get an entity."""
|
"""Get an entity."""
|
||||||
for platform in self._platforms.values():
|
for platform in self._platforms.values():
|
||||||
entity = platform.entities.get(entity_id)
|
entity_obj = cast(Optional[entity.Entity], platform.entities.get(entity_id))
|
||||||
if entity is not None:
|
if entity_obj is not None:
|
||||||
return entity
|
return entity_obj
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def setup(self, config):
|
def setup(self, config):
|
||||||
@ -237,7 +248,7 @@ class EntityComponent:
|
|||||||
if entity_id in platform.entities:
|
if entity_id in platform.entities:
|
||||||
await platform.async_remove_entity(entity_id)
|
await platform.async_remove_entity(entity_id)
|
||||||
|
|
||||||
async def async_prepare_reload(self, *, skip_reset=False):
|
async def async_prepare_reload(self, *, skip_reset: bool = False) -> Optional[dict]:
|
||||||
"""Prepare reloading this entity component.
|
"""Prepare reloading this entity component.
|
||||||
|
|
||||||
This method must be run in the event loop.
|
This method must be run in the event loop.
|
||||||
@ -250,25 +261,30 @@ class EntityComponent:
|
|||||||
|
|
||||||
integration = await async_get_integration(self.hass, self.domain)
|
integration = await async_get_integration(self.hass, self.domain)
|
||||||
|
|
||||||
conf = await conf_util.async_process_component_config(
|
processed_conf = await conf_util.async_process_component_config(
|
||||||
self.hass, conf, integration
|
self.hass, conf, integration
|
||||||
)
|
)
|
||||||
|
|
||||||
if conf is None:
|
if processed_conf is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not skip_reset:
|
if not skip_reset:
|
||||||
await self._async_reset()
|
await self._async_reset()
|
||||||
return conf
|
|
||||||
|
return processed_conf
|
||||||
|
|
||||||
def _async_init_entity_platform(
|
def _async_init_entity_platform(
|
||||||
self, platform_type, platform, scan_interval=None, entity_namespace=None
|
self,
|
||||||
):
|
platform_type: str,
|
||||||
|
platform: Optional[ModuleType],
|
||||||
|
scan_interval: Optional[timedelta] = None,
|
||||||
|
entity_namespace: Optional[str] = None,
|
||||||
|
) -> EntityPlatform:
|
||||||
"""Initialize an entity platform."""
|
"""Initialize an entity platform."""
|
||||||
if scan_interval is None:
|
if scan_interval is None:
|
||||||
scan_interval = self.scan_interval
|
scan_interval = self.scan_interval
|
||||||
|
|
||||||
return EntityPlatform(
|
return EntityPlatform( # type: ignore
|
||||||
hass=self.hass,
|
hass=self.hass,
|
||||||
logger=self.logger,
|
logger=self.logger,
|
||||||
domain=self.domain,
|
domain=self.domain,
|
||||||
|
@ -14,6 +14,7 @@ class TestProximity(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Set up things to be run when tests are started."""
|
"""Set up things to be run when tests are started."""
|
||||||
self.hass = get_test_home_assistant()
|
self.hass = get_test_home_assistant()
|
||||||
|
self.hass.config.components.add("zone")
|
||||||
self.hass.states.set(
|
self.hass.states.set(
|
||||||
"zone.home",
|
"zone.home",
|
||||||
"zoning",
|
"zoning",
|
||||||
@ -211,7 +212,7 @@ class TestProximity(unittest.TestCase):
|
|||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
state = self.hass.states.get("proximity.home")
|
state = self.hass.states.get("proximity.home")
|
||||||
assert state.attributes.get("nearest") == "test1"
|
assert state.attributes.get("nearest") == "test1"
|
||||||
assert state.attributes.get("dir_of_travel") == "towards"
|
assert state.attributes.get("dir_of_travel") == "away_from"
|
||||||
|
|
||||||
def test_device_tracker_test1_awaycloser(self):
|
def test_device_tracker_test1_awaycloser(self):
|
||||||
"""Test for tracker state away closer."""
|
"""Test for tracker state away closer."""
|
||||||
@ -245,7 +246,7 @@ class TestProximity(unittest.TestCase):
|
|||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
state = self.hass.states.get("proximity.home")
|
state = self.hass.states.get("proximity.home")
|
||||||
assert state.attributes.get("nearest") == "test1"
|
assert state.attributes.get("nearest") == "test1"
|
||||||
assert state.attributes.get("dir_of_travel") == "away_from"
|
assert state.attributes.get("dir_of_travel") == "towards"
|
||||||
|
|
||||||
def test_all_device_trackers_in_ignored_zone(self):
|
def test_all_device_trackers_in_ignored_zone(self):
|
||||||
"""Test for tracker in ignored zone."""
|
"""Test for tracker in ignored zone."""
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
"""Tests for zone config flow."""
|
|
||||||
|
|
||||||
from homeassistant.components.zone import config_flow
|
|
||||||
from homeassistant.components.zone.const import CONF_PASSIVE, DOMAIN, HOME_ZONE
|
|
||||||
from homeassistant.const import (
|
|
||||||
CONF_ICON,
|
|
||||||
CONF_LATITUDE,
|
|
||||||
CONF_LONGITUDE,
|
|
||||||
CONF_NAME,
|
|
||||||
CONF_RADIUS,
|
|
||||||
)
|
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
|
||||||
|
|
||||||
|
|
||||||
async def test_flow_works(hass):
|
|
||||||
"""Test that config flow works."""
|
|
||||||
flow = config_flow.ZoneFlowHandler()
|
|
||||||
flow.hass = hass
|
|
||||||
|
|
||||||
result = await flow.async_step_init(
|
|
||||||
user_input={
|
|
||||||
CONF_NAME: "Name",
|
|
||||||
CONF_LATITUDE: "1.1",
|
|
||||||
CONF_LONGITUDE: "2.2",
|
|
||||||
CONF_RADIUS: "100",
|
|
||||||
CONF_ICON: "mdi:home",
|
|
||||||
CONF_PASSIVE: True,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result["type"] == "create_entry"
|
|
||||||
assert result["title"] == "Name"
|
|
||||||
assert result["data"] == {
|
|
||||||
CONF_NAME: "Name",
|
|
||||||
CONF_LATITUDE: "1.1",
|
|
||||||
CONF_LONGITUDE: "2.2",
|
|
||||||
CONF_RADIUS: "100",
|
|
||||||
CONF_ICON: "mdi:home",
|
|
||||||
CONF_PASSIVE: True,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_flow_requires_unique_name(hass):
|
|
||||||
"""Test that config flow verifies that each zones name is unique."""
|
|
||||||
MockConfigEntry(domain=DOMAIN, data={CONF_NAME: "Name"}).add_to_hass(hass)
|
|
||||||
flow = config_flow.ZoneFlowHandler()
|
|
||||||
flow.hass = hass
|
|
||||||
|
|
||||||
result = await flow.async_step_init(user_input={CONF_NAME: "Name"})
|
|
||||||
assert result["errors"] == {"base": "name_exists"}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_flow_requires_name_different_from_home(hass):
|
|
||||||
"""Test that config flow verifies that each zones name is unique."""
|
|
||||||
flow = config_flow.ZoneFlowHandler()
|
|
||||||
flow.hass = hass
|
|
||||||
|
|
||||||
result = await flow.async_step_init(user_input={CONF_NAME: HOME_ZONE})
|
|
||||||
assert result["errors"] == {"base": "name_exists"}
|
|
@ -1,229 +1,224 @@
|
|||||||
"""Test zone component."""
|
"""Test zone component."""
|
||||||
|
from asynctest import patch
|
||||||
import unittest
|
import pytest
|
||||||
from unittest.mock import Mock
|
|
||||||
|
|
||||||
from homeassistant import setup
|
from homeassistant import setup
|
||||||
from homeassistant.components import zone
|
from homeassistant.components import zone
|
||||||
|
from homeassistant.components.zone import DOMAIN
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_EDITABLE,
|
||||||
|
ATTR_FRIENDLY_NAME,
|
||||||
|
ATTR_ICON,
|
||||||
|
ATTR_NAME,
|
||||||
|
SERVICE_RELOAD,
|
||||||
|
)
|
||||||
|
from homeassistant.core import Context
|
||||||
|
from homeassistant.exceptions import Unauthorized
|
||||||
|
from homeassistant.helpers import entity_registry
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, get_test_home_assistant
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_entry_successful(hass):
|
@pytest.fixture
|
||||||
"""Test setup entry is successful."""
|
def storage_setup(hass, hass_storage):
|
||||||
entry = Mock()
|
"""Storage setup."""
|
||||||
entry.data = {
|
|
||||||
zone.CONF_NAME: "Test Zone",
|
async def _storage(items=None, config=None):
|
||||||
zone.CONF_LATITUDE: 1.1,
|
if items is None:
|
||||||
zone.CONF_LONGITUDE: -2.2,
|
hass_storage[DOMAIN] = {
|
||||||
zone.CONF_RADIUS: True,
|
"key": DOMAIN,
|
||||||
|
"version": 1,
|
||||||
|
"data": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "from_storage",
|
||||||
|
"name": "from storage",
|
||||||
|
"latitude": 1,
|
||||||
|
"longitude": 2,
|
||||||
|
"radius": 3,
|
||||||
|
"passive": False,
|
||||||
|
"icon": "mdi:from-storage",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
hass_storage[DOMAIN] = {
|
||||||
|
"key": DOMAIN,
|
||||||
|
"version": 1,
|
||||||
|
"data": {"items": items},
|
||||||
|
}
|
||||||
|
if config is None:
|
||||||
|
config = {}
|
||||||
|
return await setup.async_setup_component(hass, DOMAIN, config)
|
||||||
|
|
||||||
|
return _storage
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_no_zones_still_adds_home_zone(hass):
|
||||||
|
"""Test if no config is passed in we still get the home zone."""
|
||||||
|
assert await setup.async_setup_component(hass, zone.DOMAIN, {"zone": None})
|
||||||
|
assert len(hass.states.async_entity_ids("zone")) == 1
|
||||||
|
state = hass.states.get("zone.home")
|
||||||
|
assert hass.config.location_name == state.name
|
||||||
|
assert hass.config.latitude == state.attributes["latitude"]
|
||||||
|
assert hass.config.longitude == state.attributes["longitude"]
|
||||||
|
assert not state.attributes.get("passive", False)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup(hass):
|
||||||
|
"""Test a successful setup."""
|
||||||
|
info = {
|
||||||
|
"name": "Test Zone",
|
||||||
|
"latitude": 32.880837,
|
||||||
|
"longitude": -117.237561,
|
||||||
|
"radius": 250,
|
||||||
|
"passive": True,
|
||||||
}
|
}
|
||||||
hass.data[zone.DOMAIN] = {}
|
assert await setup.async_setup_component(hass, zone.DOMAIN, {"zone": info})
|
||||||
assert await zone.async_setup_entry(hass, entry) is True
|
|
||||||
assert "test_zone" in hass.data[zone.DOMAIN]
|
assert len(hass.states.async_entity_ids("zone")) == 2
|
||||||
|
state = hass.states.get("zone.test_zone")
|
||||||
|
assert info["name"] == state.name
|
||||||
|
assert info["latitude"] == state.attributes["latitude"]
|
||||||
|
assert info["longitude"] == state.attributes["longitude"]
|
||||||
|
assert info["radius"] == state.attributes["radius"]
|
||||||
|
assert info["passive"] == state.attributes["passive"]
|
||||||
|
|
||||||
|
|
||||||
async def test_unload_entry_successful(hass):
|
async def test_setup_zone_skips_home_zone(hass):
|
||||||
"""Test unload entry is successful."""
|
"""Test that zone named Home should override hass home zone."""
|
||||||
entry = Mock()
|
info = {"name": "Home", "latitude": 1.1, "longitude": -2.2}
|
||||||
entry.data = {
|
assert await setup.async_setup_component(hass, zone.DOMAIN, {"zone": info})
|
||||||
zone.CONF_NAME: "Test Zone",
|
|
||||||
zone.CONF_LATITUDE: 1.1,
|
assert len(hass.states.async_entity_ids("zone")) == 1
|
||||||
zone.CONF_LONGITUDE: -2.2,
|
state = hass.states.get("zone.home")
|
||||||
}
|
assert info["name"] == state.name
|
||||||
hass.data[zone.DOMAIN] = {}
|
|
||||||
assert await zone.async_setup_entry(hass, entry) is True
|
|
||||||
assert await zone.async_unload_entry(hass, entry) is True
|
|
||||||
assert not hass.data[zone.DOMAIN]
|
|
||||||
|
|
||||||
|
|
||||||
class TestComponentZone(unittest.TestCase):
|
async def test_setup_name_can_be_same_on_multiple_zones(hass):
|
||||||
"""Test the zone component."""
|
"""Test that zone named Home should override hass home zone."""
|
||||||
|
info = {"name": "Test Zone", "latitude": 1.1, "longitude": -2.2}
|
||||||
|
assert await setup.async_setup_component(hass, zone.DOMAIN, {"zone": [info, info]})
|
||||||
|
assert len(hass.states.async_entity_ids("zone")) == 3
|
||||||
|
|
||||||
def setUp(self): # pylint: disable=invalid-name
|
|
||||||
"""Set up things to be run when tests are started."""
|
|
||||||
self.hass = get_test_home_assistant()
|
|
||||||
|
|
||||||
def tearDown(self): # pylint: disable=invalid-name
|
async def test_active_zone_skips_passive_zones(hass):
|
||||||
"""Stop down everything that was started."""
|
"""Test active and passive zones."""
|
||||||
self.hass.stop()
|
assert await setup.async_setup_component(
|
||||||
|
hass,
|
||||||
|
zone.DOMAIN,
|
||||||
|
{
|
||||||
|
"zone": [
|
||||||
|
{
|
||||||
|
"name": "Passive Zone",
|
||||||
|
"latitude": 32.880600,
|
||||||
|
"longitude": -117.237561,
|
||||||
|
"radius": 250,
|
||||||
|
"passive": True,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
active = zone.async_active_zone(hass, 32.880600, -117.237561)
|
||||||
|
assert active is None
|
||||||
|
|
||||||
def test_setup_no_zones_still_adds_home_zone(self):
|
|
||||||
"""Test if no config is passed in we still get the home zone."""
|
|
||||||
assert setup.setup_component(self.hass, zone.DOMAIN, {"zone": None})
|
|
||||||
assert len(self.hass.states.entity_ids("zone")) == 1
|
|
||||||
state = self.hass.states.get("zone.home")
|
|
||||||
assert self.hass.config.location_name == state.name
|
|
||||||
assert self.hass.config.latitude == state.attributes["latitude"]
|
|
||||||
assert self.hass.config.longitude == state.attributes["longitude"]
|
|
||||||
assert not state.attributes.get("passive", False)
|
|
||||||
|
|
||||||
def test_setup(self):
|
async def test_active_zone_skips_passive_zones_2(hass):
|
||||||
"""Test a successful setup."""
|
"""Test active and passive zones."""
|
||||||
info = {
|
assert await setup.async_setup_component(
|
||||||
"name": "Test Zone",
|
hass,
|
||||||
"latitude": 32.880837,
|
zone.DOMAIN,
|
||||||
"longitude": -117.237561,
|
{
|
||||||
"radius": 250,
|
"zone": [
|
||||||
"passive": True,
|
{
|
||||||
}
|
"name": "Active Zone",
|
||||||
assert setup.setup_component(self.hass, zone.DOMAIN, {"zone": info})
|
"latitude": 32.880800,
|
||||||
|
"longitude": -117.237561,
|
||||||
|
"radius": 500,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
active = zone.async_active_zone(hass, 32.880700, -117.237561)
|
||||||
|
assert "zone.active_zone" == active.entity_id
|
||||||
|
|
||||||
assert len(self.hass.states.entity_ids("zone")) == 2
|
|
||||||
state = self.hass.states.get("zone.test_zone")
|
|
||||||
assert info["name"] == state.name
|
|
||||||
assert info["latitude"] == state.attributes["latitude"]
|
|
||||||
assert info["longitude"] == state.attributes["longitude"]
|
|
||||||
assert info["radius"] == state.attributes["radius"]
|
|
||||||
assert info["passive"] == state.attributes["passive"]
|
|
||||||
|
|
||||||
def test_setup_zone_skips_home_zone(self):
|
async def test_active_zone_prefers_smaller_zone_if_same_distance(hass):
|
||||||
"""Test that zone named Home should override hass home zone."""
|
"""Test zone size preferences."""
|
||||||
info = {"name": "Home", "latitude": 1.1, "longitude": -2.2}
|
latitude = 32.880600
|
||||||
assert setup.setup_component(self.hass, zone.DOMAIN, {"zone": info})
|
longitude = -117.237561
|
||||||
|
assert await setup.async_setup_component(
|
||||||
|
hass,
|
||||||
|
zone.DOMAIN,
|
||||||
|
{
|
||||||
|
"zone": [
|
||||||
|
{
|
||||||
|
"name": "Small Zone",
|
||||||
|
"latitude": latitude,
|
||||||
|
"longitude": longitude,
|
||||||
|
"radius": 250,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Big Zone",
|
||||||
|
"latitude": latitude,
|
||||||
|
"longitude": longitude,
|
||||||
|
"radius": 500,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
assert len(self.hass.states.entity_ids("zone")) == 1
|
active = zone.async_active_zone(hass, latitude, longitude)
|
||||||
state = self.hass.states.get("zone.home")
|
assert "zone.small_zone" == active.entity_id
|
||||||
assert info["name"] == state.name
|
|
||||||
|
|
||||||
def test_setup_name_can_be_same_on_multiple_zones(self):
|
|
||||||
"""Test that zone named Home should override hass home zone."""
|
|
||||||
info = {"name": "Test Zone", "latitude": 1.1, "longitude": -2.2}
|
|
||||||
assert setup.setup_component(self.hass, zone.DOMAIN, {"zone": [info, info]})
|
|
||||||
assert len(self.hass.states.entity_ids("zone")) == 3
|
|
||||||
|
|
||||||
def test_setup_registered_zone_skips_home_zone(self):
|
async def test_active_zone_prefers_smaller_zone_if_same_distance_2(hass):
|
||||||
"""Test that config entry named home should override hass home zone."""
|
"""Test zone size preferences."""
|
||||||
entry = MockConfigEntry(domain=zone.DOMAIN, data={zone.CONF_NAME: "home"})
|
latitude = 32.880600
|
||||||
entry.add_to_hass(self.hass)
|
longitude = -117.237561
|
||||||
assert setup.setup_component(self.hass, zone.DOMAIN, {"zone": None})
|
assert await setup.async_setup_component(
|
||||||
assert len(self.hass.states.entity_ids("zone")) == 0
|
hass,
|
||||||
|
zone.DOMAIN,
|
||||||
|
{
|
||||||
|
"zone": [
|
||||||
|
{
|
||||||
|
"name": "Smallest Zone",
|
||||||
|
"latitude": latitude,
|
||||||
|
"longitude": longitude,
|
||||||
|
"radius": 50,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_setup_registered_zone_skips_configured_zone(self):
|
active = zone.async_active_zone(hass, latitude, longitude)
|
||||||
"""Test if config entry will override configured zone."""
|
assert "zone.smallest_zone" == active.entity_id
|
||||||
entry = MockConfigEntry(domain=zone.DOMAIN, data={zone.CONF_NAME: "Test Zone"})
|
|
||||||
entry.add_to_hass(self.hass)
|
|
||||||
info = {"name": "Test Zone", "latitude": 1.1, "longitude": -2.2}
|
|
||||||
assert setup.setup_component(self.hass, zone.DOMAIN, {"zone": info})
|
|
||||||
|
|
||||||
assert len(self.hass.states.entity_ids("zone")) == 1
|
|
||||||
state = self.hass.states.get("zone.test_zone")
|
|
||||||
assert not state
|
|
||||||
|
|
||||||
def test_active_zone_skips_passive_zones(self):
|
async def test_in_zone_works_for_passive_zones(hass):
|
||||||
"""Test active and passive zones."""
|
"""Test working in passive zones."""
|
||||||
assert setup.setup_component(
|
latitude = 32.880600
|
||||||
self.hass,
|
longitude = -117.237561
|
||||||
zone.DOMAIN,
|
assert await setup.async_setup_component(
|
||||||
{
|
hass,
|
||||||
"zone": [
|
zone.DOMAIN,
|
||||||
{
|
{
|
||||||
"name": "Passive Zone",
|
"zone": [
|
||||||
"latitude": 32.880600,
|
{
|
||||||
"longitude": -117.237561,
|
"name": "Passive Zone",
|
||||||
"radius": 250,
|
"latitude": latitude,
|
||||||
"passive": True,
|
"longitude": longitude,
|
||||||
}
|
"radius": 250,
|
||||||
]
|
"passive": True,
|
||||||
},
|
}
|
||||||
)
|
]
|
||||||
self.hass.block_till_done()
|
},
|
||||||
active = zone.async_active_zone(self.hass, 32.880600, -117.237561)
|
)
|
||||||
assert active is None
|
|
||||||
|
|
||||||
def test_active_zone_skips_passive_zones_2(self):
|
assert zone.in_zone(hass.states.get("zone.passive_zone"), latitude, longitude)
|
||||||
"""Test active and passive zones."""
|
|
||||||
assert setup.setup_component(
|
|
||||||
self.hass,
|
|
||||||
zone.DOMAIN,
|
|
||||||
{
|
|
||||||
"zone": [
|
|
||||||
{
|
|
||||||
"name": "Active Zone",
|
|
||||||
"latitude": 32.880800,
|
|
||||||
"longitude": -117.237561,
|
|
||||||
"radius": 500,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.hass.block_till_done()
|
|
||||||
active = zone.async_active_zone(self.hass, 32.880700, -117.237561)
|
|
||||||
assert "zone.active_zone" == active.entity_id
|
|
||||||
|
|
||||||
def test_active_zone_prefers_smaller_zone_if_same_distance(self):
|
|
||||||
"""Test zone size preferences."""
|
|
||||||
latitude = 32.880600
|
|
||||||
longitude = -117.237561
|
|
||||||
assert setup.setup_component(
|
|
||||||
self.hass,
|
|
||||||
zone.DOMAIN,
|
|
||||||
{
|
|
||||||
"zone": [
|
|
||||||
{
|
|
||||||
"name": "Small Zone",
|
|
||||||
"latitude": latitude,
|
|
||||||
"longitude": longitude,
|
|
||||||
"radius": 250,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Big Zone",
|
|
||||||
"latitude": latitude,
|
|
||||||
"longitude": longitude,
|
|
||||||
"radius": 500,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
active = zone.async_active_zone(self.hass, latitude, longitude)
|
|
||||||
assert "zone.small_zone" == active.entity_id
|
|
||||||
|
|
||||||
def test_active_zone_prefers_smaller_zone_if_same_distance_2(self):
|
|
||||||
"""Test zone size preferences."""
|
|
||||||
latitude = 32.880600
|
|
||||||
longitude = -117.237561
|
|
||||||
assert setup.setup_component(
|
|
||||||
self.hass,
|
|
||||||
zone.DOMAIN,
|
|
||||||
{
|
|
||||||
"zone": [
|
|
||||||
{
|
|
||||||
"name": "Smallest Zone",
|
|
||||||
"latitude": latitude,
|
|
||||||
"longitude": longitude,
|
|
||||||
"radius": 50,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
active = zone.async_active_zone(self.hass, latitude, longitude)
|
|
||||||
assert "zone.smallest_zone" == active.entity_id
|
|
||||||
|
|
||||||
def test_in_zone_works_for_passive_zones(self):
|
|
||||||
"""Test working in passive zones."""
|
|
||||||
latitude = 32.880600
|
|
||||||
longitude = -117.237561
|
|
||||||
assert setup.setup_component(
|
|
||||||
self.hass,
|
|
||||||
zone.DOMAIN,
|
|
||||||
{
|
|
||||||
"zone": [
|
|
||||||
{
|
|
||||||
"name": "Passive Zone",
|
|
||||||
"latitude": latitude,
|
|
||||||
"longitude": longitude,
|
|
||||||
"radius": 250,
|
|
||||||
"passive": True,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert zone.zone.in_zone(
|
|
||||||
self.hass.states.get("zone.passive_zone"), latitude, longitude
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_core_config_update(hass):
|
async def test_core_config_update(hass):
|
||||||
@ -243,3 +238,252 @@ async def test_core_config_update(hass):
|
|||||||
assert home_updated.name == "Updated Name"
|
assert home_updated.name == "Updated Name"
|
||||||
assert home_updated.attributes["latitude"] == 10
|
assert home_updated.attributes["latitude"] == 10
|
||||||
assert home_updated.attributes["longitude"] == 20
|
assert home_updated.attributes["longitude"] == 20
|
||||||
|
|
||||||
|
|
||||||
|
async def test_reload(hass, hass_admin_user, hass_read_only_user):
|
||||||
|
"""Test reload service."""
|
||||||
|
count_start = len(hass.states.async_entity_ids())
|
||||||
|
ent_reg = await entity_registry.async_get_registry(hass)
|
||||||
|
|
||||||
|
assert await setup.async_setup_component(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
{
|
||||||
|
DOMAIN: [
|
||||||
|
{"name": "yaml 1", "latitude": 1, "longitude": 2},
|
||||||
|
{"name": "yaml 2", "latitude": 3, "longitude": 4},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert count_start + 3 == len(hass.states.async_entity_ids())
|
||||||
|
|
||||||
|
state_1 = hass.states.get("zone.yaml_1")
|
||||||
|
state_2 = hass.states.get("zone.yaml_2")
|
||||||
|
state_3 = hass.states.get("zone.yaml_3")
|
||||||
|
|
||||||
|
assert state_1 is not None
|
||||||
|
assert state_1.attributes["latitude"] == 1
|
||||||
|
assert state_1.attributes["longitude"] == 2
|
||||||
|
assert state_2 is not None
|
||||||
|
assert state_2.attributes["latitude"] == 3
|
||||||
|
assert state_2.attributes["longitude"] == 4
|
||||||
|
assert state_3 is None
|
||||||
|
assert len(ent_reg.entities) == 0
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.config.load_yaml_config_file",
|
||||||
|
autospec=True,
|
||||||
|
return_value={
|
||||||
|
DOMAIN: [
|
||||||
|
{"name": "yaml 2", "latitude": 3, "longitude": 4},
|
||||||
|
{"name": "yaml 3", "latitude": 5, "longitude": 6},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
):
|
||||||
|
with pytest.raises(Unauthorized):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_RELOAD,
|
||||||
|
blocking=True,
|
||||||
|
context=Context(user_id=hass_read_only_user.id),
|
||||||
|
)
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_RELOAD,
|
||||||
|
blocking=True,
|
||||||
|
context=Context(user_id=hass_admin_user.id),
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert count_start + 3 == len(hass.states.async_entity_ids())
|
||||||
|
|
||||||
|
state_1 = hass.states.get("zone.yaml_1")
|
||||||
|
state_2 = hass.states.get("zone.yaml_2")
|
||||||
|
state_3 = hass.states.get("zone.yaml_3")
|
||||||
|
|
||||||
|
assert state_1 is None
|
||||||
|
assert state_2 is not None
|
||||||
|
assert state_2.attributes["latitude"] == 3
|
||||||
|
assert state_2.attributes["longitude"] == 4
|
||||||
|
assert state_3 is not None
|
||||||
|
assert state_3.attributes["latitude"] == 5
|
||||||
|
assert state_3.attributes["longitude"] == 6
|
||||||
|
|
||||||
|
|
||||||
|
async def test_load_from_storage(hass, storage_setup):
|
||||||
|
"""Test set up from storage."""
|
||||||
|
assert await storage_setup()
|
||||||
|
state = hass.states.get(f"{DOMAIN}.from_storage")
|
||||||
|
assert state.state == "zoning"
|
||||||
|
assert state.name == "from storage"
|
||||||
|
assert state.attributes.get(ATTR_EDITABLE)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_editable_state_attribute(hass, storage_setup):
|
||||||
|
"""Test editable attribute."""
|
||||||
|
assert await storage_setup(
|
||||||
|
config={DOMAIN: [{"name": "yaml option", "latitude": 3, "longitude": 4}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
state = hass.states.get(f"{DOMAIN}.from_storage")
|
||||||
|
assert state.state == "zoning"
|
||||||
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "from storage"
|
||||||
|
assert state.attributes.get(ATTR_EDITABLE)
|
||||||
|
|
||||||
|
state = hass.states.get(f"{DOMAIN}.yaml_option")
|
||||||
|
assert state.state == "zoning"
|
||||||
|
assert not state.attributes.get(ATTR_EDITABLE)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_ws_list(hass, hass_ws_client, storage_setup):
|
||||||
|
"""Test listing via WS."""
|
||||||
|
assert await storage_setup(
|
||||||
|
config={DOMAIN: [{"name": "yaml option", "latitude": 3, "longitude": 4}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
await client.send_json({"id": 6, "type": f"{DOMAIN}/list"})
|
||||||
|
resp = await client.receive_json()
|
||||||
|
assert resp["success"]
|
||||||
|
|
||||||
|
storage_ent = "from_storage"
|
||||||
|
yaml_ent = "from_yaml"
|
||||||
|
result = {item["id"]: item for item in resp["result"]}
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert storage_ent in result
|
||||||
|
assert yaml_ent not in result
|
||||||
|
assert result[storage_ent][ATTR_NAME] == "from storage"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_ws_delete(hass, hass_ws_client, storage_setup):
|
||||||
|
"""Test WS delete cleans up entity registry."""
|
||||||
|
assert await storage_setup()
|
||||||
|
|
||||||
|
input_id = "from_storage"
|
||||||
|
input_entity_id = f"{DOMAIN}.{input_id}"
|
||||||
|
ent_reg = await entity_registry.async_get_registry(hass)
|
||||||
|
|
||||||
|
state = hass.states.get(input_entity_id)
|
||||||
|
assert state is not None
|
||||||
|
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, input_id) is not None
|
||||||
|
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{"id": 6, "type": f"{DOMAIN}/delete", f"{DOMAIN}_id": f"{input_id}"}
|
||||||
|
)
|
||||||
|
resp = await client.receive_json()
|
||||||
|
assert resp["success"]
|
||||||
|
|
||||||
|
state = hass.states.get(input_entity_id)
|
||||||
|
assert state is None
|
||||||
|
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, input_id) is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update(hass, hass_ws_client, storage_setup):
|
||||||
|
"""Test updating min/max updates the state."""
|
||||||
|
|
||||||
|
items = [
|
||||||
|
{
|
||||||
|
"id": "from_storage",
|
||||||
|
"name": "from storage",
|
||||||
|
"latitude": 1,
|
||||||
|
"longitude": 2,
|
||||||
|
"radius": 3,
|
||||||
|
"passive": False,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
assert await storage_setup(items)
|
||||||
|
|
||||||
|
input_id = "from_storage"
|
||||||
|
input_entity_id = f"{DOMAIN}.{input_id}"
|
||||||
|
ent_reg = await entity_registry.async_get_registry(hass)
|
||||||
|
|
||||||
|
state = hass.states.get(input_entity_id)
|
||||||
|
assert state.attributes["latitude"] == 1
|
||||||
|
assert state.attributes["longitude"] == 2
|
||||||
|
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, input_id) is not None
|
||||||
|
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"type": f"{DOMAIN}/update",
|
||||||
|
f"{DOMAIN}_id": f"{input_id}",
|
||||||
|
"latitude": 3,
|
||||||
|
"longitude": 4,
|
||||||
|
"passive": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
resp = await client.receive_json()
|
||||||
|
assert resp["success"]
|
||||||
|
|
||||||
|
state = hass.states.get(input_entity_id)
|
||||||
|
assert state.attributes["latitude"] == 3
|
||||||
|
assert state.attributes["longitude"] == 4
|
||||||
|
assert state.attributes["passive"] is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_ws_create(hass, hass_ws_client, storage_setup):
|
||||||
|
"""Test create WS."""
|
||||||
|
assert await storage_setup(items=[])
|
||||||
|
|
||||||
|
input_id = "new_input"
|
||||||
|
input_entity_id = f"{DOMAIN}.{input_id}"
|
||||||
|
ent_reg = await entity_registry.async_get_registry(hass)
|
||||||
|
|
||||||
|
state = hass.states.get(input_entity_id)
|
||||||
|
assert state is None
|
||||||
|
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, input_id) is None
|
||||||
|
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"type": f"{DOMAIN}/create",
|
||||||
|
"name": "New Input",
|
||||||
|
"latitude": 3,
|
||||||
|
"longitude": 4,
|
||||||
|
"passive": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
resp = await client.receive_json()
|
||||||
|
assert resp["success"]
|
||||||
|
|
||||||
|
state = hass.states.get(input_entity_id)
|
||||||
|
assert state.state == "zoning"
|
||||||
|
assert state.attributes["latitude"] == 3
|
||||||
|
assert state.attributes["longitude"] == 4
|
||||||
|
assert state.attributes["passive"] is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import_config_entry(hass):
|
||||||
|
"""Test we import config entry and then delete it."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain="zone",
|
||||||
|
data={
|
||||||
|
"name": "from config entry",
|
||||||
|
"latitude": 1,
|
||||||
|
"longitude": 2,
|
||||||
|
"radius": 3,
|
||||||
|
"passive": False,
|
||||||
|
"icon": "mdi:from-config-entry",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
assert await setup.async_setup_component(hass, DOMAIN, {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.config_entries.async_entries()) == 0
|
||||||
|
|
||||||
|
state = hass.states.get("zone.from_config_entry")
|
||||||
|
assert state is not None
|
||||||
|
assert state.attributes[zone.ATTR_LATITUDE] == 1
|
||||||
|
assert state.attributes[zone.ATTR_LONGITUDE] == 2
|
||||||
|
assert state.attributes[zone.ATTR_RADIUS] == 3
|
||||||
|
assert state.attributes[zone.ATTR_PASSIVE] is False
|
||||||
|
assert state.attributes[ATTR_ICON] == "mdi:from-config-entry"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user