mirror of
https://github.com/home-assistant/core.git
synced 2026-04-27 04:46:34 +00:00
Add support for Wave Enhance and Corentium Home 2 in Airthings BLE integration (#153780)
This commit is contained in:
committed by
GitHub
parent
645f32fd65
commit
9640ebb593
@@ -6,7 +6,11 @@ import dataclasses
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from airthings_ble import AirthingsBluetoothDeviceData, AirthingsDevice
|
||||
from airthings_ble import (
|
||||
AirthingsBluetoothDeviceData,
|
||||
AirthingsDevice,
|
||||
UnsupportedDeviceError,
|
||||
)
|
||||
from bleak import BleakError
|
||||
from habluetooth import BluetoothServiceInfoBleak
|
||||
import voluptuous as vol
|
||||
@@ -28,6 +32,7 @@ SERVICE_UUIDS = [
|
||||
"b42e4a8e-ade7-11e4-89d3-123b93f75cba",
|
||||
"b42e1c08-ade7-11e4-89d3-123b93f75cba",
|
||||
"b42e3882-ade7-11e4-89d3-123b93f75cba",
|
||||
"b42e90a2-ade7-11e4-89d3-123b93f75cba",
|
||||
]
|
||||
|
||||
|
||||
@@ -38,6 +43,7 @@ class Discovery:
|
||||
name: str
|
||||
discovery_info: BluetoothServiceInfo
|
||||
device: AirthingsDevice
|
||||
data: AirthingsBluetoothDeviceData
|
||||
|
||||
|
||||
def get_name(device: AirthingsDevice) -> str:
|
||||
@@ -63,8 +69,8 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._discovered_device: Discovery | None = None
|
||||
self._discovered_devices: dict[str, Discovery] = {}
|
||||
|
||||
async def _get_device_data(
|
||||
self, discovery_info: BluetoothServiceInfo
|
||||
async def _get_device(
|
||||
self, data: AirthingsBluetoothDeviceData, discovery_info: BluetoothServiceInfo
|
||||
) -> AirthingsDevice:
|
||||
ble_device = bluetooth.async_ble_device_from_address(
|
||||
self.hass, discovery_info.address
|
||||
@@ -73,10 +79,8 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
_LOGGER.debug("no ble_device in _get_device_data")
|
||||
raise AirthingsDeviceUpdateError("No ble_device")
|
||||
|
||||
airthings = AirthingsBluetoothDeviceData(_LOGGER)
|
||||
|
||||
try:
|
||||
data = await airthings.update_device(ble_device)
|
||||
device = await data.update_device(ble_device)
|
||||
except BleakError as err:
|
||||
_LOGGER.error(
|
||||
"Error connecting to and getting data from %s: %s",
|
||||
@@ -84,12 +88,15 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
err,
|
||||
)
|
||||
raise AirthingsDeviceUpdateError("Failed getting device data") from err
|
||||
except UnsupportedDeviceError:
|
||||
_LOGGER.debug("Skipping unsupported device: %s", discovery_info.name)
|
||||
raise
|
||||
except Exception as err:
|
||||
_LOGGER.error(
|
||||
"Unknown error occurred from %s: %s", discovery_info.address, err
|
||||
)
|
||||
raise
|
||||
return data
|
||||
return device
|
||||
|
||||
async def async_step_bluetooth(
|
||||
self, discovery_info: BluetoothServiceInfo
|
||||
@@ -99,17 +106,21 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
await self.async_set_unique_id(discovery_info.address)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
data = AirthingsBluetoothDeviceData(logger=_LOGGER)
|
||||
|
||||
try:
|
||||
device = await self._get_device_data(discovery_info)
|
||||
device = await self._get_device(data=data, discovery_info=discovery_info)
|
||||
except AirthingsDeviceUpdateError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
except UnsupportedDeviceError:
|
||||
return self.async_abort(reason="unsupported_device")
|
||||
except Exception:
|
||||
_LOGGER.exception("Unknown error occurred")
|
||||
return self.async_abort(reason="unknown")
|
||||
|
||||
name = get_name(device)
|
||||
self.context["title_placeholders"] = {"name": name}
|
||||
self._discovered_device = Discovery(name, discovery_info, device)
|
||||
self._discovered_device = Discovery(name, discovery_info, device, data=data)
|
||||
|
||||
return await self.async_step_bluetooth_confirm()
|
||||
|
||||
@@ -164,16 +175,28 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
if MFCT_ID not in discovery_info.manufacturer_data:
|
||||
continue
|
||||
if not any(uuid in SERVICE_UUIDS for uuid in discovery_info.service_uuids):
|
||||
_LOGGER.debug(
|
||||
"Skipping unsupported device: %s (%s)", discovery_info.name, address
|
||||
)
|
||||
continue
|
||||
devices.append(discovery_info)
|
||||
|
||||
for discovery_info in devices:
|
||||
address = discovery_info.address
|
||||
data = AirthingsBluetoothDeviceData(logger=_LOGGER)
|
||||
try:
|
||||
device = await self._get_device_data(discovery_info)
|
||||
device = await self._get_device(data, discovery_info)
|
||||
except AirthingsDeviceUpdateError:
|
||||
_LOGGER.error(
|
||||
"Error connecting to and getting data from %s",
|
||||
"Error connecting to and getting data from %s (%s)",
|
||||
discovery_info.name,
|
||||
discovery_info.address,
|
||||
)
|
||||
continue
|
||||
except UnsupportedDeviceError:
|
||||
_LOGGER.debug(
|
||||
"Skipping unsupported device: %s (%s)",
|
||||
discovery_info.name,
|
||||
discovery_info.address,
|
||||
)
|
||||
continue
|
||||
@@ -181,7 +204,10 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
_LOGGER.exception("Unknown error occurred")
|
||||
return self.async_abort(reason="unknown")
|
||||
name = get_name(device)
|
||||
self._discovered_devices[address] = Discovery(name, discovery_info, device)
|
||||
_LOGGER.debug("Discovered Airthings device: %s (%s)", name, address)
|
||||
self._discovered_devices[address] = Discovery(
|
||||
name, discovery_info, device, data
|
||||
)
|
||||
|
||||
if not self._discovered_devices:
|
||||
return self.async_abort(reason="no_devices_found")
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
{
|
||||
"manufacturer_id": 820,
|
||||
"service_uuid": "b42e3882-ade7-11e4-89d3-123b93f75cba"
|
||||
},
|
||||
{
|
||||
"manufacturer_id": 820,
|
||||
"service_uuid": "b42e90a2-ade7-11e4-89d3-123b93f75cba"
|
||||
}
|
||||
],
|
||||
"codeowners": ["@vincegio", "@LaStrada"],
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"firmware_upgrade_required": "Your device requires a firmware upgrade. Please use the Airthings app (Android/iOS) to upgrade it.",
|
||||
"unsupported_device": "Unsupported device",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
}
|
||||
},
|
||||
|
||||
5
homeassistant/generated/bluetooth.py
generated
5
homeassistant/generated/bluetooth.py
generated
@@ -48,6 +48,11 @@ BLUETOOTH: Final[list[dict[str, bool | str | int | list[int]]]] = [
|
||||
"manufacturer_id": 820,
|
||||
"service_uuid": "b42e3882-ade7-11e4-89d3-123b93f75cba",
|
||||
},
|
||||
{
|
||||
"domain": "airthings_ble",
|
||||
"manufacturer_id": 820,
|
||||
"service_uuid": "b42e90a2-ade7-11e4-89d3-123b93f75cba",
|
||||
},
|
||||
{
|
||||
"connectable": False,
|
||||
"domain": "aranet",
|
||||
|
||||
@@ -146,6 +146,27 @@ VIEW_PLUS_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
tx_power=0,
|
||||
)
|
||||
|
||||
UNKNOWN_AIRTHINGS_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
name="unknown",
|
||||
address="00:cc:cc:cc:cc:cc",
|
||||
rssi=-61,
|
||||
manufacturer_data={820: b"\xe4/\xa5\xae\t\x00"},
|
||||
service_data={},
|
||||
service_uuids=[],
|
||||
source="local",
|
||||
device=generate_ble_device(
|
||||
"cc:cc:cc:cc:cc:cc",
|
||||
"unknown",
|
||||
),
|
||||
advertisement=generate_advertisement_data(
|
||||
manufacturer_data={},
|
||||
service_uuids=[],
|
||||
),
|
||||
connectable=True,
|
||||
time=0,
|
||||
tx_power=0,
|
||||
)
|
||||
|
||||
UNKNOWN_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
name="unknown",
|
||||
address="00:cc:cc:cc:cc:cc",
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from airthings_ble import AirthingsDevice, AirthingsDeviceType
|
||||
from airthings_ble import AirthingsDevice, AirthingsDeviceType, UnsupportedDeviceError
|
||||
from bleak import BleakError
|
||||
from home_assistant_bluetooth import BluetoothServiceInfoBleak
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.airthings_ble.const import DOMAIN
|
||||
@@ -13,6 +14,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from . import (
|
||||
UNKNOWN_AIRTHINGS_SERVICE_INFO,
|
||||
UNKNOWN_SERVICE_INFO,
|
||||
VIEW_PLUS_SERVICE_INFO,
|
||||
WAVE_DEVICE_INFO,
|
||||
@@ -73,7 +75,12 @@ async def test_bluetooth_discovery_no_BLEDevice(hass: HomeAssistant) -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exc", "reason"), [(Exception(), "unknown"), (BleakError(), "cannot_connect")]
|
||||
("exc", "reason"),
|
||||
[
|
||||
(Exception(), "unknown"),
|
||||
(BleakError(), "cannot_connect"),
|
||||
(UnsupportedDeviceError(), "unsupported_device"),
|
||||
],
|
||||
)
|
||||
async def test_bluetooth_discovery_airthings_ble_update_failed(
|
||||
hass: HomeAssistant, exc: Exception, reason: str
|
||||
@@ -234,22 +241,34 @@ async def test_user_setup_existing_and_unknown_device(hass: HomeAssistant) -> No
|
||||
assert result["reason"] == "no_devices_found"
|
||||
|
||||
|
||||
async def test_user_setup_unknown_error(hass: HomeAssistant) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
("exc", "reason", "service_info"),
|
||||
[
|
||||
(Exception(), "unknown", WAVE_SERVICE_INFO),
|
||||
(UnsupportedDeviceError(), "no_devices_found", UNKNOWN_AIRTHINGS_SERVICE_INFO),
|
||||
],
|
||||
)
|
||||
async def test_user_setup_unknown_error(
|
||||
hass: HomeAssistant,
|
||||
exc: Exception,
|
||||
reason: str,
|
||||
service_info: BluetoothServiceInfoBleak,
|
||||
) -> None:
|
||||
"""Test the user initiated form with an unknown error."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.airthings_ble.config_flow.async_discovered_service_info",
|
||||
return_value=[WAVE_SERVICE_INFO],
|
||||
),
|
||||
patch_async_ble_device_from_address(WAVE_SERVICE_INFO),
|
||||
patch_airthings_ble(None, Exception()),
|
||||
patch_async_ble_device_from_address(service_info),
|
||||
patch_airthings_ble(None, exc),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "unknown"
|
||||
assert result["reason"] == reason
|
||||
|
||||
|
||||
async def test_user_setup_unable_to_connect(hass: HomeAssistant) -> None:
|
||||
@@ -350,3 +369,16 @@ async def test_step_user_firmware_required(hass: HomeAssistant) -> None:
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "firmware_upgrade_required"
|
||||
|
||||
|
||||
async def test_discovering_unsupported_devices(hass: HomeAssistant) -> None:
|
||||
"""Test discovering unsupported devices."""
|
||||
with patch(
|
||||
"homeassistant.components.airthings_ble.config_flow.async_discovered_service_info",
|
||||
return_value=[UNKNOWN_AIRTHINGS_SERVICE_INFO, UNKNOWN_SERVICE_INFO],
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "no_devices_found"
|
||||
|
||||
Reference in New Issue
Block a user