mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 02:07:09 +00:00
Ensure AirVisual Pro uses long-running Samba connection (#83869)
This commit is contained in:
parent
c7d1402320
commit
9aaeefeb8e
@ -3,7 +3,7 @@
|
|||||||
"name": "AirVisual Cloud",
|
"name": "AirVisual Cloud",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
||||||
"requirements": ["pyairvisual==2022.11.1"],
|
"requirements": ["pyairvisual==2022.12.0"],
|
||||||
"codeowners": ["@bachya"],
|
"codeowners": ["@bachya"],
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pyairvisual", "pysmb"],
|
"loggers": ["pyairvisual", "pysmb"],
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
"""The AirVisual Pro integration."""
|
"""The AirVisual Pro integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from contextlib import suppress
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pyairvisual import NodeSamba
|
from pyairvisual import NodeSamba
|
||||||
from pyairvisual.node import NodeProError
|
from pyairvisual.node import NodeConnectionError, NodeProError
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, Platform
|
from homeassistant.const import (
|
||||||
from homeassistant.core import HomeAssistant, callback
|
CONF_IP_ADDRESS,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
|
from homeassistant.core import Event, HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
|
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
|
||||||
from homeassistant.helpers.update_coordinator import (
|
from homeassistant.helpers.update_coordinator import (
|
||||||
CoordinatorEntity,
|
CoordinatorEntity,
|
||||||
@ -24,19 +33,41 @@ PLATFORMS = [Platform.SENSOR]
|
|||||||
UPDATE_INTERVAL = timedelta(minutes=1)
|
UPDATE_INTERVAL = timedelta(minutes=1)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AirVisualProData:
|
||||||
|
"""Define a data class."""
|
||||||
|
|
||||||
|
coordinator: DataUpdateCoordinator
|
||||||
|
node: NodeSamba
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up AirVisual Pro from a config entry."""
|
"""Set up AirVisual Pro from a config entry."""
|
||||||
|
node = NodeSamba(entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD])
|
||||||
|
|
||||||
|
try:
|
||||||
|
await node.async_connect()
|
||||||
|
except NodeProError as err:
|
||||||
|
raise ConfigEntryNotReady() from err
|
||||||
|
|
||||||
|
reload_task: asyncio.Task | None = None
|
||||||
|
|
||||||
async def async_get_data() -> dict[str, Any]:
|
async def async_get_data() -> dict[str, Any]:
|
||||||
"""Get data from the device."""
|
"""Get data from the device."""
|
||||||
try:
|
try:
|
||||||
async with NodeSamba(
|
data = await node.async_get_latest_measurements()
|
||||||
entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD]
|
except NodeConnectionError as err:
|
||||||
) as node:
|
nonlocal reload_task
|
||||||
return await node.async_get_latest_measurements()
|
if not reload_task:
|
||||||
|
reload_task = hass.async_create_task(
|
||||||
|
hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
)
|
||||||
|
raise UpdateFailed(f"Connection to Pro unit lost: {err}") from err
|
||||||
except NodeProError as err:
|
except NodeProError as err:
|
||||||
raise UpdateFailed(f"Error while retrieving data: {err}") from err
|
raise UpdateFailed(f"Error while retrieving data: {err}") from err
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
coordinator = DataUpdateCoordinator(
|
coordinator = DataUpdateCoordinator(
|
||||||
hass,
|
hass,
|
||||||
LOGGER,
|
LOGGER,
|
||||||
@ -46,7 +77,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = AirVisualProData(
|
||||||
|
coordinator=coordinator, node=node
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_shutdown(_: Event) -> None:
|
||||||
|
"""Define an event handler to disconnect from the websocket."""
|
||||||
|
nonlocal reload_task
|
||||||
|
if reload_task:
|
||||||
|
with suppress(asyncio.CancelledError):
|
||||||
|
reload_task.cancel()
|
||||||
|
await node.async_disconnect()
|
||||||
|
|
||||||
|
entry.async_on_unload(
|
||||||
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_shutdown)
|
||||||
|
)
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
@ -56,7 +101,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
data = hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
await data.node.async_disconnect()
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ from homeassistant.components.diagnostics import async_redact_data
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_PASSWORD
|
from homeassistant.const import CONF_PASSWORD
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
|
||||||
|
|
||||||
|
from . import AirVisualProData
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
CONF_MAC_ADDRESS = "mac_address"
|
CONF_MAC_ADDRESS = "mac_address"
|
||||||
@ -25,12 +25,12 @@ async def async_get_config_entry_diagnostics(
|
|||||||
hass: HomeAssistant, entry: ConfigEntry
|
hass: HomeAssistant, entry: ConfigEntry
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Return diagnostics for a config entry."""
|
"""Return diagnostics for a config entry."""
|
||||||
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
data: AirVisualProData = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
return async_redact_data(
|
return async_redact_data(
|
||||||
{
|
{
|
||||||
"entry": entry.as_dict(),
|
"entry": entry.as_dict(),
|
||||||
"data": coordinator.data,
|
"data": data.coordinator.data,
|
||||||
},
|
},
|
||||||
TO_REDACT,
|
TO_REDACT,
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "AirVisual Pro",
|
"name": "AirVisual Pro",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/airvisual_pro",
|
"documentation": "https://www.home-assistant.io/integrations/airvisual_pro",
|
||||||
"requirements": ["pyairvisual==2022.11.1"],
|
"requirements": ["pyairvisual==2022.12.0"],
|
||||||
"codeowners": ["@bachya"],
|
"codeowners": ["@bachya"],
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pyairvisual", "pysmb"],
|
"loggers": ["pyairvisual", "pysmb"],
|
||||||
|
@ -19,9 +19,8 @@ from homeassistant.const import (
|
|||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity import EntityCategory
|
from homeassistant.helpers.entity import EntityCategory
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
|
||||||
|
|
||||||
from . import AirVisualProEntity
|
from . import AirVisualProData, AirVisualProEntity
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
SENSOR_KIND_AQI = "air_quality_index"
|
SENSOR_KIND_AQI = "air_quality_index"
|
||||||
@ -113,10 +112,10 @@ async def async_setup_entry(
|
|||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up AirVisual sensors based on a config entry."""
|
"""Set up AirVisual sensors based on a config entry."""
|
||||||
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
data: AirVisualProData = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
AirVisualProSensor(coordinator, description)
|
AirVisualProSensor(data.coordinator, description)
|
||||||
for description in SENSOR_DESCRIPTIONS
|
for description in SENSOR_DESCRIPTIONS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1464,7 +1464,7 @@ pyairnow==1.1.0
|
|||||||
|
|
||||||
# homeassistant.components.airvisual
|
# homeassistant.components.airvisual
|
||||||
# homeassistant.components.airvisual_pro
|
# homeassistant.components.airvisual_pro
|
||||||
pyairvisual==2022.11.1
|
pyairvisual==2022.12.0
|
||||||
|
|
||||||
# homeassistant.components.almond
|
# homeassistant.components.almond
|
||||||
pyalmond==0.0.2
|
pyalmond==0.0.2
|
||||||
|
@ -1052,7 +1052,7 @@ pyairnow==1.1.0
|
|||||||
|
|
||||||
# homeassistant.components.airvisual
|
# homeassistant.components.airvisual
|
||||||
# homeassistant.components.airvisual_pro
|
# homeassistant.components.airvisual_pro
|
||||||
pyairvisual==2022.11.1
|
pyairvisual==2022.12.0
|
||||||
|
|
||||||
# homeassistant.components.almond
|
# homeassistant.components.almond
|
||||||
pyalmond==0.0.2
|
pyalmond==0.0.2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user