diff --git a/homeassistant/components/owntracks/messages.py b/homeassistant/components/owntracks/messages.py index d357843c42e..7fab391efc1 100644 --- a/homeassistant/components/owntracks/messages.py +++ b/homeassistant/components/owntracks/messages.py @@ -271,8 +271,17 @@ async def async_handle_waypoint(hass, name_base, waypoint): return 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 await zone.async_update_ha_state() diff --git a/homeassistant/components/zone/.translations/bg.json b/homeassistant/components/zone/.translations/bg.json deleted file mode 100644 index 5770058c5eb..00000000000 --- a/homeassistant/components/zone/.translations/bg.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/ca.json b/homeassistant/components/zone/.translations/ca.json deleted file mode 100644 index aa8296b92df..00000000000 --- a/homeassistant/components/zone/.translations/ca.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/cs.json b/homeassistant/components/zone/.translations/cs.json deleted file mode 100644 index a521377e5e0..00000000000 --- a/homeassistant/components/zone/.translations/cs.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/cy.json b/homeassistant/components/zone/.translations/cy.json deleted file mode 100644 index e34fae81b61..00000000000 --- a/homeassistant/components/zone/.translations/cy.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/da.json b/homeassistant/components/zone/.translations/da.json deleted file mode 100644 index c6981f242d2..00000000000 --- a/homeassistant/components/zone/.translations/da.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/de.json b/homeassistant/components/zone/.translations/de.json deleted file mode 100644 index 483c7f065a3..00000000000 --- a/homeassistant/components/zone/.translations/de.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/en.json b/homeassistant/components/zone/.translations/en.json deleted file mode 100644 index 1faf0110a53..00000000000 --- a/homeassistant/components/zone/.translations/en.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/es-419.json b/homeassistant/components/zone/.translations/es-419.json deleted file mode 100644 index b15be44b7b1..00000000000 --- a/homeassistant/components/zone/.translations/es-419.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/es.json b/homeassistant/components/zone/.translations/es.json deleted file mode 100644 index 7a0f6c967c2..00000000000 --- a/homeassistant/components/zone/.translations/es.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/et.json b/homeassistant/components/zone/.translations/et.json deleted file mode 100644 index aa921f376e7..00000000000 --- a/homeassistant/components/zone/.translations/et.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "config": { - "step": { - "init": { - "data": { - "icon": "Ikoon", - "latitude": "Laius", - "longitude": "Pikkus", - "name": "Nimi", - "radius": "Raadius" - }, - "title": "M\u00e4\u00e4ra tsooni parameetrid" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/fr.json b/homeassistant/components/zone/.translations/fr.json deleted file mode 100644 index eb02aba7b50..00000000000 --- a/homeassistant/components/zone/.translations/fr.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/he.json b/homeassistant/components/zone/.translations/he.json deleted file mode 100644 index b6a2a30b625..00000000000 --- a/homeassistant/components/zone/.translations/he.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/hr.json b/homeassistant/components/zone/.translations/hr.json deleted file mode 100644 index 8a9f543be0a..00000000000 --- a/homeassistant/components/zone/.translations/hr.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/hu.json b/homeassistant/components/zone/.translations/hu.json deleted file mode 100644 index 0181f688c27..00000000000 --- a/homeassistant/components/zone/.translations/hu.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/id.json b/homeassistant/components/zone/.translations/id.json deleted file mode 100644 index b84710dc408..00000000000 --- a/homeassistant/components/zone/.translations/id.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/it.json b/homeassistant/components/zone/.translations/it.json deleted file mode 100644 index 24de27a8bbb..00000000000 --- a/homeassistant/components/zone/.translations/it.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/ja.json b/homeassistant/components/zone/.translations/ja.json deleted file mode 100644 index 093f5ad9938..00000000000 --- a/homeassistant/components/zone/.translations/ja.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "config": { - "step": { - "init": { - "data": { - "latitude": "\u7def\u5ea6", - "longitude": "\u7d4c\u5ea6", - "name": "\u540d\u524d" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/ko.json b/homeassistant/components/zone/.translations/ko.json deleted file mode 100644 index 421f079a67e..00000000000 --- a/homeassistant/components/zone/.translations/ko.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/lb.json b/homeassistant/components/zone/.translations/lb.json deleted file mode 100644 index 10b65bcca30..00000000000 --- a/homeassistant/components/zone/.translations/lb.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/nl.json b/homeassistant/components/zone/.translations/nl.json deleted file mode 100644 index 6dcf565ada6..00000000000 --- a/homeassistant/components/zone/.translations/nl.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/nn.json b/homeassistant/components/zone/.translations/nn.json deleted file mode 100644 index 39161f98c82..00000000000 --- a/homeassistant/components/zone/.translations/nn.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/no.json b/homeassistant/components/zone/.translations/no.json deleted file mode 100644 index 3c1a91976f0..00000000000 --- a/homeassistant/components/zone/.translations/no.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/pl.json b/homeassistant/components/zone/.translations/pl.json deleted file mode 100644 index e649de4c75e..00000000000 --- a/homeassistant/components/zone/.translations/pl.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/pt-BR.json b/homeassistant/components/zone/.translations/pt-BR.json deleted file mode 100644 index f2a41b0b267..00000000000 --- a/homeassistant/components/zone/.translations/pt-BR.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/pt.json b/homeassistant/components/zone/.translations/pt.json deleted file mode 100644 index 2c3292e58c1..00000000000 --- a/homeassistant/components/zone/.translations/pt.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/ru.json b/homeassistant/components/zone/.translations/ru.json deleted file mode 100644 index 6a017e9e1c3..00000000000 --- a/homeassistant/components/zone/.translations/ru.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/sl.json b/homeassistant/components/zone/.translations/sl.json deleted file mode 100644 index 1885cb5d2c8..00000000000 --- a/homeassistant/components/zone/.translations/sl.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/sv.json b/homeassistant/components/zone/.translations/sv.json deleted file mode 100644 index 55c5bcf7127..00000000000 --- a/homeassistant/components/zone/.translations/sv.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/th.json b/homeassistant/components/zone/.translations/th.json deleted file mode 100644 index e39765f2da2..00000000000 --- a/homeassistant/components/zone/.translations/th.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/uk.json b/homeassistant/components/zone/.translations/uk.json deleted file mode 100644 index ce082d34a1c..00000000000 --- a/homeassistant/components/zone/.translations/uk.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/vi.json b/homeassistant/components/zone/.translations/vi.json deleted file mode 100644 index 7217944bd6b..00000000000 --- a/homeassistant/components/zone/.translations/vi.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/zh-Hans.json b/homeassistant/components/zone/.translations/zh-Hans.json deleted file mode 100644 index 6d06b68dad8..00000000000 --- a/homeassistant/components/zone/.translations/zh-Hans.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/zh-Hant.json b/homeassistant/components/zone/.translations/zh-Hant.json deleted file mode 100644 index 12c1141397d..00000000000 --- a/homeassistant/components/zone/.translations/zh-Hant.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index e88993beee8..91a1338b671 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -1,36 +1,41 @@ """Support for the definition of zones.""" import logging -from typing import Set, cast +from typing import Dict, List, Optional, cast import voluptuous as vol +from homeassistant import config_entries from homeassistant.const import ( + ATTR_EDITABLE, + ATTR_HIDDEN, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ICON, + CONF_ID, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_RADIUS, 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.util import slugify 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 .zone import Zone - -# mypy: allow-untyped-calls, allow-untyped-defs _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = "Unnamed zone" DEFAULT_PASSIVE = False DEFAULT_RADIUS = 100 @@ -40,29 +45,47 @@ ENTITY_ID_HOME = ENTITY_ID_FORMAT.format(HOME_ZONE) ICON_HOME = "mdi:home" ICON_IMPORT = "mdi:import" -# The config that zone accepts is the same as if it has platforms. -PLATFORM_SCHEMA = vol.Schema( - { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_LATITUDE): cv.latitude, - vol.Required(CONF_LONGITUDE): cv.longitude, - vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS): vol.Coerce(float), - vol.Optional(CONF_PASSIVE, default=DEFAULT_PASSIVE): cv.boolean, - vol.Optional(CONF_ICON): cv.icon, - }, +CREATE_FIELDS = { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_LATITUDE): cv.latitude, + vol.Required(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS): vol.Coerce(float), + 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, ) +RELOAD_SERVICE_SCHEMA = vol.Schema({}) +STORAGE_KEY = DOMAIN +STORAGE_VERSION = 1 + @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. This method must be run in the event loop. """ # Sort entity IDs so that we are deterministic if equal distance to 2 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)) ) @@ -80,6 +103,9 @@ def async_active_zone(hass, latitude, longitude, radius=0): zone.attributes[ATTR_LONGITUDE], ) + if zone_dist is None: + continue + within_zone = zone_dist - radius < zone.attributes[ATTR_RADIUS] closer_zone = closest is None or zone_dist < min_dist # type: ignore smaller_zone = ( @@ -95,79 +121,227 @@ def async_active_zone(hass, latitude, longitude, radius=0): return closest -async def async_setup(hass, config): - """Set up configured zones as well as Home Assistant zone if necessary.""" - 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) +def in_zone(zone: State, latitude: float, longitude: float, radius: float = 0) -> bool: + """Test if given latitude, longitude is in given zone. - if ENTITY_ID_HOME in entities or HOME_ZONE in zone_entries: - return True - - zone = Zone( - hass, - hass.config.location_name, - hass.config.latitude, - hass.config.longitude, - DEFAULT_RADIUS, - ICON_HOME, - False, + Async friendly. + """ + zone_dist = distance( + latitude, + longitude, + zone.attributes[ATTR_LATITUDE], + zone.attributes[ATTR_LONGITUDE], ) - 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 - 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.""" - zone.name = hass.config.location_name - zone.latitude = hass.config.latitude - zone.longitude = hass.config.longitude - zone.async_write_ha_state() + await home_zone.async_update_config(_home_conf(hass)) hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, core_config_updated) + hass.data[DOMAIN] = storage_collection + 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.""" - entry = config_entry.data - name = entry[CONF_NAME] - zone = Zone( - hass, - name, - entry[CONF_LATITUDE], - entry[CONF_LONGITUDE], - entry.get(CONF_RADIUS, DEFAULT_RADIUS), - entry.get(CONF_ICON), - 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 + storage_collection = cast(ZoneStorageCollection, hass.data[DOMAIN]) + + data = dict(config_entry.data) + data.setdefault(CONF_PASSIVE, DEFAULT_PASSIVE) + data.setdefault(CONF_RADIUS, DEFAULT_RADIUS) + + await storage_collection.async_create_item(data) + + hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id)) + return True -async def async_unload_entry(hass, config_entry): - """Unload a config entry.""" - zones = hass.data[DOMAIN] - name = slugify(config_entry.data[CONF_NAME]) - zone = zones.pop(name) - await zone.async_remove() +async def async_unload_entry( + hass: HomeAssistant, config_entry: config_entries.ConfigEntry +) -> bool: + """Will be called once we remove it.""" 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, + } diff --git a/homeassistant/components/zone/config_flow.py b/homeassistant/components/zone/config_flow.py index 4531ff7b834..bb34a83ad26 100644 --- a/homeassistant/components/zone/config_flow.py +++ b/homeassistant/components/zone/config_flow.py @@ -1,75 +1,13 @@ -"""Config flow to configure zone component.""" - -from typing import Set - -import voluptuous as vol +"""Config flow to configure zone component. +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.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 - -# mypy: allow-untyped-defs, no-check-untyped-defs +from .const import DOMAIN # noqa # pylint:disable=unused-import -@callback -def configured_zones(hass: HomeAssistantType) -> Set[str]: - """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, - ) +class ZoneConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Stub zone config flow class.""" diff --git a/homeassistant/components/zone/manifest.json b/homeassistant/components/zone/manifest.json index 8efed9ba7a6..d45399c3f31 100644 --- a/homeassistant/components/zone/manifest.json +++ b/homeassistant/components/zone/manifest.json @@ -1,7 +1,7 @@ { "domain": "zone", "name": "Zone", - "config_flow": true, + "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/zone", "requirements": [], "dependencies": [], diff --git a/homeassistant/components/zone/strings.json b/homeassistant/components/zone/strings.json deleted file mode 100644 index ff2c7c07c14..00000000000 --- a/homeassistant/components/zone/strings.json +++ /dev/null @@ -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" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/zone/zone.py b/homeassistant/components/zone/zone.py deleted file mode 100644 index f084492bd34..00000000000 --- a/homeassistant/components/zone/zone.py +++ /dev/null @@ -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 diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 3886dfd2f20..2a013b16ae2 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -98,6 +98,5 @@ FLOWS = [ "wled", "wwlln", "zha", - "zone", "zwave" ] diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index 80790a6d831..1b3721788f5 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -114,7 +114,7 @@ class ObservableCollection(ABC): 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: """Load the YAML collection. Overrides existing data.""" @@ -133,7 +133,7 @@ class YamlCollection(ObservableCollection): event = CHANGE_ADDED 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: self.data.pop(item_id) @@ -246,7 +246,7 @@ def attach_entity_component_collection( """Handle a collection change.""" if change_type == CHANGE_ADDED: 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 return diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 02853f7615b..c3d09853960 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -473,7 +473,7 @@ def zone( if latitude is None or longitude is None: return False - return zone_cmp.zone.in_zone( + return zone_cmp.in_zone( zone_ent, latitude, longitude, entity.attributes.get(ATTR_GPS_ACCURACY, 0) ) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 7ccc6c35613..b9d1a73351c 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -284,7 +284,7 @@ class Entity(ABC): self._async_write_ha_state() @callback - def async_write_ha_state(self): + def async_write_ha_state(self) -> None: """Write the state to the state machine.""" if self.hass is None: 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}" ) - self._async_write_ha_state() + self._async_write_ha_state() # type: ignore @callback def _async_write_ha_state(self): diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 733cb22b3b2..e26dc5dfbea 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -3,6 +3,8 @@ import asyncio from datetime import timedelta from itertools import chain import logging +from types import ModuleType +from typing import Dict, Optional, cast from homeassistant import config as conf_util from homeassistant.config_entries import ConfigEntry @@ -13,6 +15,7 @@ from homeassistant.helpers import ( config_per_platform, config_validation as cv, discovery, + entity, service, ) 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 - 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( "Forced update failed. Entity %s not found.", entity_id ) return - await entity.async_update_ha_state(True) + await entity_obj.async_update_ha_state(True) class EntityComponent: @@ -59,7 +62,13 @@ class EntityComponent: - 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.""" self.logger = logger self.hass = hass @@ -68,7 +77,9 @@ class EntityComponent: 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.add_entities = self._platforms[domain].add_entities @@ -81,12 +92,12 @@ class EntityComponent: 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.""" for platform in self._platforms.values(): - entity = platform.entities.get(entity_id) - if entity is not None: - return entity + entity_obj = cast(Optional[entity.Entity], platform.entities.get(entity_id)) + if entity_obj is not None: + return entity_obj return None def setup(self, config): @@ -237,7 +248,7 @@ class EntityComponent: if entity_id in platform.entities: 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. This method must be run in the event loop. @@ -250,25 +261,30 @@ class EntityComponent: 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 ) - if conf is None: + if processed_conf is None: return None if not skip_reset: await self._async_reset() - return conf + + return processed_conf 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.""" if scan_interval is None: scan_interval = self.scan_interval - return EntityPlatform( + return EntityPlatform( # type: ignore hass=self.hass, logger=self.logger, domain=self.domain, diff --git a/tests/components/proximity/test_init.py b/tests/components/proximity/test_init.py index a01d625d8c4..826a73bece2 100644 --- a/tests/components/proximity/test_init.py +++ b/tests/components/proximity/test_init.py @@ -14,6 +14,7 @@ class TestProximity(unittest.TestCase): def setUp(self): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() + self.hass.config.components.add("zone") self.hass.states.set( "zone.home", "zoning", @@ -211,7 +212,7 @@ class TestProximity(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get("proximity.home") 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): """Test for tracker state away closer.""" @@ -245,7 +246,7 @@ class TestProximity(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get("proximity.home") 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): """Test for tracker in ignored zone.""" diff --git a/tests/components/zone/test_config_flow.py b/tests/components/zone/test_config_flow.py deleted file mode 100644 index 5f57e8b4064..00000000000 --- a/tests/components/zone/test_config_flow.py +++ /dev/null @@ -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"} diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py index d4a76463c18..0835b77579a 100644 --- a/tests/components/zone/test_init.py +++ b/tests/components/zone/test_init.py @@ -1,229 +1,224 @@ """Test zone component.""" - -import unittest -from unittest.mock import Mock +from asynctest import patch +import pytest from homeassistant import setup 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): - """Test setup entry is successful.""" - entry = Mock() - entry.data = { - zone.CONF_NAME: "Test Zone", - zone.CONF_LATITUDE: 1.1, - zone.CONF_LONGITUDE: -2.2, - zone.CONF_RADIUS: True, +@pytest.fixture +def storage_setup(hass, hass_storage): + """Storage setup.""" + + async def _storage(items=None, config=None): + if items is None: + hass_storage[DOMAIN] = { + "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 zone.async_setup_entry(hass, entry) is True - assert "test_zone" in hass.data[zone.DOMAIN] + assert await setup.async_setup_component(hass, zone.DOMAIN, {"zone": info}) + + 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): - """Test unload entry is successful.""" - entry = Mock() - entry.data = { - zone.CONF_NAME: "Test Zone", - zone.CONF_LATITUDE: 1.1, - zone.CONF_LONGITUDE: -2.2, - } - 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] +async def test_setup_zone_skips_home_zone(hass): + """Test that zone named Home should override hass home zone.""" + info = {"name": "Home", "latitude": 1.1, "longitude": -2.2} + assert await setup.async_setup_component(hass, zone.DOMAIN, {"zone": info}) + + assert len(hass.states.async_entity_ids("zone")) == 1 + state = hass.states.get("zone.home") + assert info["name"] == state.name -class TestComponentZone(unittest.TestCase): - """Test the zone component.""" +async def test_setup_name_can_be_same_on_multiple_zones(hass): + """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 - """Stop down everything that was started.""" - self.hass.stop() +async def test_active_zone_skips_passive_zones(hass): + """Test active and passive zones.""" + 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): - """Test a successful setup.""" - info = { - "name": "Test Zone", - "latitude": 32.880837, - "longitude": -117.237561, - "radius": 250, - "passive": True, - } - assert setup.setup_component(self.hass, zone.DOMAIN, {"zone": info}) +async def test_active_zone_skips_passive_zones_2(hass): + """Test active and passive zones.""" + assert await setup.async_setup_component( + hass, + zone.DOMAIN, + { + "zone": [ + { + "name": "Active Zone", + "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): - """Test that zone named Home should override hass home zone.""" - info = {"name": "Home", "latitude": 1.1, "longitude": -2.2} - assert setup.setup_component(self.hass, zone.DOMAIN, {"zone": info}) +async def test_active_zone_prefers_smaller_zone_if_same_distance(hass): + """Test zone size preferences.""" + latitude = 32.880600 + 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 - state = self.hass.states.get("zone.home") - assert info["name"] == state.name + active = zone.async_active_zone(hass, latitude, longitude) + assert "zone.small_zone" == active.entity_id - 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): - """Test that config entry named home should override hass home zone.""" - entry = MockConfigEntry(domain=zone.DOMAIN, data={zone.CONF_NAME: "home"}) - entry.add_to_hass(self.hass) - assert setup.setup_component(self.hass, zone.DOMAIN, {"zone": None}) - assert len(self.hass.states.entity_ids("zone")) == 0 +async def test_active_zone_prefers_smaller_zone_if_same_distance_2(hass): + """Test zone size preferences.""" + latitude = 32.880600 + longitude = -117.237561 + assert await setup.async_setup_component( + hass, + zone.DOMAIN, + { + "zone": [ + { + "name": "Smallest Zone", + "latitude": latitude, + "longitude": longitude, + "radius": 50, + } + ] + }, + ) - def test_setup_registered_zone_skips_configured_zone(self): - """Test if config entry will override configured zone.""" - 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}) + active = zone.async_active_zone(hass, latitude, longitude) + assert "zone.smallest_zone" == active.entity_id - 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): - """Test active and passive zones.""" - assert setup.setup_component( - self.hass, - zone.DOMAIN, - { - "zone": [ - { - "name": "Passive Zone", - "latitude": 32.880600, - "longitude": -117.237561, - "radius": 250, - "passive": True, - } - ] - }, - ) - self.hass.block_till_done() - active = zone.async_active_zone(self.hass, 32.880600, -117.237561) - assert active is None +async def test_in_zone_works_for_passive_zones(hass): + """Test working in passive zones.""" + latitude = 32.880600 + longitude = -117.237561 + assert await setup.async_setup_component( + hass, + zone.DOMAIN, + { + "zone": [ + { + "name": "Passive Zone", + "latitude": latitude, + "longitude": longitude, + "radius": 250, + "passive": True, + } + ] + }, + ) - def test_active_zone_skips_passive_zones_2(self): - """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 - ) + assert zone.in_zone(hass.states.get("zone.passive_zone"), latitude, longitude) 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.attributes["latitude"] == 10 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"