Create an issue when Bluetooth is active on old HAOS ()

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
pull/78671/head^2
J. Nick Koston 2022-09-19 15:02:13 -10:00 committed by GitHub
parent caba202efa
commit 12856dea05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 176 additions and 8 deletions
homeassistant/components/bluetooth
tests/components/bluetooth

View File

@ -8,14 +8,24 @@ import platform
from typing import TYPE_CHECKING, cast
import async_timeout
from awesomeversion import AwesomeVersion
from homeassistant.components import usb
from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY, ConfigEntry
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.config_entries import (
SOURCE_IGNORE,
SOURCE_INTEGRATION_DISCOVERY,
ConfigEntry,
)
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, discovery_flow
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.issue_registry import (
IssueSeverity,
async_create_issue,
async_delete_issue,
)
from homeassistant.loader import async_get_bluetooth
from . import models
@ -71,6 +81,8 @@ __all__ = [
_LOGGER = logging.getLogger(__name__)
RECOMMENDED_MIN_HAOS_VERSION = AwesomeVersion("9.0.dev0")
def _get_manager(hass: HomeAssistant) -> BluetoothManager:
"""Get the bluetooth manager."""
@ -223,6 +235,43 @@ async def async_get_adapter_from_address(
return await _get_manager(hass).async_get_adapter_from_address(address)
@hass_callback
def _async_haos_is_new_enough(hass: HomeAssistant) -> bool:
"""Check if the version of Home Assistant Operating System is new enough."""
# Only warn if a USB adapter is plugged in
if not any(
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.source != SOURCE_IGNORE
):
return True
if (
not hass.components.hassio.is_hassio()
or not (os_info := hass.components.hassio.get_os_info())
or not (haos_version := os_info.get("version"))
or AwesomeVersion(haos_version) >= RECOMMENDED_MIN_HAOS_VERSION
):
return True
return False
@hass_callback
def _async_check_haos(hass: HomeAssistant) -> None:
"""Create or delete an the haos_outdated issue."""
if _async_haos_is_new_enough(hass):
async_delete_issue(hass, DOMAIN, "haos_outdated")
return
async_create_issue(
hass,
DOMAIN,
"haos_outdated",
is_fixable=False,
severity=IssueSeverity.WARNING,
learn_more_url="/config/updates",
translation_key="haos_outdated",
)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the bluetooth integration."""
integration_matcher = IntegrationMatcher(await async_get_bluetooth(hass))
@ -261,6 +310,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
EVENT_HOMEASSISTANT_STOP, hass_callback(lambda event: cancel())
)
# Wait to check until after start to make sure
# that the system info is available.
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STARTED,
hass_callback(lambda event: _async_check_haos(hass)),
)
return True

View File

@ -3,6 +3,7 @@
"name": "Bluetooth",
"documentation": "https://www.home-assistant.io/integrations/bluetooth",
"dependencies": ["usb"],
"after_dependencies": ["hassio"],
"quality_scale": "internal",
"requirements": [
"bleak==0.17.0",

View File

@ -1,4 +1,10 @@
{
"issues": {
"haos_outdated": {
"title": "Update to Home Assistant Operating System 9.0 or later",
"description": "To improve Bluetooth reliability and performance, we highly recommend you update to version 9.0 or later of the Home Assistant Operating System."
}
},
"config": {
"flow_title": "{name}",
"step": {

View File

@ -9,9 +9,6 @@
"bluetooth_confirm": {
"description": "Do you want to setup {name}?"
},
"enable_bluetooth": {
"description": "Do you want to setup Bluetooth?"
},
"multiple_adapters": {
"data": {
"adapter": "Adapter"
@ -29,14 +26,18 @@
}
}
},
"issues": {
"haos_outdated": {
"description": "To improve Bluetooth reliability and performance, we highly recommend you update to version 9.0 or later of the Home Assistant Operating System.",
"title": "Update to Home Assistant Operating System 9.0 or later"
}
},
"options": {
"step": {
"init": {
"data": {
"adapter": "The Bluetooth Adapter to use for scanning",
"passive": "Passive scanning"
},
"description": "Passive listening requires BlueZ 5.63 or later with experimental features enabled."
}
}
}
}

View File

@ -5,6 +5,44 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
@pytest.fixture(name="operating_system_85")
def mock_operating_system_85():
"""Mock running Home Assistant Operating system 8.5."""
with patch("homeassistant.components.hassio.is_hassio", return_value=True), patch(
"homeassistant.components.hassio.get_os_info",
return_value={
"version": "8.5",
"version_latest": "10.0.dev20220912",
"update_available": False,
"board": "odroid-n2",
"boot": "B",
"data_disk": "/dev/mmcblk1p4",
},
), patch("homeassistant.components.hassio.get_info", return_value={}), patch(
"homeassistant.components.hassio.get_host_info", return_value={}
):
yield
@pytest.fixture(name="operating_system_90")
def mock_operating_system_90():
"""Mock running Home Assistant Operating system 9.0."""
with patch("homeassistant.components.hassio.is_hassio", return_value=True), patch(
"homeassistant.components.hassio.get_os_info",
return_value={
"version": "9.0.dev20220912",
"version_latest": "10.0.dev20220912",
"update_available": False,
"board": "odroid-n2",
"boot": "B",
"data_disk": "/dev/mmcblk1p4",
},
), patch("homeassistant.components.hassio.get_info", return_value={}), patch(
"homeassistant.components.hassio.get_host_info", return_value={}
):
yield
@pytest.fixture(name="bluez_dbus_mock")
def bluez_dbus_mock():
"""Fixture that mocks out the bluez dbus calls."""
@ -39,6 +77,23 @@ def windows_adapter():
yield
@pytest.fixture(name="no_adapters")
def no_adapter_fixture(bluez_dbus_mock):
"""Fixture that mocks no adapters on Linux."""
with patch(
"homeassistant.components.bluetooth.platform.system", return_value="Linux"
), patch(
"homeassistant.components.bluetooth.scanner.platform.system",
return_value="Linux",
), patch(
"homeassistant.components.bluetooth.util.platform.system", return_value="Linux"
), patch(
"bluetooth_adapters.get_bluetooth_adapter_details",
return_value={},
):
yield
@pytest.fixture(name="one_adapter")
def one_adapter_fixture(bluez_dbus_mock):
"""Fixture that mocks one adapter on Linux."""

View File

@ -37,6 +37,7 @@ from homeassistant.components.bluetooth.match import (
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.issue_registry import async_get as async_get_issue_registry
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
@ -2680,3 +2681,51 @@ async def test_discover_new_usb_adapters(hass, mock_bleak_scanner_start, one_ada
await hass.async_block_till_done()
assert len(hass.config_entries.flow.async_progress(DOMAIN)) == 1
async def test_issue_outdated_haos(
hass, mock_bleak_scanner_start, one_adapter, operating_system_85
):
"""Test we create an issue on outdated haos."""
entry = MockConfigEntry(
domain=bluetooth.DOMAIN, data={}, unique_id="00:00:00:00:00:01"
)
entry.add_to_hass(hass)
assert await async_setup_component(hass, bluetooth.DOMAIN, {})
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
registry = async_get_issue_registry(hass)
issue = registry.async_get_issue(DOMAIN, "haos_outdated")
assert issue is not None
async def test_issue_outdated_haos_no_adapters(
hass, mock_bleak_scanner_start, no_adapters, operating_system_85
):
"""Test we do not create an issue on outdated haos if there are no adapters."""
assert await async_setup_component(hass, bluetooth.DOMAIN, {})
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
registry = async_get_issue_registry(hass)
issue = registry.async_get_issue(DOMAIN, "haos_outdated")
assert issue is None
async def test_haos_9_or_later(
hass, mock_bleak_scanner_start, one_adapter, operating_system_90
):
"""Test we do not create issues for haos 9.x or later."""
entry = MockConfigEntry(
domain=bluetooth.DOMAIN, data={}, unique_id="00:00:00:00:00:01"
)
entry.add_to_hass(hass)
assert await async_setup_component(hass, bluetooth.DOMAIN, {})
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
registry = async_get_issue_registry(hass)
issue = registry.async_get_issue(DOMAIN, "haos_outdated")
assert issue is None