mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Fjäråskupan kitchen fan (#53140)
* Add fjäråskupan fan control * Update tests/components/fjaraskupan/conftest.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/fjaraskupan/config_flow.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/fjaraskupan/config_flow.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Increase manual update to 2 minutes * Address review comments * Switch to discovery flow * Address more review comments Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
6218cd648d
commit
1f4c12195e
@ -317,6 +317,9 @@ omit =
|
|||||||
homeassistant/components/firmata/switch.py
|
homeassistant/components/firmata/switch.py
|
||||||
homeassistant/components/fitbit/*
|
homeassistant/components/fitbit/*
|
||||||
homeassistant/components/fixer/sensor.py
|
homeassistant/components/fixer/sensor.py
|
||||||
|
homeassistant/components/fjaraskupan/__init__.py
|
||||||
|
homeassistant/components/fjaraskupan/const.py
|
||||||
|
homeassistant/components/fjaraskupan/fan.py
|
||||||
homeassistant/components/fleetgo/device_tracker.py
|
homeassistant/components/fleetgo/device_tracker.py
|
||||||
homeassistant/components/flexit/climate.py
|
homeassistant/components/flexit/climate.py
|
||||||
homeassistant/components/flic/binary_sensor.py
|
homeassistant/components/flic/binary_sensor.py
|
||||||
|
@ -163,6 +163,7 @@ homeassistant/components/filter/* @dgomes
|
|||||||
homeassistant/components/fireservicerota/* @cyberjunky
|
homeassistant/components/fireservicerota/* @cyberjunky
|
||||||
homeassistant/components/firmata/* @DaAwesomeP
|
homeassistant/components/firmata/* @DaAwesomeP
|
||||||
homeassistant/components/fixer/* @fabaff
|
homeassistant/components/fixer/* @fabaff
|
||||||
|
homeassistant/components/fjaraskupan/* @elupus
|
||||||
homeassistant/components/flick_electric/* @ZephireNZ
|
homeassistant/components/flick_electric/* @ZephireNZ
|
||||||
homeassistant/components/flipr/* @cnico
|
homeassistant/components/flipr/* @cnico
|
||||||
homeassistant/components/flo/* @dmulcahey
|
homeassistant/components/flo/* @dmulcahey
|
||||||
|
143
homeassistant/components/fjaraskupan/__init__.py
Normal file
143
homeassistant/components/fjaraskupan/__init__.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
"""The Fjäråskupan integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
from bleak import BleakScanner
|
||||||
|
from bleak.backends.device import BLEDevice
|
||||||
|
from bleak.backends.scanner import AdvertisementData
|
||||||
|
from fjaraskupan import Device, State, device_filter
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers.dispatcher import (
|
||||||
|
async_dispatcher_connect,
|
||||||
|
async_dispatcher_send,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
|
from .const import DISPATCH_DETECTION, DOMAIN
|
||||||
|
|
||||||
|
PLATFORMS = ["fan"]
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DeviceState:
|
||||||
|
"""Store state of a device."""
|
||||||
|
|
||||||
|
device: Device
|
||||||
|
coordinator: DataUpdateCoordinator[State]
|
||||||
|
device_info: DeviceInfo
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EntryState:
|
||||||
|
"""Store state of config entry."""
|
||||||
|
|
||||||
|
scanner: BleakScanner
|
||||||
|
devices: dict[str, DeviceState]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up Fjäråskupan from a config entry."""
|
||||||
|
|
||||||
|
scanner = BleakScanner()
|
||||||
|
|
||||||
|
state = EntryState(scanner, {})
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
hass.data[DOMAIN][entry.entry_id] = state
|
||||||
|
|
||||||
|
async def detection_callback(
|
||||||
|
ble_device: BLEDevice, advertisement_data: AdvertisementData
|
||||||
|
) -> None:
|
||||||
|
if not device_filter(ble_device, advertisement_data):
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Detection: %s %s - %s", ble_device.name, ble_device, advertisement_data
|
||||||
|
)
|
||||||
|
|
||||||
|
data = state.devices.get(ble_device.address)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
data.device.detection_callback(ble_device, advertisement_data)
|
||||||
|
data.coordinator.async_set_updated_data(data.device.state)
|
||||||
|
else:
|
||||||
|
|
||||||
|
device = Device(ble_device)
|
||||||
|
device.detection_callback(ble_device, advertisement_data)
|
||||||
|
|
||||||
|
async def async_update_data():
|
||||||
|
"""Handle an explicit update request."""
|
||||||
|
await device.update()
|
||||||
|
return device.state
|
||||||
|
|
||||||
|
coordinator: DataUpdateCoordinator[State] = DataUpdateCoordinator(
|
||||||
|
hass,
|
||||||
|
logger=_LOGGER,
|
||||||
|
name="Fjaraskupan Updater",
|
||||||
|
update_interval=timedelta(seconds=120),
|
||||||
|
update_method=async_update_data,
|
||||||
|
)
|
||||||
|
coordinator.async_set_updated_data(device.state)
|
||||||
|
|
||||||
|
device_info: DeviceInfo = {
|
||||||
|
"identifiers": {(DOMAIN, ble_device.address)},
|
||||||
|
"manufacturer": "Fjäråskupan",
|
||||||
|
"name": "Fjäråskupan",
|
||||||
|
}
|
||||||
|
device_state = DeviceState(device, coordinator, device_info)
|
||||||
|
state.devices[ble_device.address] = device_state
|
||||||
|
async_dispatcher_send(
|
||||||
|
hass, f"{DISPATCH_DETECTION}.{entry.entry_id}", device_state
|
||||||
|
)
|
||||||
|
|
||||||
|
scanner.register_detection_callback(detection_callback)
|
||||||
|
await scanner.start()
|
||||||
|
|
||||||
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_setup_entry_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
constructor: Callable[[DeviceState], list[Entity]],
|
||||||
|
) -> None:
|
||||||
|
"""Set up a platform with added entities."""
|
||||||
|
|
||||||
|
entry_state: EntryState = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
async_add_entities(
|
||||||
|
entity
|
||||||
|
for device_state in entry_state.devices.values()
|
||||||
|
for entity in constructor(device_state)
|
||||||
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _detection(device_state: DeviceState) -> None:
|
||||||
|
async_add_entities(constructor(device_state))
|
||||||
|
|
||||||
|
entry.async_on_unload(
|
||||||
|
async_dispatcher_connect(
|
||||||
|
hass, f"{DISPATCH_DETECTION}.{entry.entry_id}", _detection
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
if unload_ok:
|
||||||
|
entry_state: EntryState = hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
await entry_state.scanner.stop()
|
||||||
|
|
||||||
|
return unload_ok
|
38
homeassistant/components/fjaraskupan/config_flow.py
Normal file
38
homeassistant/components/fjaraskupan/config_flow.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"""Config flow for Fjäråskupan integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
|
from bleak import BleakScanner
|
||||||
|
from bleak.backends.device import BLEDevice
|
||||||
|
from bleak.backends.scanner import AdvertisementData
|
||||||
|
from fjaraskupan import device_filter
|
||||||
|
|
||||||
|
from homeassistant.helpers.config_entry_flow import register_discovery_flow
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
CONST_WAIT_TIME = 5.0
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_has_devices(hass) -> bool:
|
||||||
|
"""Return if there are devices that can be discovered."""
|
||||||
|
|
||||||
|
event = asyncio.Event()
|
||||||
|
|
||||||
|
def detection(device: BLEDevice, advertisement_data: AdvertisementData):
|
||||||
|
if device_filter(device, advertisement_data):
|
||||||
|
event.set()
|
||||||
|
|
||||||
|
async with BleakScanner(detection_callback=detection):
|
||||||
|
try:
|
||||||
|
async with async_timeout.timeout(CONST_WAIT_TIME):
|
||||||
|
await event.wait()
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
register_discovery_flow(DOMAIN, "Fjäråskupan", _async_has_devices)
|
5
homeassistant/components/fjaraskupan/const.py
Normal file
5
homeassistant/components/fjaraskupan/const.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
"""Constants for the Fjäråskupan integration."""
|
||||||
|
|
||||||
|
DOMAIN = "fjaraskupan"
|
||||||
|
|
||||||
|
DISPATCH_DETECTION = f"{DOMAIN}.detection"
|
192
homeassistant/components/fjaraskupan/fan.py
Normal file
192
homeassistant/components/fjaraskupan/fan.py
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
"""Support for Fjäråskupan fans."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from fjaraskupan import (
|
||||||
|
COMMAND_AFTERCOOKINGTIMERAUTO,
|
||||||
|
COMMAND_AFTERCOOKINGTIMERMANUAL,
|
||||||
|
COMMAND_AFTERCOOKINGTIMEROFF,
|
||||||
|
COMMAND_STOP_FAN,
|
||||||
|
Device,
|
||||||
|
State,
|
||||||
|
)
|
||||||
|
|
||||||
|
from homeassistant.components.fan import (
|
||||||
|
SUPPORT_PRESET_MODE,
|
||||||
|
SUPPORT_SET_SPEED,
|
||||||
|
FanEntity,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
CoordinatorEntity,
|
||||||
|
DataUpdateCoordinator,
|
||||||
|
)
|
||||||
|
from homeassistant.util.percentage import (
|
||||||
|
ordered_list_item_to_percentage,
|
||||||
|
percentage_to_ordered_list_item,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import DeviceState, async_setup_entry_platform
|
||||||
|
|
||||||
|
ORDERED_NAMED_FAN_SPEEDS = ["1", "2", "3", "4", "5", "6", "7", "8"]
|
||||||
|
|
||||||
|
PRESET_MODE_NORMAL = "normal"
|
||||||
|
PRESET_MODE_AFTER_COOKING_MANUAL = "after_cooking_manual"
|
||||||
|
PRESET_MODE_AFTER_COOKING_AUTO = "after_cooking_auto"
|
||||||
|
PRESET_MODES = [
|
||||||
|
PRESET_MODE_NORMAL,
|
||||||
|
PRESET_MODE_AFTER_COOKING_AUTO,
|
||||||
|
PRESET_MODE_AFTER_COOKING_MANUAL,
|
||||||
|
]
|
||||||
|
|
||||||
|
PRESET_TO_COMMAND = {
|
||||||
|
PRESET_MODE_AFTER_COOKING_MANUAL: COMMAND_AFTERCOOKINGTIMERMANUAL,
|
||||||
|
PRESET_MODE_AFTER_COOKING_AUTO: COMMAND_AFTERCOOKINGTIMERAUTO,
|
||||||
|
PRESET_MODE_NORMAL: COMMAND_AFTERCOOKINGTIMEROFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up sensors dynamically through discovery."""
|
||||||
|
|
||||||
|
def _constructor(device_state: DeviceState):
|
||||||
|
return [
|
||||||
|
Fan(device_state.coordinator, device_state.device, device_state.device_info)
|
||||||
|
]
|
||||||
|
|
||||||
|
async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor)
|
||||||
|
|
||||||
|
|
||||||
|
class Fan(CoordinatorEntity[State], FanEntity):
|
||||||
|
"""Fan entity."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: DataUpdateCoordinator[State],
|
||||||
|
device: Device,
|
||||||
|
device_info: DeviceInfo,
|
||||||
|
) -> None:
|
||||||
|
"""Init fan entity."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._device = device
|
||||||
|
self._default_on_speed = 25
|
||||||
|
self._attr_name = device_info["name"]
|
||||||
|
self._attr_unique_id = device.address
|
||||||
|
self._attr_device_info = device_info
|
||||||
|
self._percentage = 0
|
||||||
|
self._preset_mode = PRESET_MODE_NORMAL
|
||||||
|
self._update_from_device_data(coordinator.data)
|
||||||
|
|
||||||
|
async def async_set_percentage(self, percentage: int) -> None:
|
||||||
|
"""Set speed."""
|
||||||
|
new_speed = percentage_to_ordered_list_item(
|
||||||
|
ORDERED_NAMED_FAN_SPEEDS, percentage
|
||||||
|
)
|
||||||
|
await self._device.send_fan_speed(int(new_speed))
|
||||||
|
self.coordinator.async_set_updated_data(self._device.state)
|
||||||
|
|
||||||
|
async def async_turn_on(
|
||||||
|
self,
|
||||||
|
speed: str = None,
|
||||||
|
percentage: int = None,
|
||||||
|
preset_mode: str = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
"""Turn on the fan."""
|
||||||
|
|
||||||
|
if preset_mode is None:
|
||||||
|
preset_mode = self._preset_mode
|
||||||
|
|
||||||
|
if percentage is None:
|
||||||
|
percentage = self._default_on_speed
|
||||||
|
|
||||||
|
new_speed = percentage_to_ordered_list_item(
|
||||||
|
ORDERED_NAMED_FAN_SPEEDS, percentage
|
||||||
|
)
|
||||||
|
|
||||||
|
async with self._device:
|
||||||
|
if preset_mode != self._preset_mode:
|
||||||
|
await self._device.send_command(PRESET_TO_COMMAND[preset_mode])
|
||||||
|
|
||||||
|
if preset_mode == PRESET_MODE_NORMAL:
|
||||||
|
await self._device.send_fan_speed(int(new_speed))
|
||||||
|
elif preset_mode == PRESET_MODE_AFTER_COOKING_MANUAL:
|
||||||
|
await self._device.send_after_cooking(int(new_speed))
|
||||||
|
elif preset_mode == PRESET_MODE_AFTER_COOKING_AUTO:
|
||||||
|
await self._device.send_after_cooking(0)
|
||||||
|
|
||||||
|
self.coordinator.async_set_updated_data(self._device.state)
|
||||||
|
|
||||||
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
|
"""Set new preset mode."""
|
||||||
|
await self._device.send_command(PRESET_TO_COMMAND[preset_mode])
|
||||||
|
self.coordinator.async_set_updated_data(self._device.state)
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs) -> None:
|
||||||
|
"""Turn the entity off."""
|
||||||
|
await self._device.send_command(COMMAND_STOP_FAN)
|
||||||
|
self.coordinator.async_set_updated_data(self._device.state)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_count(self) -> int:
|
||||||
|
"""Return the number of speeds the fan supports."""
|
||||||
|
return len(ORDERED_NAMED_FAN_SPEEDS)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def percentage(self) -> int | None:
|
||||||
|
"""Return the current speed."""
|
||||||
|
return self._percentage
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self) -> int:
|
||||||
|
"""Flag supported features."""
|
||||||
|
return SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Return true if fan is on."""
|
||||||
|
return self._percentage != 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self) -> str | None:
|
||||||
|
"""Return the current preset mode."""
|
||||||
|
return self._preset_mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_modes(self) -> list[str] | None:
|
||||||
|
"""Return a list of available preset modes."""
|
||||||
|
return PRESET_MODES
|
||||||
|
|
||||||
|
def _update_from_device_data(self, data: State | None) -> None:
|
||||||
|
"""Handle data update."""
|
||||||
|
if not data:
|
||||||
|
self._percentage = 0
|
||||||
|
return
|
||||||
|
|
||||||
|
if data.fan_speed:
|
||||||
|
self._percentage = ordered_list_item_to_percentage(
|
||||||
|
ORDERED_NAMED_FAN_SPEEDS, str(data.fan_speed)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._percentage = 0
|
||||||
|
|
||||||
|
if data.after_cooking_on:
|
||||||
|
if data.after_cooking_fan_speed:
|
||||||
|
self._preset_mode = PRESET_MODE_AFTER_COOKING_MANUAL
|
||||||
|
else:
|
||||||
|
self._preset_mode = PRESET_MODE_AFTER_COOKING_AUTO
|
||||||
|
else:
|
||||||
|
self._preset_mode = PRESET_MODE_NORMAL
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _handle_coordinator_update(self) -> None:
|
||||||
|
"""Handle data update."""
|
||||||
|
|
||||||
|
self._update_from_device_data(self.coordinator.data)
|
||||||
|
self.async_write_ha_state()
|
13
homeassistant/components/fjaraskupan/manifest.json
Normal file
13
homeassistant/components/fjaraskupan/manifest.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"domain": "fjaraskupan",
|
||||||
|
"name": "Fj\u00e4r\u00e5skupan",
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/fjaraskupan",
|
||||||
|
"requirements": [
|
||||||
|
"fjaraskupan==1.0.0"
|
||||||
|
],
|
||||||
|
"codeowners": [
|
||||||
|
"@elupus"
|
||||||
|
],
|
||||||
|
"iot_class": "local_polling"
|
||||||
|
}
|
13
homeassistant/components/fjaraskupan/strings.json
Normal file
13
homeassistant/components/fjaraskupan/strings.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"confirm": {
|
||||||
|
"description": "Do you want to set up Fjäråskupan?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
||||||
|
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
homeassistant/components/fjaraskupan/translations/en.json
Normal file
13
homeassistant/components/fjaraskupan/translations/en.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"no_devices_found": "No devices found on the network",
|
||||||
|
"single_instance_allowed": "Already configured. Only a single configuration possible."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"confirm": {
|
||||||
|
"description": "Do you want to set up Fjäråskupan?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -77,6 +77,7 @@ FLOWS = [
|
|||||||
"ezviz",
|
"ezviz",
|
||||||
"faa_delays",
|
"faa_delays",
|
||||||
"fireservicerota",
|
"fireservicerota",
|
||||||
|
"fjaraskupan",
|
||||||
"flick_electric",
|
"flick_electric",
|
||||||
"flipr",
|
"flipr",
|
||||||
"flo",
|
"flo",
|
||||||
|
@ -625,6 +625,9 @@ fitbit==0.3.1
|
|||||||
# homeassistant.components.fixer
|
# homeassistant.components.fixer
|
||||||
fixerio==1.0.0a0
|
fixerio==1.0.0a0
|
||||||
|
|
||||||
|
# homeassistant.components.fjaraskupan
|
||||||
|
fjaraskupan==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.flipr
|
# homeassistant.components.flipr
|
||||||
flipr-api==1.4.1
|
flipr-api==1.4.1
|
||||||
|
|
||||||
|
@ -345,6 +345,9 @@ faadelays==0.0.7
|
|||||||
# homeassistant.components.feedreader
|
# homeassistant.components.feedreader
|
||||||
feedparser==6.0.2
|
feedparser==6.0.2
|
||||||
|
|
||||||
|
# homeassistant.components.fjaraskupan
|
||||||
|
fjaraskupan==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.flipr
|
# homeassistant.components.flipr
|
||||||
flipr-api==1.4.1
|
flipr-api==1.4.1
|
||||||
|
|
||||||
|
1
tests/components/fjaraskupan/__init__.py
Normal file
1
tests/components/fjaraskupan/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Fjäråskupan integration."""
|
41
tests/components/fjaraskupan/conftest.py
Normal file
41
tests/components/fjaraskupan/conftest.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
"""Standard fixtures for the Fjäråskupan integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from bleak.backends.device import BLEDevice
|
||||||
|
from bleak.backends.scanner import AdvertisementData, BaseBleakScanner
|
||||||
|
from pytest import fixture
|
||||||
|
|
||||||
|
|
||||||
|
@fixture(name="scanner", autouse=True)
|
||||||
|
def fixture_scanner(hass):
|
||||||
|
"""Fixture for scanner."""
|
||||||
|
|
||||||
|
devices = [BLEDevice("1.1.1.1", "COOKERHOOD_FJAR")]
|
||||||
|
|
||||||
|
class MockScanner(BaseBleakScanner):
|
||||||
|
"""Mock Scanner."""
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
"""Start scanning for devices."""
|
||||||
|
for device in devices:
|
||||||
|
self._callback(device, AdvertisementData())
|
||||||
|
|
||||||
|
async def stop(self):
|
||||||
|
"""Stop scanning for devices."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def discovered_devices(self) -> list[BLEDevice]:
|
||||||
|
"""Return discovered devices."""
|
||||||
|
return devices
|
||||||
|
|
||||||
|
def set_scanning_filter(self, **kwargs):
|
||||||
|
"""Set the scanning filter."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.fjaraskupan.config_flow.BleakScanner", new=MockScanner
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.fjaraskupan.config_flow.CONST_WAIT_TIME", new=0.01
|
||||||
|
):
|
||||||
|
yield devices
|
59
tests/components/fjaraskupan/test_config_flow.py
Normal file
59
tests/components/fjaraskupan/test_config_flow.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
"""Test the Fjäråskupan config flow."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from bleak.backends.device import BLEDevice
|
||||||
|
from pytest import fixture
|
||||||
|
|
||||||
|
from homeassistant import config_entries, setup
|
||||||
|
from homeassistant.components.fjaraskupan.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import (
|
||||||
|
RESULT_TYPE_ABORT,
|
||||||
|
RESULT_TYPE_CREATE_ENTRY,
|
||||||
|
RESULT_TYPE_FORM,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@fixture(name="mock_setup_entry", autouse=True)
|
||||||
|
async def fixture_mock_setup_entry(hass):
|
||||||
|
"""Fixture for config entry."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.fjaraskupan.async_setup_entry", return_value=True
|
||||||
|
) as mock_setup_entry:
|
||||||
|
yield mock_setup_entry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_configure(hass: HomeAssistant, mock_setup_entry) -> None:
|
||||||
|
"""Test we get the form."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_FORM
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["title"] == "Fjäråskupan"
|
||||||
|
assert result["data"] == {}
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_scan_no_devices(hass: HomeAssistant, scanner: list[BLEDevice]) -> None:
|
||||||
|
"""Test we get the form."""
|
||||||
|
scanner.clear()
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_FORM
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == "no_devices_found"
|
Loading…
x
Reference in New Issue
Block a user