mirror of
https://github.com/home-assistant/core.git
synced 2025-10-11 04:39:31 +00:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4be9766498 | ||
![]() |
4080d6a822 | ||
![]() |
6d06844318 | ||
![]() |
a150d6dcf3 | ||
![]() |
a0390783bb | ||
![]() |
91b10e875f | ||
![]() |
f04969cf30 | ||
![]() |
cdde5a37cd | ||
![]() |
a0403a8864 | ||
![]() |
bfaad97318 | ||
![]() |
d6c15d2f45 | ||
![]() |
815502044e | ||
![]() |
08f5b49dc4 | ||
![]() |
fab55b0ea2 | ||
![]() |
649ec2fc8e | ||
![]() |
21e0df42ac | ||
![]() |
f7f9126610 | ||
![]() |
52809396d4 | ||
![]() |
121d967732 | ||
![]() |
3b147bcbf7 | ||
![]() |
ca81c6e684 | ||
![]() |
e62ba49979 | ||
![]() |
6ea20090a4 | ||
![]() |
c43b7d10d8 | ||
![]() |
430fa24acd | ||
![]() |
d51b2ad675 | ||
![]() |
b8fbe758d8 | ||
![]() |
61476f4f2c | ||
![]() |
cab60bcd0c | ||
![]() |
c0394232f3 | ||
![]() |
a5d9e89d08 | ||
![]() |
f43b26f250 | ||
![]() |
58b32bbeff | ||
![]() |
6d0a465390 | ||
![]() |
a5d334bbf7 | ||
![]() |
a77fd4892e |
@@ -68,6 +68,7 @@ homeassistant/components/config/* @home-assistant/core
|
|||||||
homeassistant/components/configurator/* @home-assistant/core
|
homeassistant/components/configurator/* @home-assistant/core
|
||||||
homeassistant/components/conversation/* @home-assistant/core
|
homeassistant/components/conversation/* @home-assistant/core
|
||||||
homeassistant/components/coolmaster/* @OnFreund
|
homeassistant/components/coolmaster/* @OnFreund
|
||||||
|
homeassistant/components/coronavirus/* @home_assistant/core
|
||||||
homeassistant/components/counter/* @fabaff
|
homeassistant/components/counter/* @fabaff
|
||||||
homeassistant/components/cover/* @home-assistant/core
|
homeassistant/components/cover/* @home-assistant/core
|
||||||
homeassistant/components/cpuspeed/* @fabaff
|
homeassistant/components/cpuspeed/* @fabaff
|
||||||
|
@@ -41,7 +41,7 @@ stages:
|
|||||||
jq curl
|
jq curl
|
||||||
|
|
||||||
release="$(Build.SourceBranchName)"
|
release="$(Build.SourceBranchName)"
|
||||||
created_by="$(curl -s https://api.github.com/repos/home-assistant/home-assistant/releases/tags/${release} | jq --raw-output '.author.login')"
|
created_by="$(curl -s https://api.github.com/repos/home-assistant/core/releases/tags/${release} | jq --raw-output '.author.login')"
|
||||||
|
|
||||||
if [[ "${created_by}" =~ ^(balloob|pvizeli|fabaff|robbiet480|bramkragten|frenck)$ ]]; then
|
if [[ "${created_by}" =~ ^(balloob|pvizeli|fabaff|robbiet480|bramkragten|frenck)$ ]]; then
|
||||||
exit 0
|
exit 0
|
||||||
|
13
homeassistant/components/coronavirus/.translations/en.json
Normal file
13
homeassistant/components/coronavirus/.translations/en.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"country": "Country"
|
||||||
|
},
|
||||||
|
"title": "Pick a country to monitor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Coronavirus"
|
||||||
|
}
|
||||||
|
}
|
95
homeassistant/components/coronavirus/__init__.py
Normal file
95
homeassistant/components/coronavirus/__init__.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
"""The Coronavirus integration."""
|
||||||
|
import asyncio
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import async_timeout
|
||||||
|
import coronavirus
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers import aiohttp_client, entity_registry, update_coordinator
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
PLATFORMS = ["sensor"]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: dict):
|
||||||
|
"""Set up the Coronavirus component."""
|
||||||
|
# Make sure coordinator is initialized.
|
||||||
|
await get_coordinator(hass)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Set up Coronavirus from a config entry."""
|
||||||
|
if isinstance(entry.data["country"], int):
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
entry, data={**entry.data, "country": entry.title}
|
||||||
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_migrator(entity_entry: entity_registry.RegistryEntry):
|
||||||
|
"""Migrate away from unstable ID."""
|
||||||
|
country, info_type = entity_entry.unique_id.rsplit("-", 1)
|
||||||
|
if not country.isnumeric():
|
||||||
|
return None
|
||||||
|
return {"new_unique_id": f"{entry.title}-{info_type}"}
|
||||||
|
|
||||||
|
await entity_registry.async_migrate_entries(
|
||||||
|
hass, entry.entry_id, _async_migrator
|
||||||
|
)
|
||||||
|
|
||||||
|
if not entry.unique_id:
|
||||||
|
hass.config_entries.async_update_entry(entry, unique_id=entry.data["country"])
|
||||||
|
|
||||||
|
for component in PLATFORMS:
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
unload_ok = all(
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||||
|
for component in PLATFORMS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
async def get_coordinator(hass):
|
||||||
|
"""Get the data update coordinator."""
|
||||||
|
if DOMAIN in hass.data:
|
||||||
|
return hass.data[DOMAIN]
|
||||||
|
|
||||||
|
async def async_get_cases():
|
||||||
|
try:
|
||||||
|
with async_timeout.timeout(10):
|
||||||
|
return {
|
||||||
|
case.country: case
|
||||||
|
for case in await coronavirus.get_cases(
|
||||||
|
aiohttp_client.async_get_clientsession(hass)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
except (asyncio.TimeoutError, aiohttp.ClientError):
|
||||||
|
raise update_coordinator.UpdateFailed
|
||||||
|
|
||||||
|
hass.data[DOMAIN] = update_coordinator.DataUpdateCoordinator(
|
||||||
|
hass,
|
||||||
|
logging.getLogger(__name__),
|
||||||
|
name=DOMAIN,
|
||||||
|
update_method=async_get_cases,
|
||||||
|
update_interval=timedelta(hours=1),
|
||||||
|
)
|
||||||
|
await hass.data[DOMAIN].async_refresh()
|
||||||
|
return hass.data[DOMAIN]
|
45
homeassistant/components/coronavirus/config_flow.py
Normal file
45
homeassistant/components/coronavirus/config_flow.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
"""Config flow for Coronavirus integration."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
|
||||||
|
from . import get_coordinator
|
||||||
|
from .const import DOMAIN, OPTION_WORLDWIDE # pylint:disable=unused-import
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Coronavirus."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||||
|
|
||||||
|
_options = None
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle the initial step."""
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if self._options is None:
|
||||||
|
self._options = {OPTION_WORLDWIDE: "Worldwide"}
|
||||||
|
coordinator = await get_coordinator(self.hass)
|
||||||
|
for case in sorted(
|
||||||
|
coordinator.data.values(), key=lambda case: case.country
|
||||||
|
):
|
||||||
|
self._options[case.country] = case.country
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
await self.async_set_unique_id(user_input["country"])
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=self._options[user_input["country"]], data=user_input
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=vol.Schema({vol.Required("country"): vol.In(self._options)}),
|
||||||
|
errors=errors,
|
||||||
|
)
|
6
homeassistant/components/coronavirus/const.py
Normal file
6
homeassistant/components/coronavirus/const.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
"""Constants for the Coronavirus integration."""
|
||||||
|
from coronavirus import DEFAULT_SOURCE
|
||||||
|
|
||||||
|
DOMAIN = "coronavirus"
|
||||||
|
OPTION_WORLDWIDE = "__worldwide"
|
||||||
|
ATTRIBUTION = f"Data provided by {DEFAULT_SOURCE.NAME}"
|
12
homeassistant/components/coronavirus/manifest.json
Normal file
12
homeassistant/components/coronavirus/manifest.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"domain": "coronavirus",
|
||||||
|
"name": "Coronavirus (COVID-19)",
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/coronavirus",
|
||||||
|
"requirements": ["coronavirus==1.0.1"],
|
||||||
|
"ssdp": [],
|
||||||
|
"zeroconf": [],
|
||||||
|
"homekit": {},
|
||||||
|
"dependencies": [],
|
||||||
|
"codeowners": ["@home_assistant/core"]
|
||||||
|
}
|
69
homeassistant/components/coronavirus/sensor.py
Normal file
69
homeassistant/components/coronavirus/sensor.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
"""Sensor platform for the Corona virus."""
|
||||||
|
from homeassistant.const import ATTR_ATTRIBUTION
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
from . import get_coordinator
|
||||||
|
from .const import ATTRIBUTION, OPTION_WORLDWIDE
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Defer sensor setup to the shared sensor module."""
|
||||||
|
coordinator = await get_coordinator(hass)
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
CoronavirusSensor(coordinator, config_entry.data["country"], info_type)
|
||||||
|
for info_type in ("confirmed", "recovered", "deaths", "current")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CoronavirusSensor(Entity):
|
||||||
|
"""Sensor representing corona virus data."""
|
||||||
|
|
||||||
|
name = None
|
||||||
|
unique_id = None
|
||||||
|
|
||||||
|
def __init__(self, coordinator, country, info_type):
|
||||||
|
"""Initialize coronavirus sensor."""
|
||||||
|
if country == OPTION_WORLDWIDE:
|
||||||
|
self.name = f"Worldwide Coronavirus {info_type}"
|
||||||
|
else:
|
||||||
|
self.name = f"{coordinator.data[country].country} Coronavirus {info_type}"
|
||||||
|
self.unique_id = f"{country}-{info_type}"
|
||||||
|
self.coordinator = coordinator
|
||||||
|
self.country = country
|
||||||
|
self.info_type = info_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return if sensor is available."""
|
||||||
|
return self.coordinator.last_update_success and (
|
||||||
|
self.country in self.coordinator.data or self.country == OPTION_WORLDWIDE
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""State of the sensor."""
|
||||||
|
if self.country == OPTION_WORLDWIDE:
|
||||||
|
return sum(
|
||||||
|
getattr(case, self.info_type) for case in self.coordinator.data.values()
|
||||||
|
)
|
||||||
|
|
||||||
|
return getattr(self.coordinator.data[self.country], self.info_type)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return unit of measurement."""
|
||||||
|
return "people"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return device attributes."""
|
||||||
|
return {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""When entity is added to hass."""
|
||||||
|
self.coordinator.async_add_listener(self.async_write_ha_state)
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self):
|
||||||
|
"""When entity will be removed from hass."""
|
||||||
|
self.coordinator.async_remove_listener(self.async_write_ha_state)
|
16
homeassistant/components/coronavirus/strings.json
Normal file
16
homeassistant/components/coronavirus/strings.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"title": "Coronavirus",
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Pick a country to monitor",
|
||||||
|
"data": {
|
||||||
|
"country": "Country"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "This country is already configured."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -37,7 +37,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
gateway.option_allow_clip_sensor
|
gateway.option_allow_clip_sensor
|
||||||
or not sensor.type.startswith("CLIP")
|
or not sensor.type.startswith("CLIP")
|
||||||
)
|
)
|
||||||
and sensor.deconz_id not in gateway.deconz_ids.values()
|
|
||||||
):
|
):
|
||||||
entities.append(DeconzBinarySensor(sensor, gateway))
|
entities.append(DeconzBinarySensor(sensor, gateway))
|
||||||
|
|
||||||
|
@@ -44,7 +44,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
gateway.option_allow_clip_sensor
|
gateway.option_allow_clip_sensor
|
||||||
or not sensor.type.startswith("CLIP")
|
or not sensor.type.startswith("CLIP")
|
||||||
)
|
)
|
||||||
and sensor.deconz_id not in gateway.deconz_ids.values()
|
|
||||||
):
|
):
|
||||||
entities.append(DeconzThermostat(sensor, gateway))
|
entities.append(DeconzThermostat(sensor, gateway))
|
||||||
|
|
||||||
|
@@ -67,7 +67,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
entities = []
|
entities = []
|
||||||
|
|
||||||
for group in groups:
|
for group in groups:
|
||||||
if group.lights and group.deconz_id not in gateway.deconz_ids.values():
|
if group.lights:
|
||||||
entities.append(DeconzGroup(group, gateway))
|
entities.append(DeconzGroup(group, gateway))
|
||||||
|
|
||||||
async_add_entities(entities, True)
|
async_add_entities(entities, True)
|
||||||
|
@@ -68,7 +68,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
gateway.option_allow_clip_sensor
|
gateway.option_allow_clip_sensor
|
||||||
or not sensor.type.startswith("CLIP")
|
or not sensor.type.startswith("CLIP")
|
||||||
)
|
)
|
||||||
and sensor.deconz_id not in gateway.deconz_ids.values()
|
|
||||||
):
|
):
|
||||||
entities.append(DeconzSensor(sensor, gateway))
|
entities.append(DeconzSensor(sensor, gateway))
|
||||||
|
|
||||||
|
@@ -91,7 +91,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
# Protocol version specific obis
|
# Protocol version specific obis
|
||||||
if dsmr_version in ("4", "5"):
|
if dsmr_version in ("4", "5"):
|
||||||
gas_obis = obis_ref.HOURLY_GAS_METER_READING
|
gas_obis = obis_ref.HOURLY_GAS_METER_READING
|
||||||
elif dsmr_version in ("5B"):
|
elif dsmr_version in ("5B",):
|
||||||
gas_obis = obis_ref.BELGIUM_HOURLY_GAS_METER_READING
|
gas_obis = obis_ref.BELGIUM_HOURLY_GAS_METER_READING
|
||||||
else:
|
else:
|
||||||
gas_obis = obis_ref.GAS_METER_READING
|
gas_obis = obis_ref.GAS_METER_READING
|
||||||
@@ -238,7 +238,7 @@ class DSMREntity(Entity):
|
|||||||
"""Convert 2/1 to normal/low depending on DSMR version."""
|
"""Convert 2/1 to normal/low depending on DSMR version."""
|
||||||
# DSMR V5B: Note: In Belgium values are swapped:
|
# DSMR V5B: Note: In Belgium values are swapped:
|
||||||
# Rate code 2 is used for low rate and rate code 1 is used for normal rate.
|
# Rate code 2 is used for low rate and rate code 1 is used for normal rate.
|
||||||
if dsmr_version in ("5B"):
|
if dsmr_version in ("5B",):
|
||||||
if value == "0001":
|
if value == "0001":
|
||||||
value = "0002"
|
value = "0002"
|
||||||
elif value == "0002":
|
elif value == "0002":
|
||||||
|
@@ -4,5 +4,6 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/dwd_weather_warnings",
|
"documentation": "https://www.home-assistant.io/integrations/dwd_weather_warnings",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
|
"after_dependencies": ["rest"],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"domain": "eight_sleep",
|
"domain": "eight_sleep",
|
||||||
"name": "Eight Sleep",
|
"name": "Eight Sleep",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/eight_sleep",
|
"documentation": "https://www.home-assistant.io/integrations/eight_sleep",
|
||||||
"requirements": ["pyeight==0.1.3"],
|
"requirements": ["pyeight==0.1.4"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@mezz64"]
|
"codeowners": ["@mezz64"]
|
||||||
}
|
}
|
||||||
|
@@ -261,14 +261,26 @@ class EightUserSensor(EightSleepUserEntity):
|
|||||||
bed_temp = None
|
bed_temp = None
|
||||||
|
|
||||||
if "current" in self._sensor_root:
|
if "current" in self._sensor_root:
|
||||||
|
try:
|
||||||
state_attr[ATTR_RESP_RATE] = round(self._attr["resp_rate"], 2)
|
state_attr[ATTR_RESP_RATE] = round(self._attr["resp_rate"], 2)
|
||||||
|
except TypeError:
|
||||||
|
state_attr[ATTR_RESP_RATE] = None
|
||||||
|
try:
|
||||||
state_attr[ATTR_HEART_RATE] = round(self._attr["heart_rate"], 2)
|
state_attr[ATTR_HEART_RATE] = round(self._attr["heart_rate"], 2)
|
||||||
|
except TypeError:
|
||||||
|
state_attr[ATTR_HEART_RATE] = None
|
||||||
state_attr[ATTR_SLEEP_STAGE] = self._attr["stage"]
|
state_attr[ATTR_SLEEP_STAGE] = self._attr["stage"]
|
||||||
state_attr[ATTR_ROOM_TEMP] = room_temp
|
state_attr[ATTR_ROOM_TEMP] = room_temp
|
||||||
state_attr[ATTR_BED_TEMP] = bed_temp
|
state_attr[ATTR_BED_TEMP] = bed_temp
|
||||||
elif "last" in self._sensor_root:
|
elif "last" in self._sensor_root:
|
||||||
|
try:
|
||||||
state_attr[ATTR_AVG_RESP_RATE] = round(self._attr["resp_rate"], 2)
|
state_attr[ATTR_AVG_RESP_RATE] = round(self._attr["resp_rate"], 2)
|
||||||
|
except TypeError:
|
||||||
|
state_attr[ATTR_AVG_RESP_RATE] = None
|
||||||
|
try:
|
||||||
state_attr[ATTR_AVG_HEART_RATE] = round(self._attr["heart_rate"], 2)
|
state_attr[ATTR_AVG_HEART_RATE] = round(self._attr["heart_rate"], 2)
|
||||||
|
except TypeError:
|
||||||
|
state_attr[ATTR_AVG_HEART_RATE] = None
|
||||||
state_attr[ATTR_AVG_ROOM_TEMP] = room_temp
|
state_attr[ATTR_AVG_ROOM_TEMP] = room_temp
|
||||||
state_attr[ATTR_AVG_BED_TEMP] = bed_temp
|
state_attr[ATTR_AVG_BED_TEMP] = bed_temp
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/emulated_hue",
|
"documentation": "https://www.home-assistant.io/integrations/emulated_hue",
|
||||||
"requirements": ["aiohttp_cors==0.7.0"],
|
"requirements": ["aiohttp_cors==0.7.0"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
|
"after_dependencies": ["http"],
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
"quality_scale": "internal"
|
"quality_scale": "internal"
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"name": "Home Assistant Frontend",
|
"name": "Home Assistant Frontend",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"home-assistant-frontend==20200220.4"
|
"home-assistant-frontend==20200220.5"
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"api",
|
"api",
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
"""Code to handle a Hue bridge."""
|
"""Code to handle a Hue bridge."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
import logging
|
||||||
|
|
||||||
from aiohttp import client_exceptions
|
from aiohttp import client_exceptions
|
||||||
import aiohue
|
import aiohue
|
||||||
@@ -24,7 +25,8 @@ SCENE_SCHEMA = vol.Schema(
|
|||||||
{vol.Required(ATTR_GROUP_NAME): cv.string, vol.Required(ATTR_SCENE_NAME): cv.string}
|
{vol.Required(ATTR_GROUP_NAME): cv.string, vol.Required(ATTR_SCENE_NAME): cv.string}
|
||||||
)
|
)
|
||||||
# How long should we sleep if the hub is busy
|
# How long should we sleep if the hub is busy
|
||||||
HUB_BUSY_SLEEP = 0.01
|
HUB_BUSY_SLEEP = 0.5
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class HueBridge:
|
class HueBridge:
|
||||||
@@ -123,9 +125,14 @@ class HueBridge:
|
|||||||
except (
|
except (
|
||||||
client_exceptions.ClientOSError,
|
client_exceptions.ClientOSError,
|
||||||
client_exceptions.ClientResponseError,
|
client_exceptions.ClientResponseError,
|
||||||
|
client_exceptions.ServerDisconnectedError,
|
||||||
) as err:
|
) as err:
|
||||||
if tries == 3 or (
|
if tries == 3:
|
||||||
|
_LOGGER.error("Request failed %s times, giving up.", tries)
|
||||||
|
raise
|
||||||
|
|
||||||
# We only retry if it's a server error. So raise on all 4XX errors.
|
# We only retry if it's a server error. So raise on all 4XX errors.
|
||||||
|
if (
|
||||||
isinstance(err, client_exceptions.ClientResponseError)
|
isinstance(err, client_exceptions.ClientResponseError)
|
||||||
and err.status < 500
|
and err.status < 500
|
||||||
):
|
):
|
||||||
|
@@ -5,6 +5,7 @@ from functools import partial
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from aiohttp import client_exceptions
|
||||||
import aiohue
|
import aiohue
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
|
||||||
@@ -172,7 +173,11 @@ async def async_safe_fetch(bridge, fetch_method):
|
|||||||
except aiohue.Unauthorized:
|
except aiohue.Unauthorized:
|
||||||
await bridge.handle_unauthorized_error()
|
await bridge.handle_unauthorized_error()
|
||||||
raise UpdateFailed
|
raise UpdateFailed
|
||||||
except (asyncio.TimeoutError, aiohue.AiohueException):
|
except (
|
||||||
|
asyncio.TimeoutError,
|
||||||
|
aiohue.AiohueException,
|
||||||
|
client_exceptions.ClientError,
|
||||||
|
):
|
||||||
raise UpdateFailed
|
raise UpdateFailed
|
||||||
|
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@ import asyncio
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from aiohttp import client_exceptions
|
||||||
from aiohue import AiohueException, Unauthorized
|
from aiohue import AiohueException, Unauthorized
|
||||||
from aiohue.sensors import TYPE_ZLL_PRESENCE
|
from aiohue.sensors import TYPE_ZLL_PRESENCE
|
||||||
import async_timeout
|
import async_timeout
|
||||||
@@ -60,7 +61,7 @@ class SensorManager:
|
|||||||
except Unauthorized:
|
except Unauthorized:
|
||||||
await self.bridge.handle_unauthorized_error()
|
await self.bridge.handle_unauthorized_error()
|
||||||
raise UpdateFailed
|
raise UpdateFailed
|
||||||
except (asyncio.TimeoutError, AiohueException):
|
except (asyncio.TimeoutError, AiohueException, client_exceptions.ClientError):
|
||||||
raise UpdateFailed
|
raise UpdateFailed
|
||||||
|
|
||||||
async def async_register_component(self, binary, async_add_entities):
|
async def async_register_component(self, binary, async_add_entities):
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"name": "Instituto Português do Mar e Atmosfera (IPMA)",
|
"name": "Instituto Português do Mar e Atmosfera (IPMA)",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/ipma",
|
"documentation": "https://www.home-assistant.io/integrations/ipma",
|
||||||
"requirements": ["pyipma==2.0.3"],
|
"requirements": ["pyipma==2.0.4"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@dgomes", "@abmantis"]
|
"codeowners": ["@dgomes", "@abmantis"]
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"device_automation": {
|
"device_automation": {
|
||||||
"action_type": {
|
"action_type": {
|
||||||
|
"brightness_decrease": "Decrease {entity_name} brightness",
|
||||||
|
"brightness_increase": "Increase {entity_name} brightness",
|
||||||
"toggle": "Toggle {entity_name}",
|
"toggle": "Toggle {entity_name}",
|
||||||
"turn_on": "Turn on {entity_name}",
|
"turn_on": "Turn on {entity_name}",
|
||||||
"turn_off": "Turn off {entity_name}"
|
"turn_off": "Turn off {entity_name}"
|
||||||
|
@@ -199,6 +199,9 @@ def humanify(hass, events):
|
|||||||
"""
|
"""
|
||||||
domain_prefixes = tuple(f"{dom}." for dom in CONTINUOUS_DOMAINS)
|
domain_prefixes = tuple(f"{dom}." for dom in CONTINUOUS_DOMAINS)
|
||||||
|
|
||||||
|
# Track last states to filter out duplicates
|
||||||
|
last_state = {}
|
||||||
|
|
||||||
# Group events in batches of GROUP_BY_MINUTES
|
# Group events in batches of GROUP_BY_MINUTES
|
||||||
for _, g_events in groupby(
|
for _, g_events in groupby(
|
||||||
events, lambda event: event.time_fired.minute // GROUP_BY_MINUTES
|
events, lambda event: event.time_fired.minute // GROUP_BY_MINUTES
|
||||||
@@ -236,9 +239,15 @@ def humanify(hass, events):
|
|||||||
# Yield entries
|
# Yield entries
|
||||||
for event in events_batch:
|
for event in events_batch:
|
||||||
if event.event_type == EVENT_STATE_CHANGED:
|
if event.event_type == EVENT_STATE_CHANGED:
|
||||||
|
|
||||||
to_state = State.from_dict(event.data.get("new_state"))
|
to_state = State.from_dict(event.data.get("new_state"))
|
||||||
|
|
||||||
|
# Filter out states that become same state again (force_update=True)
|
||||||
|
# or light becoming different color
|
||||||
|
if last_state.get(to_state.entity_id) == to_state.state:
|
||||||
|
continue
|
||||||
|
|
||||||
|
last_state[to_state.entity_id] = to_state.state
|
||||||
|
|
||||||
domain = to_state.domain
|
domain = to_state.domain
|
||||||
|
|
||||||
# Skip all but the last sensor state
|
# Skip all but the last sensor state
|
||||||
|
@@ -104,6 +104,9 @@ class LovelaceStorage:
|
|||||||
|
|
||||||
async def async_save(self, config):
|
async def async_save(self, config):
|
||||||
"""Save config."""
|
"""Save config."""
|
||||||
|
if self._hass.config.safe_mode:
|
||||||
|
raise HomeAssistantError("Deleting not supported in safe mode")
|
||||||
|
|
||||||
if self._data is None:
|
if self._data is None:
|
||||||
await self._load()
|
await self._load()
|
||||||
self._data["config"] = config
|
self._data["config"] = config
|
||||||
@@ -112,6 +115,9 @@ class LovelaceStorage:
|
|||||||
|
|
||||||
async def async_delete(self):
|
async def async_delete(self):
|
||||||
"""Delete config."""
|
"""Delete config."""
|
||||||
|
if self._hass.config.safe_mode:
|
||||||
|
raise HomeAssistantError("Deleting not supported in safe mode")
|
||||||
|
|
||||||
await self.async_save(None)
|
await self.async_save(None)
|
||||||
|
|
||||||
async def _load(self):
|
async def _load(self):
|
||||||
|
@@ -47,7 +47,7 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry)
|
|||||||
await server.async_update()
|
await server.async_update()
|
||||||
server.start_periodic_update()
|
server.start_periodic_update()
|
||||||
|
|
||||||
# Set up platform(s).
|
# Set up platforms.
|
||||||
for platform in PLATFORMS:
|
for platform in PLATFORMS:
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
hass.config_entries.async_forward_entry_setup(config_entry, platform)
|
hass.config_entries.async_forward_entry_setup(config_entry, platform)
|
||||||
@@ -103,7 +103,6 @@ class MinecraftServer:
|
|||||||
self._mc_status = MCStatus(self.host, self.port)
|
self._mc_status = MCStatus(self.host, self.port)
|
||||||
|
|
||||||
# Data provided by 3rd party library
|
# Data provided by 3rd party library
|
||||||
self.description = None
|
|
||||||
self.version = None
|
self.version = None
|
||||||
self.protocol_version = None
|
self.protocol_version = None
|
||||||
self.latency_time = None
|
self.latency_time = None
|
||||||
@@ -168,7 +167,6 @@ class MinecraftServer:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Got answer to request, update properties.
|
# Got answer to request, update properties.
|
||||||
self.description = status_response.description["text"]
|
|
||||||
self.version = status_response.version.name
|
self.version = status_response.version.name
|
||||||
self.protocol_version = status_response.version.protocol
|
self.protocol_version = status_response.version.protocol
|
||||||
self.players_online = status_response.players.online
|
self.players_online = status_response.players.online
|
||||||
@@ -185,7 +183,6 @@ class MinecraftServer:
|
|||||||
self._last_status_request_failed = False
|
self._last_status_request_failed = False
|
||||||
except OSError as error:
|
except OSError as error:
|
||||||
# No answer to request, set all properties to unknown.
|
# No answer to request, set all properties to unknown.
|
||||||
self.description = None
|
|
||||||
self.version = None
|
self.version = None
|
||||||
self.protocol_version = None
|
self.protocol_version = None
|
||||||
self.players_online = None
|
self.players_online = None
|
||||||
|
@@ -61,7 +61,7 @@ class PushoverNotificationService(BaseNotificationService):
|
|||||||
url = data.get(ATTR_URL, None)
|
url = data.get(ATTR_URL, None)
|
||||||
url_title = data.get(ATTR_URL_TITLE, None)
|
url_title = data.get(ATTR_URL_TITLE, None)
|
||||||
priority = data.get(ATTR_PRIORITY, None)
|
priority = data.get(ATTR_PRIORITY, None)
|
||||||
retry = data.get(ATTR_PRIORITY, None)
|
retry = data.get(ATTR_RETRY, None)
|
||||||
expire = data.get(ATTR_EXPIRE, None)
|
expire = data.get(ATTR_EXPIRE, None)
|
||||||
callback_url = data.get(ATTR_CALLBACK_URL, None)
|
callback_url = data.get(ATTR_CALLBACK_URL, None)
|
||||||
timestamp = data.get(ATTR_TIMESTAMP, None)
|
timestamp = data.get(ATTR_TIMESTAMP, None)
|
||||||
|
@@ -4,5 +4,6 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/pvoutput",
|
"documentation": "https://www.home-assistant.io/integrations/pvoutput",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
|
"after_dependencies": ["rest"],
|
||||||
"codeowners": ["@fabaff"]
|
"codeowners": ["@fabaff"]
|
||||||
}
|
}
|
||||||
|
@@ -202,6 +202,8 @@ class RestSensor(Entity):
|
|||||||
self.rest.update()
|
self.rest.update()
|
||||||
value = self.rest.data
|
value = self.rest.data
|
||||||
_LOGGER.debug("Data fetched from resource: %s", value)
|
_LOGGER.debug("Data fetched from resource: %s", value)
|
||||||
|
if self.rest.headers is not None:
|
||||||
|
# If the http request failed, headers will be None
|
||||||
content_type = self.rest.headers.get("content-type")
|
content_type = self.rest.headers.get("content-type")
|
||||||
|
|
||||||
if content_type and content_type.startswith("text/xml"):
|
if content_type and content_type.startswith("text/xml"):
|
||||||
|
@@ -154,6 +154,7 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_save_refresh_token(hass, config_entry, token):
|
def _async_save_refresh_token(hass, config_entry, token):
|
||||||
|
"""Save a refresh token to the config entry."""
|
||||||
hass.config_entries.async_update_entry(
|
hass.config_entries.async_update_entry(
|
||||||
config_entry, data={**config_entry.data, CONF_TOKEN: token}
|
config_entry, data={**config_entry.data, CONF_TOKEN: token}
|
||||||
)
|
)
|
||||||
@@ -547,12 +548,7 @@ class SimpliSafe:
|
|||||||
_LOGGER.error("Unknown error while updating: %s", result)
|
_LOGGER.error("Unknown error while updating: %s", result)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._api.refresh_token_dirty:
|
if self._api.refresh_token != self._config_entry.data[CONF_TOKEN]:
|
||||||
# Reconnect the websocket:
|
|
||||||
await self._api.websocket.async_disconnect()
|
|
||||||
await self._api.websocket.async_connect()
|
|
||||||
|
|
||||||
# Save the new refresh token:
|
|
||||||
_async_save_refresh_token(
|
_async_save_refresh_token(
|
||||||
self._hass, self._config_entry, self._api.refresh_token
|
self._hass, self._config_entry, self._api.refresh_token
|
||||||
)
|
)
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"name": "SimpliSafe",
|
"name": "SimpliSafe",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/simplisafe",
|
"documentation": "https://www.home-assistant.io/integrations/simplisafe",
|
||||||
"requirements": ["simplisafe-python==8.1.1"],
|
"requirements": ["simplisafe-python==9.0.2"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@bachya"]
|
"codeowners": ["@bachya"]
|
||||||
}
|
}
|
||||||
|
@@ -68,3 +68,8 @@ class TeslaDeviceEntity(TeslaDevice, TrackerEntity):
|
|||||||
def source_type(self):
|
def source_type(self):
|
||||||
"""Return the source type, eg gps or router, of the device."""
|
"""Return the source type, eg gps or router, of the device."""
|
||||||
return SOURCE_TYPE_GPS
|
return SOURCE_TYPE_GPS
|
||||||
|
|
||||||
|
@property
|
||||||
|
def force_update(self):
|
||||||
|
"""All updates do not need to be written to the state machine."""
|
||||||
|
return False
|
||||||
|
@@ -200,6 +200,11 @@ class UniFiClientTracker(UniFiClient, ScannerEntity):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
self.wired_bug = None
|
self.wired_bug = None
|
||||||
|
|
||||||
|
# A client that has never been seen cannot be connected.
|
||||||
|
if self.client.last_seen is None:
|
||||||
|
return False
|
||||||
|
|
||||||
since_last_seen = dt_util.utcnow() - dt_util.utc_from_timestamp(
|
since_last_seen = dt_util.utcnow() - dt_util.utc_from_timestamp(
|
||||||
float(self.client.last_seen)
|
float(self.client.last_seen)
|
||||||
)
|
)
|
||||||
@@ -333,4 +338,4 @@ class UniFiDeviceTracker(ScannerEntity):
|
|||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""No polling needed."""
|
"""No polling needed."""
|
||||||
return False
|
return True
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/unifi",
|
"documentation": "https://www.home-assistant.io/integrations/unifi",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"aiounifi==13"
|
"aiounifi==14"
|
||||||
],
|
],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
|
@@ -62,4 +62,4 @@ class UniFiClient(Entity):
|
|||||||
@property
|
@property
|
||||||
def should_poll(self) -> bool:
|
def should_poll(self) -> bool:
|
||||||
"""No polling needed."""
|
"""No polling needed."""
|
||||||
return False
|
return True
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 106
|
MINOR_VERSION = 106
|
||||||
PATCH_VERSION = "0"
|
PATCH_VERSION = "5"
|
||||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER = (3, 7, 0)
|
REQUIRED_PYTHON_VER = (3, 7, 0)
|
||||||
|
@@ -17,6 +17,7 @@ FLOWS = [
|
|||||||
"cast",
|
"cast",
|
||||||
"cert_expiry",
|
"cert_expiry",
|
||||||
"coolmaster",
|
"coolmaster",
|
||||||
|
"coronavirus",
|
||||||
"daikin",
|
"daikin",
|
||||||
"deconz",
|
"deconz",
|
||||||
"dialogflow",
|
"dialogflow",
|
||||||
|
@@ -11,7 +11,7 @@ import asyncio
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, cast
|
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, cast
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
@@ -560,3 +560,21 @@ def async_setup_entity_restore(
|
|||||||
states.async_set(entry.entity_id, STATE_UNAVAILABLE, attrs)
|
states.async_set(entry.entity_id, STATE_UNAVAILABLE, attrs)
|
||||||
|
|
||||||
hass.bus.async_listen(EVENT_HOMEASSISTANT_START, _write_unavailable_states)
|
hass.bus.async_listen(EVENT_HOMEASSISTANT_START, _write_unavailable_states)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_migrate_entries(
|
||||||
|
hass: HomeAssistantType,
|
||||||
|
config_entry_id: str,
|
||||||
|
entry_callback: Callable[[RegistryEntry], Optional[dict]],
|
||||||
|
) -> None:
|
||||||
|
"""Migrator of unique IDs."""
|
||||||
|
ent_reg = await async_get_registry(hass)
|
||||||
|
|
||||||
|
for entry in ent_reg.entities.values():
|
||||||
|
if entry.config_entry_id != config_entry_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
updates = entry_callback(entry)
|
||||||
|
|
||||||
|
if updates is not None:
|
||||||
|
ent_reg.async_update_entity(entry.entity_id, **updates) # type: ignore
|
||||||
|
@@ -11,7 +11,7 @@ cryptography==2.8
|
|||||||
defusedxml==0.6.0
|
defusedxml==0.6.0
|
||||||
distro==1.4.0
|
distro==1.4.0
|
||||||
hass-nabucasa==0.31
|
hass-nabucasa==0.31
|
||||||
home-assistant-frontend==20200220.4
|
home-assistant-frontend==20200220.5
|
||||||
importlib-metadata==1.5.0
|
importlib-metadata==1.5.0
|
||||||
jinja2>=2.10.3
|
jinja2>=2.10.3
|
||||||
netdisco==2.6.0
|
netdisco==2.6.0
|
||||||
|
@@ -199,7 +199,7 @@ aiopylgtv==0.3.3
|
|||||||
aioswitcher==2019.4.26
|
aioswitcher==2019.4.26
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==13
|
aiounifi==14
|
||||||
|
|
||||||
# homeassistant.components.wwlln
|
# homeassistant.components.wwlln
|
||||||
aiowwlln==2.0.2
|
aiowwlln==2.0.2
|
||||||
@@ -398,6 +398,9 @@ connect-box==0.2.5
|
|||||||
# homeassistant.components.xiaomi_miio
|
# homeassistant.components.xiaomi_miio
|
||||||
construct==2.9.45
|
construct==2.9.45
|
||||||
|
|
||||||
|
# homeassistant.components.coronavirus
|
||||||
|
coronavirus==1.0.1
|
||||||
|
|
||||||
# homeassistant.scripts.credstash
|
# homeassistant.scripts.credstash
|
||||||
# credstash==1.15.0
|
# credstash==1.15.0
|
||||||
|
|
||||||
@@ -683,7 +686,7 @@ hole==0.5.0
|
|||||||
holidays==0.10.1
|
holidays==0.10.1
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20200220.4
|
home-assistant-frontend==20200220.5
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.8
|
homeassistant-pyozw==0.1.8
|
||||||
@@ -1223,7 +1226,7 @@ pyeconet==0.0.11
|
|||||||
pyedimax==0.2.1
|
pyedimax==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.eight_sleep
|
# homeassistant.components.eight_sleep
|
||||||
pyeight==0.1.3
|
pyeight==0.1.4
|
||||||
|
|
||||||
# homeassistant.components.emby
|
# homeassistant.components.emby
|
||||||
pyemby==1.6
|
pyemby==1.6
|
||||||
@@ -1305,7 +1308,7 @@ pyicloud==0.9.2
|
|||||||
pyintesishome==1.6
|
pyintesishome==1.6
|
||||||
|
|
||||||
# homeassistant.components.ipma
|
# homeassistant.components.ipma
|
||||||
pyipma==2.0.3
|
pyipma==2.0.4
|
||||||
|
|
||||||
# homeassistant.components.iqvia
|
# homeassistant.components.iqvia
|
||||||
pyiqvia==0.2.1
|
pyiqvia==0.2.1
|
||||||
@@ -1827,7 +1830,7 @@ simplehound==0.3
|
|||||||
simplepush==1.1.4
|
simplepush==1.1.4
|
||||||
|
|
||||||
# homeassistant.components.simplisafe
|
# homeassistant.components.simplisafe
|
||||||
simplisafe-python==8.1.1
|
simplisafe-python==9.0.2
|
||||||
|
|
||||||
# homeassistant.components.sisyphus
|
# homeassistant.components.sisyphus
|
||||||
sisyphus-control==2.2.1
|
sisyphus-control==2.2.1
|
||||||
|
@@ -78,7 +78,7 @@ aiopylgtv==0.3.3
|
|||||||
aioswitcher==2019.4.26
|
aioswitcher==2019.4.26
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==13
|
aiounifi==14
|
||||||
|
|
||||||
# homeassistant.components.wwlln
|
# homeassistant.components.wwlln
|
||||||
aiowwlln==2.0.2
|
aiowwlln==2.0.2
|
||||||
@@ -143,6 +143,9 @@ colorlog==4.1.0
|
|||||||
# homeassistant.components.xiaomi_miio
|
# homeassistant.components.xiaomi_miio
|
||||||
construct==2.9.45
|
construct==2.9.45
|
||||||
|
|
||||||
|
# homeassistant.components.coronavirus
|
||||||
|
coronavirus==1.0.1
|
||||||
|
|
||||||
# homeassistant.scripts.credstash
|
# homeassistant.scripts.credstash
|
||||||
# credstash==1.15.0
|
# credstash==1.15.0
|
||||||
|
|
||||||
@@ -254,7 +257,7 @@ hole==0.5.0
|
|||||||
holidays==0.10.1
|
holidays==0.10.1
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20200220.4
|
home-assistant-frontend==20200220.5
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.8
|
homeassistant-pyozw==0.1.8
|
||||||
@@ -477,7 +480,7 @@ pyhomematic==0.1.64
|
|||||||
pyicloud==0.9.2
|
pyicloud==0.9.2
|
||||||
|
|
||||||
# homeassistant.components.ipma
|
# homeassistant.components.ipma
|
||||||
pyipma==2.0.3
|
pyipma==2.0.4
|
||||||
|
|
||||||
# homeassistant.components.iqvia
|
# homeassistant.components.iqvia
|
||||||
pyiqvia==0.2.1
|
pyiqvia==0.2.1
|
||||||
@@ -626,7 +629,7 @@ sentry-sdk==0.13.5
|
|||||||
simplehound==0.3
|
simplehound==0.3
|
||||||
|
|
||||||
# homeassistant.components.simplisafe
|
# homeassistant.components.simplisafe
|
||||||
simplisafe-python==8.1.1
|
simplisafe-python==9.0.2
|
||||||
|
|
||||||
# homeassistant.components.sleepiq
|
# homeassistant.components.sleepiq
|
||||||
sleepyq==0.7
|
sleepyq==0.7
|
||||||
|
@@ -65,7 +65,7 @@ class ImportCollector(ast.NodeVisitor):
|
|||||||
|
|
||||||
# self.hass.components.hue.async_create()
|
# self.hass.components.hue.async_create()
|
||||||
# Name(id=self)
|
# Name(id=self)
|
||||||
# .Attribute(attr=hass)
|
# .Attribute(attr=hass) or .Attribute(attr=_hass)
|
||||||
# .Attribute(attr=hue)
|
# .Attribute(attr=hue)
|
||||||
# .Attribute(attr=async_create)
|
# .Attribute(attr=async_create)
|
||||||
if (
|
if (
|
||||||
@@ -78,7 +78,7 @@ class ImportCollector(ast.NodeVisitor):
|
|||||||
)
|
)
|
||||||
or (
|
or (
|
||||||
isinstance(node.value.value, ast.Attribute)
|
isinstance(node.value.value, ast.Attribute)
|
||||||
and node.value.value.attr == "hass"
|
and node.value.value.attr in ("hass", "_hass")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
@@ -89,20 +89,47 @@ class ImportCollector(ast.NodeVisitor):
|
|||||||
|
|
||||||
|
|
||||||
ALLOWED_USED_COMPONENTS = {
|
ALLOWED_USED_COMPONENTS = {
|
||||||
# This component will always be set up
|
# Internal integrations
|
||||||
"persistent_notification",
|
"alert",
|
||||||
# These allow to register things without being set up
|
|
||||||
"conversation",
|
|
||||||
"frontend",
|
|
||||||
"hassio",
|
|
||||||
"system_health",
|
|
||||||
"websocket_api",
|
|
||||||
"automation",
|
"automation",
|
||||||
|
"conversation",
|
||||||
"device_automation",
|
"device_automation",
|
||||||
"zone",
|
"frontend",
|
||||||
|
"group",
|
||||||
|
"hassio",
|
||||||
"homeassistant",
|
"homeassistant",
|
||||||
"system_log",
|
"input_boolean",
|
||||||
|
"input_datetime",
|
||||||
|
"input_number",
|
||||||
|
"input_select",
|
||||||
|
"input_text",
|
||||||
|
"persistent_notification",
|
||||||
"person",
|
"person",
|
||||||
|
"script",
|
||||||
|
"shopping_list",
|
||||||
|
"sun",
|
||||||
|
"system_health",
|
||||||
|
"system_log",
|
||||||
|
"timer",
|
||||||
|
"webhook",
|
||||||
|
"websocket_api",
|
||||||
|
"zone",
|
||||||
|
# Entity integrations with platforms
|
||||||
|
"alarm_control_panel",
|
||||||
|
"binary_sensor",
|
||||||
|
"climate",
|
||||||
|
"cover",
|
||||||
|
"device_tracker",
|
||||||
|
"fan",
|
||||||
|
"image_processing",
|
||||||
|
"light",
|
||||||
|
"lock",
|
||||||
|
"media_player",
|
||||||
|
"scene",
|
||||||
|
"sensor",
|
||||||
|
"switch",
|
||||||
|
"vacuum",
|
||||||
|
"water_heater",
|
||||||
# Other
|
# Other
|
||||||
"mjpeg", # base class, has no reqs or component to load.
|
"mjpeg", # base class, has no reqs or component to load.
|
||||||
"stream", # Stream cannot install on all systems, can be imported without reqs.
|
"stream", # Stream cannot install on all systems, can be imported without reqs.
|
||||||
@@ -121,18 +148,7 @@ IGNORE_VIOLATIONS = {
|
|||||||
# This should become a helper method that integrations can submit data to
|
# This should become a helper method that integrations can submit data to
|
||||||
("websocket_api", "lovelace"),
|
("websocket_api", "lovelace"),
|
||||||
("websocket_api", "shopping_list"),
|
("websocket_api", "shopping_list"),
|
||||||
# Expose HA to external systems
|
|
||||||
"homekit",
|
|
||||||
"alexa",
|
|
||||||
"google_assistant",
|
|
||||||
"emulated_hue",
|
|
||||||
"prometheus",
|
|
||||||
"conversation",
|
|
||||||
"logbook",
|
"logbook",
|
||||||
"mobile_app",
|
|
||||||
# These should be extracted to external package
|
|
||||||
"pvoutput",
|
|
||||||
"dwd_weather_warnings",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
1
tests/components/coronavirus/__init__.py
Normal file
1
tests/components/coronavirus/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Coronavirus integration."""
|
33
tests/components/coronavirus/test_config_flow.py
Normal file
33
tests/components/coronavirus/test_config_flow.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"""Test the Coronavirus config flow."""
|
||||||
|
from asynctest import patch
|
||||||
|
|
||||||
|
from homeassistant import config_entries, setup
|
||||||
|
from homeassistant.components.coronavirus.const import DOMAIN, OPTION_WORLDWIDE
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form(hass):
|
||||||
|
"""Test we get the form."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch("coronavirus.get_cases", return_value=[],), patch(
|
||||||
|
"homeassistant.components.coronavirus.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.coronavirus.async_setup_entry", return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"country": OPTION_WORLDWIDE},
|
||||||
|
)
|
||||||
|
assert result2["type"] == "create_entry"
|
||||||
|
assert result2["title"] == "Worldwide"
|
||||||
|
assert result2["result"].unique_id == OPTION_WORLDWIDE
|
||||||
|
assert result2["data"] == {
|
||||||
|
"country": OPTION_WORLDWIDE,
|
||||||
|
}
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
58
tests/components/coronavirus/test_init.py
Normal file
58
tests/components/coronavirus/test_init.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"""Test init of Coronavirus integration."""
|
||||||
|
from asynctest import Mock, patch
|
||||||
|
|
||||||
|
from homeassistant.components.coronavirus.const import DOMAIN, OPTION_WORLDWIDE
|
||||||
|
from homeassistant.helpers import entity_registry
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, mock_registry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_migration(hass):
|
||||||
|
"""Test that we can migrate coronavirus to stable unique ID."""
|
||||||
|
nl_entry = MockConfigEntry(domain=DOMAIN, title="Netherlands", data={"country": 34})
|
||||||
|
nl_entry.add_to_hass(hass)
|
||||||
|
worldwide_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, title="Worldwide", data={"country": OPTION_WORLDWIDE}
|
||||||
|
)
|
||||||
|
worldwide_entry.add_to_hass(hass)
|
||||||
|
mock_registry(
|
||||||
|
hass,
|
||||||
|
{
|
||||||
|
"sensor.netherlands_confirmed": entity_registry.RegistryEntry(
|
||||||
|
entity_id="sensor.netherlands_confirmed",
|
||||||
|
unique_id="34-confirmed",
|
||||||
|
platform="coronavirus",
|
||||||
|
config_entry_id=nl_entry.entry_id,
|
||||||
|
),
|
||||||
|
"sensor.worldwide_confirmed": entity_registry.RegistryEntry(
|
||||||
|
entity_id="sensor.worldwide_confirmed",
|
||||||
|
unique_id="__worldwide-confirmed",
|
||||||
|
platform="coronavirus",
|
||||||
|
config_entry_id=worldwide_entry.entry_id,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"coronavirus.get_cases",
|
||||||
|
return_value=[
|
||||||
|
Mock(country="Netherlands", confirmed=10, recovered=8, deaths=1, current=1),
|
||||||
|
Mock(country="Germany", confirmed=1, recovered=0, deaths=0, current=0),
|
||||||
|
],
|
||||||
|
):
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
ent_reg = await entity_registry.async_get_registry(hass)
|
||||||
|
|
||||||
|
sensor_nl = ent_reg.async_get("sensor.netherlands_confirmed")
|
||||||
|
assert sensor_nl.unique_id == "Netherlands-confirmed"
|
||||||
|
|
||||||
|
sensor_worldwide = ent_reg.async_get("sensor.worldwide_confirmed")
|
||||||
|
assert sensor_worldwide.unique_id == "__worldwide-confirmed"
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.netherlands_confirmed").state == "10"
|
||||||
|
assert hass.states.get("sensor.worldwide_confirmed").state == "11"
|
||||||
|
|
||||||
|
assert nl_entry.unique_id == "Netherlands"
|
||||||
|
assert worldwide_entry.unique_id == OPTION_WORLDWIDE
|
@@ -187,6 +187,50 @@ async def test_v4_meter(hass, mock_connection_factory):
|
|||||||
assert gas_consumption.attributes.get("unit_of_measurement") == "m3"
|
assert gas_consumption.attributes.get("unit_of_measurement") == "m3"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_v5_meter(hass, mock_connection_factory):
|
||||||
|
"""Test if v5 meter is correctly parsed."""
|
||||||
|
(connection_factory, transport, protocol) = mock_connection_factory
|
||||||
|
|
||||||
|
from dsmr_parser.obis_references import (
|
||||||
|
HOURLY_GAS_METER_READING,
|
||||||
|
ELECTRICITY_ACTIVE_TARIFF,
|
||||||
|
)
|
||||||
|
from dsmr_parser.objects import CosemObject, MBusObject
|
||||||
|
|
||||||
|
config = {"platform": "dsmr", "dsmr_version": "5"}
|
||||||
|
|
||||||
|
telegram = {
|
||||||
|
HOURLY_GAS_METER_READING: MBusObject(
|
||||||
|
[
|
||||||
|
{"value": datetime.datetime.fromtimestamp(1551642213)},
|
||||||
|
{"value": Decimal(745.695), "unit": "m³"},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0001", "unit": ""}]),
|
||||||
|
}
|
||||||
|
|
||||||
|
with assert_setup_component(1):
|
||||||
|
await async_setup_component(hass, "sensor", {"sensor": config})
|
||||||
|
|
||||||
|
telegram_callback = connection_factory.call_args_list[0][0][2]
|
||||||
|
|
||||||
|
# simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
|
||||||
|
telegram_callback(telegram)
|
||||||
|
|
||||||
|
# after receiving telegram entities need to have the chance to update
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
# tariff should be translated in human readable and have no unit
|
||||||
|
power_tariff = hass.states.get("sensor.power_tariff")
|
||||||
|
assert power_tariff.state == "low"
|
||||||
|
assert power_tariff.attributes.get("unit_of_measurement") == ""
|
||||||
|
|
||||||
|
# check if gas consumption is parsed correctly
|
||||||
|
gas_consumption = hass.states.get("sensor.gas_consumption")
|
||||||
|
assert gas_consumption.state == "745.695"
|
||||||
|
assert gas_consumption.attributes.get("unit_of_measurement") == "m³"
|
||||||
|
|
||||||
|
|
||||||
async def test_belgian_meter(hass, mock_connection_factory):
|
async def test_belgian_meter(hass, mock_connection_factory):
|
||||||
"""Test if Belgian meter is correctly parsed."""
|
"""Test if Belgian meter is correctly parsed."""
|
||||||
(connection_factory, transport, protocol) = mock_connection_factory
|
(connection_factory, transport, protocol) = mock_connection_factory
|
||||||
|
@@ -1484,3 +1484,36 @@ async def test_humanify_script_started_event(hass):
|
|||||||
assert event2["domain"] == "script"
|
assert event2["domain"] == "script"
|
||||||
assert event2["message"] == "started"
|
assert event2["message"] == "started"
|
||||||
assert event2["entity_id"] == "script.bye"
|
assert event2["entity_id"] == "script.bye"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_humanify_same_state(hass):
|
||||||
|
"""Test humanifying Script Run event."""
|
||||||
|
state_50 = ha.State("light.kitchen", "on", {"brightness": 50}).as_dict()
|
||||||
|
state_100 = ha.State("light.kitchen", "on", {"brightness": 100}).as_dict()
|
||||||
|
state_200 = ha.State("light.kitchen", "on", {"brightness": 200}).as_dict()
|
||||||
|
|
||||||
|
events = list(
|
||||||
|
logbook.humanify(
|
||||||
|
hass,
|
||||||
|
[
|
||||||
|
ha.Event(
|
||||||
|
EVENT_STATE_CHANGED,
|
||||||
|
{
|
||||||
|
"entity_id": "light.kitchen",
|
||||||
|
"old_state": state_50,
|
||||||
|
"new_state": state_100,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ha.Event(
|
||||||
|
EVENT_STATE_CHANGED,
|
||||||
|
{
|
||||||
|
"entity_id": "light.kitchen",
|
||||||
|
"old_state": state_100,
|
||||||
|
"new_state": state_200,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(events) == 1
|
||||||
|
@@ -45,6 +45,16 @@ async def test_lovelace_from_storage(hass, hass_ws_client, hass_storage):
|
|||||||
assert not response["success"]
|
assert not response["success"]
|
||||||
assert response["error"]["code"] == "config_not_found"
|
assert response["error"]["code"] == "config_not_found"
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{"id": 9, "type": "lovelace/config/save", "config": {"yo": "hello"}}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert not response["success"]
|
||||||
|
|
||||||
|
await client.send_json({"id": 10, "type": "lovelace/config/delete"})
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert not response["success"]
|
||||||
|
|
||||||
|
|
||||||
async def test_lovelace_from_storage_save_before_load(
|
async def test_lovelace_from_storage_save_before_load(
|
||||||
hass, hass_ws_client, hass_storage
|
hass, hass_ws_client, hass_storage
|
||||||
|
@@ -589,6 +589,35 @@ class TestRestSensor(unittest.TestCase):
|
|||||||
assert mock_logger.warning.called
|
assert mock_logger.warning.called
|
||||||
assert mock_logger.debug.called
|
assert mock_logger.debug.called
|
||||||
|
|
||||||
|
@patch("homeassistant.components.rest.sensor._LOGGER")
|
||||||
|
def test_update_with_failed_get(self, mock_logger):
|
||||||
|
"""Test attributes get extracted from a XML result with bad xml."""
|
||||||
|
value_template = template("{{ value_json.toplevel.master_value }}")
|
||||||
|
value_template.hass = self.hass
|
||||||
|
|
||||||
|
self.rest.update = Mock(
|
||||||
|
"rest.RestData.update", side_effect=self.update_side_effect(None, None),
|
||||||
|
)
|
||||||
|
self.sensor = rest.RestSensor(
|
||||||
|
self.hass,
|
||||||
|
self.rest,
|
||||||
|
self.name,
|
||||||
|
self.unit_of_measurement,
|
||||||
|
self.device_class,
|
||||||
|
value_template,
|
||||||
|
["key"],
|
||||||
|
self.force_update,
|
||||||
|
self.resource_template,
|
||||||
|
self.json_attrs_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.sensor.update()
|
||||||
|
assert {} == self.sensor.device_state_attributes
|
||||||
|
assert mock_logger.warning.called
|
||||||
|
assert mock_logger.debug.called
|
||||||
|
assert self.sensor.state is None
|
||||||
|
assert self.sensor.available is False
|
||||||
|
|
||||||
|
|
||||||
class TestRestData(unittest.TestCase):
|
class TestRestData(unittest.TestCase):
|
||||||
"""Tests for RestData."""
|
"""Tests for RestData."""
|
||||||
|
@@ -54,6 +54,14 @@ CLIENT_4 = {
|
|||||||
"last_seen": 1562600145,
|
"last_seen": 1562600145,
|
||||||
"mac": "00:00:00:00:00:04",
|
"mac": "00:00:00:00:00:04",
|
||||||
}
|
}
|
||||||
|
CLIENT_5 = {
|
||||||
|
"essid": "ssid",
|
||||||
|
"hostname": "client_5",
|
||||||
|
"ip": "10.0.0.5",
|
||||||
|
"is_wired": True,
|
||||||
|
"last_seen": None,
|
||||||
|
"mac": "00:00:00:00:00:05",
|
||||||
|
}
|
||||||
|
|
||||||
DEVICE_1 = {
|
DEVICE_1 = {
|
||||||
"board_rev": 3,
|
"board_rev": 3,
|
||||||
@@ -111,11 +119,11 @@ async def test_tracked_devices(hass):
|
|||||||
controller = await setup_unifi_integration(
|
controller = await setup_unifi_integration(
|
||||||
hass,
|
hass,
|
||||||
options={CONF_SSID_FILTER: ["ssid"]},
|
options={CONF_SSID_FILTER: ["ssid"]},
|
||||||
clients_response=[CLIENT_1, CLIENT_2, CLIENT_3, client_4_copy],
|
clients_response=[CLIENT_1, CLIENT_2, CLIENT_3, CLIENT_5, client_4_copy],
|
||||||
devices_response=[DEVICE_1, DEVICE_2],
|
devices_response=[DEVICE_1, DEVICE_2],
|
||||||
known_wireless_clients=(CLIENT_4["mac"],),
|
known_wireless_clients=(CLIENT_4["mac"],),
|
||||||
)
|
)
|
||||||
assert len(hass.states.async_all()) == 6
|
assert len(hass.states.async_all()) == 7
|
||||||
|
|
||||||
client_1 = hass.states.get("device_tracker.client_1")
|
client_1 = hass.states.get("device_tracker.client_1")
|
||||||
assert client_1 is not None
|
assert client_1 is not None
|
||||||
@@ -134,6 +142,11 @@ async def test_tracked_devices(hass):
|
|||||||
assert client_4 is not None
|
assert client_4 is not None
|
||||||
assert client_4.state == "not_home"
|
assert client_4.state == "not_home"
|
||||||
|
|
||||||
|
# A client that has never been seen should be marked away.
|
||||||
|
client_5 = hass.states.get("device_tracker.client_5")
|
||||||
|
assert client_5 is not None
|
||||||
|
assert client_5.state == "not_home"
|
||||||
|
|
||||||
device_1 = hass.states.get("device_tracker.device_1")
|
device_1 = hass.states.get("device_tracker.device_1")
|
||||||
assert device_1 is not None
|
assert device_1 is not None
|
||||||
assert device_1.state == "not_home"
|
assert device_1.state == "not_home"
|
||||||
|
Reference in New Issue
Block a user