diff --git a/homeassistant/components/nam/config_flow.py b/homeassistant/components/nam/config_flow.py index 5b85457e741..d3fec1ddbc2 100644 --- a/homeassistant/components/nam/config_flow.py +++ b/homeassistant/components/nam/config_flow.py @@ -2,7 +2,6 @@ from __future__ import annotations -import asyncio from collections.abc import Mapping from dataclasses import dataclass import logging @@ -50,8 +49,7 @@ async def async_get_config(hass: HomeAssistant, host: str) -> NamConfig: options = ConnectionOptions(host) nam = await NettigoAirMonitor.create(websession, options) - async with asyncio.timeout(10): - mac = await nam.async_get_mac_address() + mac = await nam.async_get_mac_address() return NamConfig(mac, nam.auth_enabled) @@ -66,8 +64,7 @@ async def async_check_credentials( nam = await NettigoAirMonitor.create(websession, options) - async with asyncio.timeout(10): - await nam.async_check_credentials() + await nam.async_check_credentials() class NAMFlowHandler(ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/nam/const.py b/homeassistant/components/nam/const.py index 66718b01c3f..2e4d6b0c85a 100644 --- a/homeassistant/components/nam/const.py +++ b/homeassistant/components/nam/const.py @@ -46,7 +46,7 @@ ATTR_SPS30_P2: Final = f"{ATTR_SPS30}{SUFFIX_P2}" ATTR_SPS30_P4: Final = f"{ATTR_SPS30}{SUFFIX_P4}" ATTR_UPTIME: Final = "uptime" -DEFAULT_UPDATE_INTERVAL: Final = timedelta(minutes=6) +DEFAULT_UPDATE_INTERVAL: Final = timedelta(minutes=4) DOMAIN: Final = "nam" MANUFACTURER: Final = "Nettigo" diff --git a/homeassistant/components/nam/coordinator.py b/homeassistant/components/nam/coordinator.py index ec99b3dfb17..5019f0e3a1d 100644 --- a/homeassistant/components/nam/coordinator.py +++ b/homeassistant/components/nam/coordinator.py @@ -1,15 +1,14 @@ """The Nettigo Air Monitor coordinator.""" -import asyncio import logging -from aiohttp.client_exceptions import ClientConnectorError from nettigo_air_monitor import ( ApiError, InvalidSensorDataError, NAMSensors, NettigoAirMonitor, ) +from tenacity import RetryError from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo @@ -47,11 +46,10 @@ class NAMDataUpdateCoordinator(DataUpdateCoordinator[NAMSensors]): async def _async_update_data(self) -> NAMSensors: """Update data via library.""" try: - async with asyncio.timeout(10): - data = await self.nam.async_update() + data = await self.nam.async_update() # We do not need to catch AuthFailed exception here because sensor data is # always available without authorization. - except (ApiError, ClientConnectorError, InvalidSensorDataError) as error: + except (ApiError, InvalidSensorDataError, RetryError) as error: raise UpdateFailed(error) from error return data diff --git a/homeassistant/components/nam/manifest.json b/homeassistant/components/nam/manifest.json index d4638cbdbbe..a3cb6f54c7c 100644 --- a/homeassistant/components/nam/manifest.json +++ b/homeassistant/components/nam/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_polling", "loggers": ["nettigo_air_monitor"], "quality_scale": "platinum", - "requirements": ["nettigo-air-monitor==3.0.1"], + "requirements": ["nettigo-air-monitor==3.1.0"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 13df2b16b60..25df9c24932 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1386,7 +1386,7 @@ netdata==1.1.0 netmap==0.7.0.2 # homeassistant.components.nam -nettigo-air-monitor==3.0.1 +nettigo-air-monitor==3.1.0 # homeassistant.components.neurio_energy neurio==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 48fbe7913ee..0638dc3e442 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1122,7 +1122,7 @@ nessclient==1.0.0 netmap==0.7.0.2 # homeassistant.components.nam -nettigo-air-monitor==3.0.1 +nettigo-air-monitor==3.1.0 # homeassistant.components.nexia nexia==2.0.8 diff --git a/tests/components/nam/test_sensor.py b/tests/components/nam/test_sensor.py index b9d6c20939e..9280336779e 100644 --- a/tests/components/nam/test_sensor.py +++ b/tests/components/nam/test_sensor.py @@ -5,9 +5,11 @@ from unittest.mock import AsyncMock, Mock, patch from freezegun.api import FrozenDateTimeFactory from nettigo_air_monitor import ApiError +import pytest from syrupy import SnapshotAssertion +from tenacity import RetryError -from homeassistant.components.nam.const import DOMAIN +from homeassistant.components.nam.const import DEFAULT_UPDATE_INTERVAL, DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorDeviceClass from homeassistant.const import ( ATTR_DEVICE_CLASS, @@ -96,7 +98,10 @@ async def test_incompleta_data_after_device_restart(hass: HomeAssistant) -> None assert state.state == STATE_UNAVAILABLE -async def test_availability(hass: HomeAssistant) -> None: +@pytest.mark.parametrize("exc", [ApiError("API Error"), RetryError]) +async def test_availability( + hass: HomeAssistant, freezer: FrozenDateTimeFactory, exc: Exception +) -> None: """Ensure that we mark the entities unavailable correctly when device causes an error.""" nam_data = load_json_object_fixture("nam/nam_data.json") @@ -107,22 +112,21 @@ async def test_availability(hass: HomeAssistant) -> None: assert state.state != STATE_UNAVAILABLE assert state.state == "7.6" - future = utcnow() + timedelta(minutes=6) with ( patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), patch( "homeassistant.components.nam.NettigoAirMonitor._async_http_request", - side_effect=ApiError("API Error"), + side_effect=exc, ), ): - async_fire_time_changed(hass, future) + freezer.tick(DEFAULT_UPDATE_INTERVAL) + async_fire_time_changed(hass) await hass.async_block_till_done() state = hass.states.get("sensor.nettigo_air_monitor_bme280_temperature") assert state assert state.state == STATE_UNAVAILABLE - future = utcnow() + timedelta(minutes=12) update_response = Mock(json=AsyncMock(return_value=nam_data)) with ( patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), @@ -131,7 +135,8 @@ async def test_availability(hass: HomeAssistant) -> None: return_value=update_response, ), ): - async_fire_time_changed(hass, future) + freezer.tick(DEFAULT_UPDATE_INTERVAL) + async_fire_time_changed(hass) await hass.async_block_till_done() state = hass.states.get("sensor.nettigo_air_monitor_bme280_temperature")