mirror of
https://github.com/home-assistant/core.git
synced 2026-08-03 20:24:55 +02:00
Require service on fjaraskupan to detect it (#170363)
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
from collections.abc import Callable
|
||||
import logging
|
||||
|
||||
from fjaraskupan import Device
|
||||
from fjaraskupan import UUID_SERVICE, Device
|
||||
|
||||
from homeassistant.components.bluetooth import (
|
||||
BluetoothCallbackMatcher,
|
||||
@@ -37,6 +37,7 @@ PLATFORMS = [
|
||||
]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_UUID = str(UUID_SERVICE).lower()
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: FjaraskupanConfigEntry) -> bool:
|
||||
@@ -44,39 +45,45 @@ async def async_setup_entry(hass: HomeAssistant, entry: FjaraskupanConfigEntry)
|
||||
|
||||
entry.runtime_data = {}
|
||||
|
||||
def detection_callback(
|
||||
service_info: BluetoothServiceInfoBleak, change: BluetoothChange
|
||||
def data_callback(
|
||||
service_info: BluetoothServiceInfoBleak, change_: BluetoothChange
|
||||
) -> None:
|
||||
if change != BluetoothChange.ADVERTISEMENT:
|
||||
if (data := entry.runtime_data.get(service_info.address)) is None:
|
||||
_LOGGER.debug("Ignoring: %s", service_info)
|
||||
return
|
||||
if data := entry.runtime_data.get(service_info.address):
|
||||
_LOGGER.debug("Update: %s", service_info)
|
||||
data.detection_callback(service_info)
|
||||
else:
|
||||
_LOGGER.debug("Detected: %s", service_info)
|
||||
|
||||
device = Device(service_info.device.address)
|
||||
device_info = DeviceInfo(
|
||||
connections={(dr.CONNECTION_BLUETOOTH, service_info.address)},
|
||||
identifiers={(DOMAIN, service_info.address)},
|
||||
manufacturer="Fjäråskupan",
|
||||
name="Fjäråskupan",
|
||||
)
|
||||
_LOGGER.debug("Update: %s", service_info)
|
||||
data.detection_callback(service_info)
|
||||
|
||||
coordinator: FjaraskupanCoordinator = FjaraskupanCoordinator(
|
||||
hass, entry, device, device_info
|
||||
)
|
||||
coordinator.detection_callback(service_info)
|
||||
def detect_callback(
|
||||
service_info: BluetoothServiceInfoBleak, change_: BluetoothChange
|
||||
) -> None:
|
||||
if service_info.address in entry.runtime_data:
|
||||
return
|
||||
|
||||
entry.runtime_data[service_info.address] = coordinator
|
||||
async_dispatcher_send(
|
||||
hass, f"{DISPATCH_DETECTION}.{entry.entry_id}", coordinator
|
||||
)
|
||||
_LOGGER.debug("Detected: %s", service_info)
|
||||
device = Device(service_info.device.address)
|
||||
device_info = DeviceInfo(
|
||||
connections={(dr.CONNECTION_BLUETOOTH, service_info.address)},
|
||||
identifiers={(DOMAIN, service_info.address)},
|
||||
manufacturer="Fjäråskupan",
|
||||
name="Fjäråskupan",
|
||||
)
|
||||
|
||||
coordinator: FjaraskupanCoordinator = FjaraskupanCoordinator(
|
||||
hass, entry, device, device_info
|
||||
)
|
||||
coordinator.detection_callback(service_info)
|
||||
|
||||
entry.runtime_data[service_info.address] = coordinator
|
||||
async_dispatcher_send(
|
||||
hass, f"{DISPATCH_DETECTION}.{entry.entry_id}", coordinator
|
||||
)
|
||||
|
||||
entry.async_on_unload(
|
||||
async_register_callback(
|
||||
hass,
|
||||
detection_callback,
|
||||
data_callback,
|
||||
BluetoothCallbackMatcher(
|
||||
manufacturer_id=20296,
|
||||
manufacturer_data_start=[79, 68, 70, 74, 65, 82],
|
||||
@@ -86,6 +93,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: FjaraskupanConfigEntry)
|
||||
)
|
||||
)
|
||||
|
||||
entry.async_on_unload(
|
||||
async_register_callback(
|
||||
hass,
|
||||
detect_callback,
|
||||
BluetoothCallbackMatcher(
|
||||
service_uuid=_UUID,
|
||||
connectable=False,
|
||||
),
|
||||
BluetoothScanningMode.ACTIVE,
|
||||
)
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Config flow for Fjäråskupan integration."""
|
||||
|
||||
from fjaraskupan import device_filter
|
||||
from fjaraskupan import UUID_SERVICE
|
||||
|
||||
from homeassistant.components.bluetooth import async_discovered_service_info
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -15,7 +15,8 @@ async def _async_has_devices(hass: HomeAssistant) -> bool:
|
||||
service_infos = async_discovered_service_info(hass)
|
||||
|
||||
for service_info in service_infos:
|
||||
if device_filter(service_info.device, service_info.advertisement):
|
||||
uuids = service_info.service_uuids
|
||||
if str(UUID_SERVICE) in uuids:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
"bluetooth": [
|
||||
{
|
||||
"connectable": false,
|
||||
"manufacturer_data_start": [79, 68, 70, 74, 65, 82],
|
||||
"manufacturer_id": 20296
|
||||
"service_uuid": "77a2bd49-1e5a-4961-bba1-21f34fa4bc7b"
|
||||
}
|
||||
],
|
||||
"codeowners": ["@elupus"],
|
||||
|
||||
Generated
+1
-9
@@ -144,15 +144,7 @@ BLUETOOTH: Final[list[dict[str, bool | str | int | list[int]]]] = [
|
||||
{
|
||||
"connectable": False,
|
||||
"domain": "fjaraskupan",
|
||||
"manufacturer_data_start": [
|
||||
79,
|
||||
68,
|
||||
70,
|
||||
74,
|
||||
65,
|
||||
82,
|
||||
],
|
||||
"manufacturer_id": 20296,
|
||||
"service_uuid": "77a2bd49-1e5a-4961-bba1-21f34fa4bc7b",
|
||||
},
|
||||
{
|
||||
"connectable": True,
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
"""Tests for the Fjäråskupan integration."""
|
||||
|
||||
from fjaraskupan import ANNOUNCE_MANUFACTURER, DEVICE_NAME
|
||||
|
||||
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
|
||||
|
||||
from tests.components.bluetooth import generate_advertisement_data, generate_ble_device
|
||||
|
||||
COOKER_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
name="COOKERHOOD_FJAR",
|
||||
name=DEVICE_NAME,
|
||||
address="AA:BB:CC:DD:EE:FF",
|
||||
rssi=-60,
|
||||
manufacturer_data={},
|
||||
service_uuids=[],
|
||||
service_uuids=["77a2bd49-1e5a-4961-bba1-21f34fa4bc7b"],
|
||||
service_data={},
|
||||
source="local",
|
||||
device=generate_ble_device(address="AA:BB:CC:DD:EE:FF", name="COOKERHOOD_FJAR"),
|
||||
@@ -18,3 +20,18 @@ COOKER_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
connectable=True,
|
||||
tx_power=-127,
|
||||
)
|
||||
|
||||
COOKER_SERVICE_INFO_DATA = BluetoothServiceInfoBleak(
|
||||
name=DEVICE_NAME,
|
||||
address="AA:BB:CC:DD:EE:FF",
|
||||
rssi=-60,
|
||||
manufacturer_data={ANNOUNCE_MANUFACTURER: b"ODFJAR\x01\x02\x00\x00\x00\x30\x04"},
|
||||
service_uuids=[],
|
||||
service_data={},
|
||||
source="local",
|
||||
device=generate_ble_device(address="AA:BB:CC:DD:EE:FF", name=DEVICE_NAME),
|
||||
advertisement=generate_advertisement_data(),
|
||||
time=0,
|
||||
connectable=True,
|
||||
tx_power=-127,
|
||||
)
|
||||
|
||||
@@ -10,7 +10,9 @@ from homeassistant.components.fjaraskupan.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from . import COOKER_SERVICE_INFO
|
||||
from . import COOKER_SERVICE_INFO, COOKER_SERVICE_INFO_DATA
|
||||
|
||||
from tests.components.bluetooth import inject_bluetooth_service_info
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_setup_entry", autouse=True)
|
||||
@@ -60,3 +62,36 @@ async def test_scan_no_devices(hass: HomeAssistant) -> None:
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "no_devices_found"
|
||||
|
||||
|
||||
async def test_discovery(hass: HomeAssistant) -> None:
|
||||
"""Test we get the form."""
|
||||
|
||||
inject_bluetooth_service_info(
|
||||
hass,
|
||||
COOKER_SERVICE_INFO,
|
||||
)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
result = next(iter(hass.config_entries.flow.async_progress_by_handler(DOMAIN)))
|
||||
assert result["step_id"] == "confirm"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Fjäråskupan"
|
||||
assert result["data"] == {}
|
||||
|
||||
|
||||
async def test_discovery_ignored_without_service(hass: HomeAssistant) -> None:
|
||||
"""Test we don't start discovery on only manufacturer data since that can be invalid."""
|
||||
|
||||
inject_bluetooth_service_info(
|
||||
hass,
|
||||
COOKER_SERVICE_INFO_DATA,
|
||||
)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
result = next(
|
||||
iter(hass.config_entries.flow.async_progress_by_handler(DOMAIN)), None
|
||||
)
|
||||
assert result is None
|
||||
|
||||
@@ -15,6 +15,17 @@ from tests.components.bluetooth import (
|
||||
)
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
MOCK_SERVICE_INFO_DISCOVERY = BluetoothServiceInfo(
|
||||
address="11:11:11:11:11:11",
|
||||
name=DEVICE_NAME,
|
||||
service_uuids=["77a2bd49-1e5a-4961-bba1-21f34fa4bc7b"],
|
||||
rssi=-60,
|
||||
manufacturer_data={},
|
||||
service_data={},
|
||||
source="local",
|
||||
)
|
||||
|
||||
|
||||
MOCK_SERVICE_INFO = BluetoothServiceInfo(
|
||||
address="11:11:11:11:11:11",
|
||||
name=DEVICE_NAME,
|
||||
@@ -38,6 +49,46 @@ async def test_setup(
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id) is True
|
||||
|
||||
inject_bluetooth_service_info(
|
||||
hass,
|
||||
MOCK_SERVICE_INFO_DISCOVERY,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
device_entry = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, MOCK_SERVICE_INFO_DISCOVERY.address)}
|
||||
)
|
||||
assert device_entry is not None
|
||||
assert device_entry.manufacturer == "Fjäråskupan"
|
||||
assert device_entry.name == "Fjäråskupan"
|
||||
|
||||
state = hass.states.get("fan.fjaraskupan")
|
||||
assert state
|
||||
assert state.state == "off"
|
||||
|
||||
inject_bluetooth_service_info(
|
||||
hass,
|
||||
MOCK_SERVICE_INFO,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("fan.fjaraskupan")
|
||||
assert state
|
||||
assert state.state == "on"
|
||||
|
||||
|
||||
async def test_setup_ignore_device(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test setup does not create a device before we see service."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={},
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id) is True
|
||||
|
||||
inject_bluetooth_service_info(
|
||||
hass,
|
||||
MOCK_SERVICE_INFO,
|
||||
@@ -45,11 +96,9 @@ async def test_setup(
|
||||
|
||||
await hass.async_block_till_done()
|
||||
device_entry = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, MOCK_SERVICE_INFO.address)}
|
||||
identifiers={(DOMAIN, MOCK_SERVICE_INFO_DISCOVERY.address)}
|
||||
)
|
||||
assert device_entry is not None
|
||||
assert device_entry.manufacturer == "Fjäråskupan"
|
||||
assert device_entry.name == "Fjäråskupan"
|
||||
assert device_entry is None
|
||||
|
||||
|
||||
async def test_remove_device(
|
||||
@@ -67,11 +116,11 @@ async def test_remove_device(
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id) is True
|
||||
|
||||
inject_bluetooth_service_info(hass, MOCK_SERVICE_INFO)
|
||||
inject_bluetooth_service_info(hass, MOCK_SERVICE_INFO_DISCOVERY)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
device_entry = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, MOCK_SERVICE_INFO.address)}
|
||||
identifiers={(DOMAIN, MOCK_SERVICE_INFO_DISCOVERY.address)}
|
||||
)
|
||||
assert device_entry
|
||||
|
||||
|
||||
Reference in New Issue
Block a user