mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 08:07:45 +00:00
Merge pull request #73334 from home-assistant/rc
This commit is contained in:
commit
3759adcf2b
@ -70,6 +70,7 @@ class FeedManager:
|
||||
self._last_entry_timestamp = None
|
||||
self._last_update_successful = False
|
||||
self._has_published_parsed = False
|
||||
self._has_updated_parsed = False
|
||||
self._event_type = EVENT_FEEDREADER
|
||||
self._feed_id = url
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, lambda _: self._update())
|
||||
@ -122,7 +123,7 @@ class FeedManager:
|
||||
)
|
||||
self._filter_entries()
|
||||
self._publish_new_entries()
|
||||
if self._has_published_parsed:
|
||||
if self._has_published_parsed or self._has_updated_parsed:
|
||||
self._storage.put_timestamp(
|
||||
self._feed_id, self._last_entry_timestamp
|
||||
)
|
||||
@ -143,7 +144,7 @@ class FeedManager:
|
||||
|
||||
def _update_and_fire_entry(self, entry):
|
||||
"""Update last_entry_timestamp and fire entry."""
|
||||
# Check if the entry has a published date.
|
||||
# Check if the entry has a published or updated date.
|
||||
if "published_parsed" in entry and entry.published_parsed:
|
||||
# We are lucky, `published_parsed` data available, let's make use of
|
||||
# it to publish only new available entries since the last run
|
||||
@ -151,9 +152,20 @@ class FeedManager:
|
||||
self._last_entry_timestamp = max(
|
||||
entry.published_parsed, self._last_entry_timestamp
|
||||
)
|
||||
elif "updated_parsed" in entry and entry.updated_parsed:
|
||||
# We are lucky, `updated_parsed` data available, let's make use of
|
||||
# it to publish only new available entries since the last run
|
||||
self._has_updated_parsed = True
|
||||
self._last_entry_timestamp = max(
|
||||
entry.updated_parsed, self._last_entry_timestamp
|
||||
)
|
||||
else:
|
||||
self._has_published_parsed = False
|
||||
_LOGGER.debug("No published_parsed info available for entry %s", entry)
|
||||
self._has_updated_parsed = False
|
||||
_LOGGER.debug(
|
||||
"No published_parsed or updated_parsed info available for entry %s",
|
||||
entry,
|
||||
)
|
||||
entry.update({"feed_url": self._url})
|
||||
self._hass.bus.fire(self._event_type, entry)
|
||||
|
||||
@ -167,9 +179,16 @@ class FeedManager:
|
||||
# Set last entry timestamp as epoch time if not available
|
||||
self._last_entry_timestamp = datetime.utcfromtimestamp(0).timetuple()
|
||||
for entry in self._feed.entries:
|
||||
if self._firstrun or (
|
||||
"published_parsed" in entry
|
||||
and entry.published_parsed > self._last_entry_timestamp
|
||||
if (
|
||||
self._firstrun
|
||||
or (
|
||||
"published_parsed" in entry
|
||||
and entry.published_parsed > self._last_entry_timestamp
|
||||
)
|
||||
or (
|
||||
"updated_parsed" in entry
|
||||
and entry.updated_parsed > self._last_entry_timestamp
|
||||
)
|
||||
):
|
||||
self._update_and_fire_entry(entry)
|
||||
new_entries = True
|
||||
|
@ -460,7 +460,7 @@ async def _async_setup_themes(
|
||||
async def reload_themes(_: ServiceCall) -> None:
|
||||
"""Reload themes."""
|
||||
config = await async_hass_config_yaml(hass)
|
||||
new_themes = config[DOMAIN].get(CONF_THEMES, {})
|
||||
new_themes = config.get(DOMAIN, {}).get(CONF_THEMES, {})
|
||||
hass.data[DATA_THEMES] = new_themes
|
||||
if hass.data[DATA_DEFAULT_THEME] not in new_themes:
|
||||
hass.data[DATA_DEFAULT_THEME] = DEFAULT_THEME
|
||||
|
@ -27,6 +27,7 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self.data = {}
|
||||
self.tokens = {}
|
||||
self.entry = None
|
||||
self.device_registration = False
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Prompt user input. Create or edit entry."""
|
||||
@ -88,6 +89,7 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
if not errors:
|
||||
try:
|
||||
self.device_registration = True
|
||||
return await self.async_setup_hive_entry()
|
||||
except UnknownHiveError:
|
||||
errors["base"] = "unknown"
|
||||
@ -102,9 +104,10 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
raise UnknownHiveError
|
||||
|
||||
# Setup the config entry
|
||||
await self.hive_auth.device_registration("Home Assistant")
|
||||
if self.device_registration:
|
||||
await self.hive_auth.device_registration("Home Assistant")
|
||||
self.data["device_data"] = await self.hive_auth.getDeviceData()
|
||||
self.data["tokens"] = self.tokens
|
||||
self.data["device_data"] = await self.hive_auth.getDeviceData()
|
||||
if self.context["source"] == config_entries.SOURCE_REAUTH:
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self.entry, title=self.data["username"], data=self.data
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Hive",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/hive",
|
||||
"requirements": ["pyhiveapi==0.5.5"],
|
||||
"requirements": ["pyhiveapi==0.5.9"],
|
||||
"codeowners": ["@Rendili", "@KJonline"],
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["apyhiveapi"]
|
||||
|
@ -28,7 +28,14 @@ from homeassistant.data_entry_flow import BaseServiceInfo
|
||||
from homeassistant.exceptions import TemplateError, Unauthorized
|
||||
from homeassistant.helpers import config_validation as cv, event, template
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect,
|
||||
async_dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.reload import (
|
||||
async_integration_yaml_config,
|
||||
async_setup_reload_service,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
# Loading the config flow file will register the flow
|
||||
@ -60,12 +67,14 @@ from .const import ( # noqa: F401
|
||||
DATA_MQTT,
|
||||
DATA_MQTT_CONFIG,
|
||||
DATA_MQTT_RELOAD_NEEDED,
|
||||
DATA_MQTT_UPDATED_CONFIG,
|
||||
DEFAULT_ENCODING,
|
||||
DEFAULT_QOS,
|
||||
DEFAULT_RETAIN,
|
||||
DOMAIN,
|
||||
MQTT_CONNECTED,
|
||||
MQTT_DISCONNECTED,
|
||||
MQTT_RELOADED,
|
||||
PLATFORMS,
|
||||
)
|
||||
from .models import ( # noqa: F401
|
||||
@ -227,7 +236,9 @@ async def _async_config_entry_updated(hass: HomeAssistant, entry: ConfigEntry) -
|
||||
await _async_setup_discovery(hass, mqtt_client.conf, entry)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry( # noqa: C901
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
) -> bool:
|
||||
"""Load a config entry."""
|
||||
# Merge basic configuration, and add missing defaults for basic options
|
||||
_merge_basic_config(hass, entry, hass.data.get(DATA_MQTT_CONFIG, {}))
|
||||
@ -364,6 +375,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.data[DATA_CONFIG_ENTRY_LOCK] = asyncio.Lock()
|
||||
hass.data[CONFIG_ENTRY_IS_SETUP] = set()
|
||||
|
||||
# Setup reload service. Once support for legacy config is removed in 2022.9, we
|
||||
# should no longer call async_setup_reload_service but instead implement a custom
|
||||
# service
|
||||
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
|
||||
|
||||
async def _async_reload_platforms(_: Event | None) -> None:
|
||||
"""Discover entities for a platform."""
|
||||
config_yaml = await async_integration_yaml_config(hass, DOMAIN) or {}
|
||||
hass.data[DATA_MQTT_UPDATED_CONFIG] = config_yaml.get(DOMAIN, {})
|
||||
async_dispatcher_send(hass, MQTT_RELOADED)
|
||||
|
||||
async def async_forward_entry_setup():
|
||||
"""Forward the config entry setup to the platforms."""
|
||||
async with hass.data[DATA_CONFIG_ENTRY_LOCK]:
|
||||
@ -374,6 +396,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
await hass.config_entries.async_forward_entry_setup(
|
||||
entry, component
|
||||
)
|
||||
# Setup reload service after all platforms have loaded
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen("event_mqtt_reloaded", _async_reload_platforms)
|
||||
)
|
||||
|
||||
hass.async_create_task(async_forward_entry_setup())
|
||||
|
||||
|
@ -35,6 +35,7 @@ DATA_CONFIG_ENTRY_LOCK = "mqtt_config_entry_lock"
|
||||
DATA_MQTT = "mqtt"
|
||||
DATA_MQTT_CONFIG = "mqtt_config"
|
||||
DATA_MQTT_RELOAD_NEEDED = "mqtt_reload_needed"
|
||||
DATA_MQTT_UPDATED_CONFIG = "mqtt_updated_config"
|
||||
|
||||
DEFAULT_PREFIX = "homeassistant"
|
||||
DEFAULT_BIRTH_WILL_TOPIC = DEFAULT_PREFIX + "/status"
|
||||
@ -63,6 +64,7 @@ DOMAIN = "mqtt"
|
||||
|
||||
MQTT_CONNECTED = "mqtt_connected"
|
||||
MQTT_DISCONNECTED = "mqtt_disconnected"
|
||||
MQTT_RELOADED = "mqtt_reloaded"
|
||||
|
||||
PAYLOAD_EMPTY_JSON = "{}"
|
||||
PAYLOAD_NONE = "None"
|
||||
|
@ -25,7 +25,6 @@ from homeassistant.const import (
|
||||
STATE_CLOSING,
|
||||
STATE_OPEN,
|
||||
STATE_OPENING,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@ -470,7 +469,6 @@ class MqttCover(MqttEntity, CoverEntity):
|
||||
}
|
||||
|
||||
if self._config.get(CONF_TILT_STATUS_TOPIC) is not None:
|
||||
self._tilt_value = STATE_UNKNOWN
|
||||
topics["tilt_status_topic"] = {
|
||||
"topic": self._config.get(CONF_TILT_STATUS_TOPIC),
|
||||
"msg_callback": tilt_message_received,
|
||||
|
@ -48,10 +48,6 @@ from homeassistant.helpers.entity import (
|
||||
async_generate_entity_id,
|
||||
)
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.reload import (
|
||||
async_integration_yaml_config,
|
||||
async_setup_reload_service,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import debug_info, subscription
|
||||
@ -67,13 +63,14 @@ from .const import (
|
||||
DATA_MQTT,
|
||||
DATA_MQTT_CONFIG,
|
||||
DATA_MQTT_RELOAD_NEEDED,
|
||||
DATA_MQTT_UPDATED_CONFIG,
|
||||
DEFAULT_ENCODING,
|
||||
DEFAULT_PAYLOAD_AVAILABLE,
|
||||
DEFAULT_PAYLOAD_NOT_AVAILABLE,
|
||||
DOMAIN,
|
||||
MQTT_CONNECTED,
|
||||
MQTT_DISCONNECTED,
|
||||
PLATFORMS,
|
||||
MQTT_RELOADED,
|
||||
)
|
||||
from .debug_info import log_message, log_messages
|
||||
from .discovery import (
|
||||
@ -270,14 +267,11 @@ async def async_setup_platform_discovery(
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Set up platform discovery for manual config."""
|
||||
|
||||
async def _async_discover_entities(event: Event | None) -> None:
|
||||
async def _async_discover_entities() -> None:
|
||||
"""Discover entities for a platform."""
|
||||
if event:
|
||||
if DATA_MQTT_UPDATED_CONFIG in hass.data:
|
||||
# The platform has been reloaded
|
||||
config_yaml = await async_integration_yaml_config(hass, DOMAIN)
|
||||
if not config_yaml:
|
||||
return
|
||||
config_yaml = config_yaml.get(DOMAIN, {})
|
||||
config_yaml = hass.data[DATA_MQTT_UPDATED_CONFIG]
|
||||
else:
|
||||
config_yaml = hass.data.get(DATA_MQTT_CONFIG, {})
|
||||
if not config_yaml:
|
||||
@ -293,8 +287,8 @@ async def async_setup_platform_discovery(
|
||||
)
|
||||
)
|
||||
|
||||
unsub = hass.bus.async_listen("event_mqtt_reloaded", _async_discover_entities)
|
||||
await _async_discover_entities(None)
|
||||
unsub = async_dispatcher_connect(hass, MQTT_RELOADED, _async_discover_entities)
|
||||
await _async_discover_entities()
|
||||
return unsub
|
||||
|
||||
|
||||
@ -359,7 +353,6 @@ async def async_setup_platform_helper(
|
||||
async_setup_entities: SetupEntity,
|
||||
) -> None:
|
||||
"""Return true if platform setup should be aborted."""
|
||||
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
|
||||
if not bool(hass.config_entries.async_entries(DOMAIN)):
|
||||
hass.data[DATA_MQTT_RELOAD_NEEDED] = None
|
||||
_LOGGER.warning(
|
||||
|
@ -18,7 +18,6 @@ from .const import (
|
||||
KEY_COORDINATOR_SPEED,
|
||||
KEY_COORDINATOR_TRAFFIC,
|
||||
KEY_ROUTER,
|
||||
MODE_ROUTER,
|
||||
PLATFORMS,
|
||||
)
|
||||
from .errors import CannotLoginException
|
||||
@ -72,7 +71,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_update_devices() -> bool:
|
||||
"""Fetch data from the router."""
|
||||
if router.mode == MODE_ROUTER:
|
||||
if router.track_devices:
|
||||
return await router.async_update_device_trackers()
|
||||
return False
|
||||
|
||||
@ -107,7 +106,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
update_interval=SPEED_TEST_INTERVAL,
|
||||
)
|
||||
|
||||
if router.mode == MODE_ROUTER:
|
||||
if router.track_devices:
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
await coordinator_traffic_meter.async_config_entry_first_refresh()
|
||||
|
||||
@ -134,7 +133,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
if not hass.data[DOMAIN]:
|
||||
hass.data.pop(DOMAIN)
|
||||
|
||||
if router.mode != MODE_ROUTER:
|
||||
if not router.track_devices:
|
||||
router_id = None
|
||||
# Remove devices that are no longer tracked
|
||||
device_registry = dr.async_get(hass)
|
||||
|
@ -80,6 +80,7 @@ class NetgearRouter:
|
||||
self.hardware_version = ""
|
||||
self.serial_number = ""
|
||||
|
||||
self.track_devices = True
|
||||
self.method_version = 1
|
||||
consider_home_int = entry.options.get(
|
||||
CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME.total_seconds()
|
||||
@ -112,11 +113,23 @@ class NetgearRouter:
|
||||
self.serial_number = self._info["SerialNumber"]
|
||||
self.mode = self._info.get("DeviceMode", MODE_ROUTER)
|
||||
|
||||
enabled_entries = [
|
||||
entry
|
||||
for entry in self.hass.config_entries.async_entries(DOMAIN)
|
||||
if entry.disabled_by is None
|
||||
]
|
||||
self.track_devices = self.mode == MODE_ROUTER or len(enabled_entries) == 1
|
||||
_LOGGER.debug(
|
||||
"Netgear track_devices = '%s', device mode '%s'",
|
||||
self.track_devices,
|
||||
self.mode,
|
||||
)
|
||||
|
||||
for model in MODELS_V2:
|
||||
if self.model.startswith(model):
|
||||
self.method_version = 2
|
||||
|
||||
if self.method_version == 2 and self.mode == MODE_ROUTER:
|
||||
if self.method_version == 2 and self.track_devices:
|
||||
if not self._api.get_attached_devices_2():
|
||||
_LOGGER.error(
|
||||
"Netgear Model '%s' in MODELS_V2 list, but failed to get attached devices using V2",
|
||||
@ -133,7 +146,7 @@ class NetgearRouter:
|
||||
return False
|
||||
|
||||
# set already known devices to away instead of unavailable
|
||||
if self.mode == MODE_ROUTER:
|
||||
if self.track_devices:
|
||||
device_registry = dr.async_get(self.hass)
|
||||
devices = dr.async_entries_for_config_entry(device_registry, self.entry_id)
|
||||
for device_entry in devices:
|
||||
|
@ -5,6 +5,7 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
import logging
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
RestoreSensor,
|
||||
@ -34,6 +35,8 @@ from .const import (
|
||||
)
|
||||
from .router import NetgearDeviceEntity, NetgearRouter, NetgearRouterEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SENSOR_TYPES = {
|
||||
"type": SensorEntityDescription(
|
||||
key="type",
|
||||
@ -114,7 +117,7 @@ SENSOR_TRAFFIC_TYPES = [
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:upload",
|
||||
index=0,
|
||||
value=lambda data: data[0] if data is not None else None,
|
||||
value=lambda data: data[0],
|
||||
),
|
||||
NetgearSensorEntityDescription(
|
||||
key="NewWeekUpload",
|
||||
@ -123,7 +126,7 @@ SENSOR_TRAFFIC_TYPES = [
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:upload",
|
||||
index=1,
|
||||
value=lambda data: data[1] if data is not None else None,
|
||||
value=lambda data: data[1],
|
||||
),
|
||||
NetgearSensorEntityDescription(
|
||||
key="NewWeekDownload",
|
||||
@ -132,7 +135,7 @@ SENSOR_TRAFFIC_TYPES = [
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:download",
|
||||
index=0,
|
||||
value=lambda data: data[0] if data is not None else None,
|
||||
value=lambda data: data[0],
|
||||
),
|
||||
NetgearSensorEntityDescription(
|
||||
key="NewWeekDownload",
|
||||
@ -141,7 +144,7 @@ SENSOR_TRAFFIC_TYPES = [
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:download",
|
||||
index=1,
|
||||
value=lambda data: data[1] if data is not None else None,
|
||||
value=lambda data: data[1],
|
||||
),
|
||||
NetgearSensorEntityDescription(
|
||||
key="NewMonthUpload",
|
||||
@ -150,7 +153,7 @@ SENSOR_TRAFFIC_TYPES = [
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:upload",
|
||||
index=0,
|
||||
value=lambda data: data[0] if data is not None else None,
|
||||
value=lambda data: data[0],
|
||||
),
|
||||
NetgearSensorEntityDescription(
|
||||
key="NewMonthUpload",
|
||||
@ -159,7 +162,7 @@ SENSOR_TRAFFIC_TYPES = [
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:upload",
|
||||
index=1,
|
||||
value=lambda data: data[1] if data is not None else None,
|
||||
value=lambda data: data[1],
|
||||
),
|
||||
NetgearSensorEntityDescription(
|
||||
key="NewMonthDownload",
|
||||
@ -168,7 +171,7 @@ SENSOR_TRAFFIC_TYPES = [
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:download",
|
||||
index=0,
|
||||
value=lambda data: data[0] if data is not None else None,
|
||||
value=lambda data: data[0],
|
||||
),
|
||||
NetgearSensorEntityDescription(
|
||||
key="NewMonthDownload",
|
||||
@ -177,7 +180,7 @@ SENSOR_TRAFFIC_TYPES = [
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:download",
|
||||
index=1,
|
||||
value=lambda data: data[1] if data is not None else None,
|
||||
value=lambda data: data[1],
|
||||
),
|
||||
NetgearSensorEntityDescription(
|
||||
key="NewLastMonthUpload",
|
||||
@ -186,7 +189,7 @@ SENSOR_TRAFFIC_TYPES = [
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:upload",
|
||||
index=0,
|
||||
value=lambda data: data[0] if data is not None else None,
|
||||
value=lambda data: data[0],
|
||||
),
|
||||
NetgearSensorEntityDescription(
|
||||
key="NewLastMonthUpload",
|
||||
@ -195,7 +198,7 @@ SENSOR_TRAFFIC_TYPES = [
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:upload",
|
||||
index=1,
|
||||
value=lambda data: data[1] if data is not None else None,
|
||||
value=lambda data: data[1],
|
||||
),
|
||||
NetgearSensorEntityDescription(
|
||||
key="NewLastMonthDownload",
|
||||
@ -204,7 +207,7 @@ SENSOR_TRAFFIC_TYPES = [
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:download",
|
||||
index=0,
|
||||
value=lambda data: data[0] if data is not None else None,
|
||||
value=lambda data: data[0],
|
||||
),
|
||||
NetgearSensorEntityDescription(
|
||||
key="NewLastMonthDownload",
|
||||
@ -213,7 +216,7 @@ SENSOR_TRAFFIC_TYPES = [
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:download",
|
||||
index=1,
|
||||
value=lambda data: data[1] if data is not None else None,
|
||||
value=lambda data: data[1],
|
||||
),
|
||||
]
|
||||
|
||||
@ -372,6 +375,17 @@ class NetgearRouterSensorEntity(NetgearRouterEntity, RestoreSensor):
|
||||
@callback
|
||||
def async_update_device(self) -> None:
|
||||
"""Update the Netgear device."""
|
||||
if self.coordinator.data is not None:
|
||||
data = self.coordinator.data.get(self.entity_description.key)
|
||||
self._value = self.entity_description.value(data)
|
||||
if self.coordinator.data is None:
|
||||
return
|
||||
|
||||
data = self.coordinator.data.get(self.entity_description.key)
|
||||
if data is None:
|
||||
self._value = None
|
||||
_LOGGER.debug(
|
||||
"key '%s' not in Netgear router response '%s'",
|
||||
self.entity_description.key,
|
||||
data,
|
||||
)
|
||||
return
|
||||
|
||||
self._value = self.entity_description.value(data)
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "RainMachine",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/rainmachine",
|
||||
"requirements": ["regenmaschine==2022.06.0"],
|
||||
"requirements": ["regenmaschine==2022.06.1"],
|
||||
"codeowners": ["@bachya"],
|
||||
"iot_class": "local_polling",
|
||||
"homekit": {
|
||||
|
@ -5,15 +5,18 @@ from sqlalchemy import text
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
||||
def db_size_bytes(session: Session, database_name: str) -> float:
|
||||
def db_size_bytes(session: Session, database_name: str) -> float | None:
|
||||
"""Get the mysql database size."""
|
||||
return float(
|
||||
session.execute(
|
||||
text(
|
||||
"SELECT ROUND(SUM(DATA_LENGTH + INDEX_LENGTH), 2) "
|
||||
"FROM information_schema.TABLES WHERE "
|
||||
"TABLE_SCHEMA=:database_name"
|
||||
),
|
||||
{"database_name": database_name},
|
||||
).first()[0]
|
||||
)
|
||||
size = session.execute(
|
||||
text(
|
||||
"SELECT ROUND(SUM(DATA_LENGTH + INDEX_LENGTH), 2) "
|
||||
"FROM information_schema.TABLES WHERE "
|
||||
"TABLE_SCHEMA=:database_name"
|
||||
),
|
||||
{"database_name": database_name},
|
||||
).first()[0]
|
||||
|
||||
if size is None:
|
||||
return None
|
||||
|
||||
return float(size)
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Support for balance data via the Starling Bank API."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import requests
|
||||
@ -26,6 +27,7 @@ DEFAULT_SANDBOX = False
|
||||
DEFAULT_ACCOUNT_NAME = "Starling"
|
||||
|
||||
ICON = "mdi:currency-gbp"
|
||||
SCAN_INTERVAL = timedelta(seconds=180)
|
||||
|
||||
ACCOUNT_SCHEMA = vol.Schema(
|
||||
{
|
||||
|
@ -2,7 +2,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from synology_dsm import SynologyDSM
|
||||
@ -98,7 +97,7 @@ class SynoApi:
|
||||
self._async_setup_api_requests()
|
||||
|
||||
await self._hass.async_add_executor_job(self._fetch_device_configuration)
|
||||
await self.async_update()
|
||||
await self.async_update(first_setup=True)
|
||||
|
||||
@callback
|
||||
def subscribe(self, api_key: str, unique_id: str) -> Callable[[], None]:
|
||||
@ -251,7 +250,7 @@ class SynoApi:
|
||||
# ignore API errors during logout
|
||||
pass
|
||||
|
||||
async def async_update(self, now: timedelta | None = None) -> None:
|
||||
async def async_update(self, first_setup: bool = False) -> None:
|
||||
"""Update function for updating API information."""
|
||||
LOGGER.debug("Start data update for '%s'", self._entry.unique_id)
|
||||
self._async_setup_api_requests()
|
||||
@ -259,14 +258,22 @@ class SynoApi:
|
||||
await self._hass.async_add_executor_job(
|
||||
self.dsm.update, self._with_information
|
||||
)
|
||||
except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err:
|
||||
LOGGER.warning(
|
||||
"Connection error during update, fallback by reloading the entry"
|
||||
)
|
||||
except (
|
||||
SynologyDSMLoginFailedException,
|
||||
SynologyDSMRequestException,
|
||||
SynologyDSMAPIErrorException,
|
||||
) as err:
|
||||
LOGGER.debug(
|
||||
"Connection error during update of '%s' with exception: %s",
|
||||
self._entry.unique_id,
|
||||
err,
|
||||
)
|
||||
|
||||
if first_setup:
|
||||
raise err
|
||||
|
||||
LOGGER.warning(
|
||||
"Connection error during update, fallback by reloading the entry"
|
||||
)
|
||||
await self._hass.config_entries.async_reload(self._entry.entry_id)
|
||||
return
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "UniFi Protect",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/unifiprotect",
|
||||
"requirements": ["pyunifiprotect==3.6.0", "unifi-discovery==1.1.3"],
|
||||
"requirements": ["pyunifiprotect==3.9.2", "unifi-discovery==1.1.3"],
|
||||
"dependencies": ["http"],
|
||||
"codeowners": ["@briis", "@AngellusMortis", "@bdraco"],
|
||||
"quality_scale": "platinum",
|
||||
|
@ -4,10 +4,6 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/wallbox",
|
||||
"requirements": ["wallbox==0.4.9"],
|
||||
"ssdp": [],
|
||||
"zeroconf": [],
|
||||
"homekit": {},
|
||||
"dependencies": [],
|
||||
"codeowners": ["@hesselonline"],
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["wallbox"]
|
||||
|
@ -167,8 +167,12 @@ class WallboxSensor(WallboxEntity, SensorEntity):
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
if (sensor_round := self.entity_description.precision) is not None:
|
||||
"""Return the state of the sensor. Round the value when it, and the precision property are not None."""
|
||||
if (
|
||||
sensor_round := self.entity_description.precision
|
||||
) is not None and self.coordinator.data[
|
||||
self.entity_description.key
|
||||
] is not None:
|
||||
return cast(
|
||||
StateType,
|
||||
round(self.coordinator.data[self.entity_description.key], sensor_round),
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "YoLink",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/yolink",
|
||||
"requirements": ["yolink-api==0.0.6"],
|
||||
"requirements": ["yolink-api==0.0.8"],
|
||||
"dependencies": ["auth", "application_credentials"],
|
||||
"codeowners": ["@matrixd2"],
|
||||
"iot_class": "cloud_push"
|
||||
|
@ -7,7 +7,7 @@ from .backports.enum import StrEnum
|
||||
|
||||
MAJOR_VERSION: Final = 2022
|
||||
MINOR_VERSION: Final = 6
|
||||
PATCH_VERSION: Final = "4"
|
||||
PATCH_VERSION: Final = "5"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
||||
|
@ -1538,7 +1538,7 @@ pyheos==0.7.2
|
||||
pyhik==0.3.0
|
||||
|
||||
# homeassistant.components.hive
|
||||
pyhiveapi==0.5.5
|
||||
pyhiveapi==0.5.9
|
||||
|
||||
# homeassistant.components.homematic
|
||||
pyhomematic==0.1.77
|
||||
@ -1993,7 +1993,7 @@ pytrafikverket==0.2.0.1
|
||||
pyudev==0.22.0
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
pyunifiprotect==3.6.0
|
||||
pyunifiprotect==3.9.2
|
||||
|
||||
# homeassistant.components.uptimerobot
|
||||
pyuptimerobot==22.2.0
|
||||
@ -2065,7 +2065,7 @@ raincloudy==0.0.7
|
||||
raspyrfm-client==1.2.8
|
||||
|
||||
# homeassistant.components.rainmachine
|
||||
regenmaschine==2022.06.0
|
||||
regenmaschine==2022.06.1
|
||||
|
||||
# homeassistant.components.renault
|
||||
renault-api==0.1.11
|
||||
@ -2486,7 +2486,7 @@ yeelight==0.7.10
|
||||
yeelightsunflower==0.0.10
|
||||
|
||||
# homeassistant.components.yolink
|
||||
yolink-api==0.0.6
|
||||
yolink-api==0.0.8
|
||||
|
||||
# homeassistant.components.youless
|
||||
youless-api==0.16
|
||||
|
@ -1029,7 +1029,7 @@ pyhaversion==22.4.1
|
||||
pyheos==0.7.2
|
||||
|
||||
# homeassistant.components.hive
|
||||
pyhiveapi==0.5.5
|
||||
pyhiveapi==0.5.9
|
||||
|
||||
# homeassistant.components.homematic
|
||||
pyhomematic==0.1.77
|
||||
@ -1322,7 +1322,7 @@ pytrafikverket==0.2.0.1
|
||||
pyudev==0.22.0
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
pyunifiprotect==3.6.0
|
||||
pyunifiprotect==3.9.2
|
||||
|
||||
# homeassistant.components.uptimerobot
|
||||
pyuptimerobot==22.2.0
|
||||
@ -1364,7 +1364,7 @@ rachiopy==1.0.3
|
||||
radios==0.1.1
|
||||
|
||||
# homeassistant.components.rainmachine
|
||||
regenmaschine==2022.06.0
|
||||
regenmaschine==2022.06.1
|
||||
|
||||
# homeassistant.components.renault
|
||||
renault-api==0.1.11
|
||||
@ -1638,7 +1638,7 @@ yalexs==1.1.25
|
||||
yeelight==0.7.10
|
||||
|
||||
# homeassistant.components.yolink
|
||||
yolink-api==0.0.6
|
||||
yolink-api==0.0.8
|
||||
|
||||
# homeassistant.components.youless
|
||||
youless-api==0.16
|
||||
|
@ -1,5 +1,5 @@
|
||||
[metadata]
|
||||
version = 2022.6.4
|
||||
version = 2022.6.5
|
||||
url = https://www.home-assistant.io/
|
||||
|
||||
[options]
|
||||
|
@ -23,6 +23,7 @@ VALID_CONFIG_1 = {feedreader.DOMAIN: {CONF_URLS: [URL]}}
|
||||
VALID_CONFIG_2 = {feedreader.DOMAIN: {CONF_URLS: [URL], CONF_SCAN_INTERVAL: 60}}
|
||||
VALID_CONFIG_3 = {feedreader.DOMAIN: {CONF_URLS: [URL], CONF_MAX_ENTRIES: 100}}
|
||||
VALID_CONFIG_4 = {feedreader.DOMAIN: {CONF_URLS: [URL], CONF_MAX_ENTRIES: 5}}
|
||||
VALID_CONFIG_5 = {feedreader.DOMAIN: {CONF_URLS: [URL], CONF_MAX_ENTRIES: 1}}
|
||||
|
||||
|
||||
def load_fixture_bytes(src):
|
||||
@ -56,6 +57,12 @@ def fixture_feed_three_events(hass):
|
||||
return load_fixture_bytes("feedreader3.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="feed_atom_event")
|
||||
def fixture_feed_atom_event(hass):
|
||||
"""Load test feed data for atom event."""
|
||||
return load_fixture_bytes("feedreader5.xml")
|
||||
|
||||
|
||||
@pytest.fixture(name="events")
|
||||
async def fixture_events(hass):
|
||||
"""Fixture that catches alexa events."""
|
||||
@ -98,7 +105,7 @@ async def test_setup_max_entries(hass):
|
||||
|
||||
|
||||
async def test_feed(hass, events, feed_one_event):
|
||||
"""Test simple feed with valid data."""
|
||||
"""Test simple rss feed with valid data."""
|
||||
with patch(
|
||||
"feedparser.http.get",
|
||||
return_value=feed_one_event,
|
||||
@ -120,6 +127,29 @@ async def test_feed(hass, events, feed_one_event):
|
||||
assert events[0].data.published_parsed.tm_min == 10
|
||||
|
||||
|
||||
async def test_atom_feed(hass, events, feed_atom_event):
|
||||
"""Test simple atom feed with valid data."""
|
||||
with patch(
|
||||
"feedparser.http.get",
|
||||
return_value=feed_atom_event,
|
||||
):
|
||||
assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_5)
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(events) == 1
|
||||
assert events[0].data.title == "Atom-Powered Robots Run Amok"
|
||||
assert events[0].data.description == "Some text."
|
||||
assert events[0].data.link == "http://example.org/2003/12/13/atom03"
|
||||
assert events[0].data.id == "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a"
|
||||
assert events[0].data.updated_parsed.tm_year == 2003
|
||||
assert events[0].data.updated_parsed.tm_mon == 12
|
||||
assert events[0].data.updated_parsed.tm_mday == 13
|
||||
assert events[0].data.updated_parsed.tm_hour == 18
|
||||
assert events[0].data.updated_parsed.tm_min == 30
|
||||
|
||||
|
||||
async def test_feed_updates(hass, events, feed_one_event, feed_two_event):
|
||||
"""Test feed updates."""
|
||||
side_effect = [
|
||||
|
@ -33,16 +33,6 @@ async def test_import_flow(hass):
|
||||
"AccessToken": "mock-access-token",
|
||||
},
|
||||
},
|
||||
), patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.device_registration",
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.getDeviceData",
|
||||
return_value=[
|
||||
"mock-device-group-key",
|
||||
"mock-device-key",
|
||||
"mock-device-password",
|
||||
],
|
||||
), patch(
|
||||
"homeassistant.components.hive.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
@ -67,11 +57,6 @@ async def test_import_flow(hass):
|
||||
},
|
||||
"ChallengeName": "SUCCESS",
|
||||
},
|
||||
"device_data": [
|
||||
"mock-device-group-key",
|
||||
"mock-device-key",
|
||||
"mock-device-password",
|
||||
],
|
||||
}
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
@ -96,16 +81,6 @@ async def test_user_flow(hass):
|
||||
"AccessToken": "mock-access-token",
|
||||
},
|
||||
},
|
||||
), patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.device_registration",
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.getDeviceData",
|
||||
return_value=[
|
||||
"mock-device-group-key",
|
||||
"mock-device-key",
|
||||
"mock-device-password",
|
||||
],
|
||||
), patch(
|
||||
"homeassistant.components.hive.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
@ -130,11 +105,6 @@ async def test_user_flow(hass):
|
||||
},
|
||||
"ChallengeName": "SUCCESS",
|
||||
},
|
||||
"device_data": [
|
||||
"mock-device-group-key",
|
||||
"mock-device-key",
|
||||
"mock-device-password",
|
||||
],
|
||||
}
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
|
@ -1256,12 +1256,8 @@ async def test_tilt_defaults(hass, mqtt_mock_entry_with_yaml_config):
|
||||
await mqtt_mock_entry_with_yaml_config()
|
||||
|
||||
state_attributes_dict = hass.states.get("cover.test").attributes
|
||||
assert ATTR_CURRENT_TILT_POSITION in state_attributes_dict
|
||||
|
||||
current_cover_position = hass.states.get("cover.test").attributes[
|
||||
ATTR_CURRENT_TILT_POSITION
|
||||
]
|
||||
assert current_cover_position == STATE_UNKNOWN
|
||||
# Tilt position is not yet known
|
||||
assert ATTR_CURRENT_TILT_POSITION not in state_attributes_dict
|
||||
|
||||
|
||||
async def test_tilt_via_invocation_defaults(hass, mqtt_mock_entry_with_yaml_config):
|
||||
|
@ -168,7 +168,7 @@ async def sensor_fixture(
|
||||
sensor_obj.motion_detected_at = now - timedelta(hours=1)
|
||||
sensor_obj.open_status_changed_at = now - timedelta(hours=1)
|
||||
sensor_obj.alarm_triggered_at = now - timedelta(hours=1)
|
||||
sensor_obj.tampering_detected_at = now - timedelta(hours=1)
|
||||
sensor_obj.tampering_detected_at = None
|
||||
|
||||
mock_entry.api.bootstrap.reset_objects()
|
||||
mock_entry.api.bootstrap.nvr.system_info.storage.devices = []
|
||||
@ -204,7 +204,7 @@ async def sensor_none_fixture(
|
||||
sensor_obj.mount_type = MountType.LEAK
|
||||
sensor_obj.battery_status.is_low = False
|
||||
sensor_obj.alarm_settings.is_enabled = False
|
||||
sensor_obj.tampering_detected_at = now - timedelta(hours=1)
|
||||
sensor_obj.tampering_detected_at = None
|
||||
|
||||
mock_entry.api.bootstrap.reset_objects()
|
||||
mock_entry.api.bootstrap.nvr.system_info.storage.devices = []
|
||||
|
18
tests/fixtures/feedreader5.xml
vendored
Normal file
18
tests/fixtures/feedreader5.xml
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed
|
||||
xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Example Feed</title>
|
||||
<link href="http://example.org/"/>
|
||||
<updated>2003-12-13T18:30:02Z</updated>
|
||||
<author>
|
||||
<name>John Doe</name>
|
||||
</author>
|
||||
<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
|
||||
<entry>
|
||||
<title>Atom-Powered Robots Run Amok</title>
|
||||
<link href="http://example.org/2003/12/13/atom03"/>
|
||||
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
|
||||
<updated>2003-12-13T18:30:02Z</updated>
|
||||
<summary>Some text.</summary>
|
||||
</entry>
|
||||
</feed>
|
Loading…
x
Reference in New Issue
Block a user