Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions homeassistant/components/airthings_ble/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
VOLUME_PICOCURIE = "pCi/L"

DEFAULT_SCAN_INTERVAL = 300
RADON_SCAN_INTERVAL = 1800

MAX_RETRIES_AFTER_STARTUP = 5
14 changes: 12 additions & 2 deletions homeassistant/components/airthings_ble/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
from datetime import timedelta
import logging

from airthings_ble import AirthingsBluetoothDeviceData, AirthingsDevice
from airthings_ble import (
AirthingsBluetoothDeviceData,
AirthingsDevice,
AirthingsDeviceType,
)
from bleak.backends.device import BLEDevice
from bleak_retry_connector import close_stale_connections_by_address

Expand All @@ -16,7 +20,7 @@
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.unit_system import METRIC_SYSTEM

from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, RADON_SCAN_INTERVAL

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -65,4 +69,10 @@ async def _async_update_data(self) -> AirthingsDevice:
except Exception as err:
raise UpdateFailed(f"Unable to fetch data: {err}") from err

if (
self.update_interval == timedelta(seconds=DEFAULT_SCAN_INTERVAL)
and data.model == AirthingsDeviceType.CORENTIUM_HOME_2
):
self.update_interval = timedelta(seconds=RADON_SCAN_INTERVAL)
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scan interval adjustment logic runs on every update but only changes the interval once. This check will execute unnecessarily on all subsequent updates after the initial adjustment. Consider using a flag or moving this logic to a one-time initialization location (e.g., after first successful data fetch) to avoid repeated checks.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we first fetch data, we don't know the type of device. As copilot says, we could add a flag, but isn't that a bit too much?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ideally we store this in the entry data so we don't have to recalculate this. I think we can add it behind a boolean for now, but we should avoid checking this on every update


return data
18 changes: 18 additions & 0 deletions tests/components/airthings_ble/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,24 @@ def patch_airthings_device_update():
address="cc:cc:cc:cc:cc:cc",
)

CORENTIUM_HOME_2_DEVICE_INFO = AirthingsDevice(
manufacturer="Airthings AS",
hw_version="REV X",
sw_version="R-SUB-1.3.4-master+0",
model=AirthingsDeviceType.CORENTIUM_HOME_2,
name="Airthings Corentium Home 2",
identifier="123456",
sensors={
"connectivity_mode": "Bluetooth",
"battery": 90,
"temperature": 20.0,
"humidity": 55.0,
"radon_1day_avg": 45,
"radon_1day_level": "low",
},
address="cc:cc:cc:cc:cc:cc",
)

TEMPERATURE_V1 = MockEntity(
unique_id="Airthings Wave Plus 123456_temperature",
name="Airthings Wave Plus 123456 Temperature",
Expand Down
54 changes: 54 additions & 0 deletions tests/components/airthings_ble/test_coordinator.py
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that we should still test this via test_sensor.py, and just use the freezer to make sure we only update when we expect it to

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored it now, and it's now storing the model name in the config flow. If not set, it will be set when setting up the device (e.g. already configured the device before this PR).

Also added more tests

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, we should still not create coordinators directly. instead, let's test the migration in test_init.py and the update interval via test_sensor.py

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Test the Airthings BLE coordinator."""

from datetime import timedelta
from unittest.mock import patch

import pytest

from homeassistant.components.airthings_ble.const import (
DEFAULT_SCAN_INTERVAL,
DOMAIN,
RADON_SCAN_INTERVAL,
)
from homeassistant.components.airthings_ble.coordinator import (
AirthingsBLEDataUpdateCoordinator,
)
from homeassistant.core import HomeAssistant

from . import CORENTIUM_HOME_2_DEVICE_INFO, WAVE_DEVICE_INFO, WAVE_ENHANCE_DEVICE_INFO

from tests.components.bluetooth import MockConfigEntry, generate_ble_device
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect import path for MockConfigEntry. The tests.components.bluetooth module does not export MockConfigEntry in its all list. Import MockConfigEntry from tests.common instead, matching the pattern used in other test files in this integration.

Suggested change
from tests.components.bluetooth import MockConfigEntry, generate_ble_device
from tests.common import MockConfigEntry
from tests.components.bluetooth import generate_ble_device

Copilot uses AI. Check for mistakes.


@pytest.mark.parametrize(
("device_info", "expected_interval"),
[
(CORENTIUM_HOME_2_DEVICE_INFO, RADON_SCAN_INTERVAL),
(WAVE_DEVICE_INFO, DEFAULT_SCAN_INTERVAL),
(WAVE_ENHANCE_DEVICE_INFO, DEFAULT_SCAN_INTERVAL),
],
)
async def test_scan_interval_adjustment(
hass: HomeAssistant,
device_info,
expected_interval: int,
) -> None:
"""Test that scan interval is adjusted based on device type."""
coordinator = AirthingsBLEDataUpdateCoordinator(
hass=hass,
entry=MockConfigEntry(
domain=DOMAIN,
unique_id="cc:cc:cc:cc:cc:cc",
),
)
coordinator.ble_device = generate_ble_device("cc:cc:cc:cc:cc:cc", device_info.name)

assert coordinator.update_interval == timedelta(seconds=DEFAULT_SCAN_INTERVAL)

with patch(
"homeassistant.components.airthings_ble.coordinator.AirthingsBluetoothDeviceData.update_device",
return_value=device_info,
):
await coordinator.async_refresh()

assert coordinator.update_interval == timedelta(seconds=expected_interval)
Loading