mirror of
https://github.com/home-assistant/core.git
synced 2026-05-24 01:35:22 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 296d625121 |
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671",
|
||||
"integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import axis
|
||||
from axis.errors import Unauthorized
|
||||
from axis.models.mqtt import ClientState, mqtt_json_to_event
|
||||
from axis.interfaces.mqtt import mqtt_json_to_event
|
||||
from axis.models.mqtt import ClientState
|
||||
from axis.stream_manager import Signal, State
|
||||
|
||||
from homeassistant.components import mqtt
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["axis"],
|
||||
"requirements": ["axis==72"],
|
||||
"requirements": ["axis==71"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "AXIS"
|
||||
|
||||
@@ -11,7 +11,6 @@ from bluetooth_adapters import (
|
||||
ADAPTER_CONNECTION_SLOTS,
|
||||
ADAPTER_HW_VERSION,
|
||||
ADAPTER_MANUFACTURER,
|
||||
ADAPTER_PASSIVE_SCAN,
|
||||
ADAPTER_SW_VERSION,
|
||||
DEFAULT_ADDRESS,
|
||||
DEFAULT_CONNECTION_SLOTS,
|
||||
@@ -80,6 +79,7 @@ from .const import (
|
||||
BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS,
|
||||
CONF_ADAPTER,
|
||||
CONF_DETAILS,
|
||||
CONF_PASSIVE,
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID,
|
||||
CONF_SOURCE_DEVICE_ID,
|
||||
CONF_SOURCE_DOMAIN,
|
||||
@@ -93,7 +93,7 @@ from .manager import HomeAssistantBluetoothManager
|
||||
from .match import BluetoothCallbackMatcher, IntegrationMatcher
|
||||
from .models import BluetoothCallback, BluetoothChange
|
||||
from .storage import BluetoothStorage
|
||||
from .util import adapter_title, resolve_scanning_mode
|
||||
from .util import adapter_title
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
@@ -387,15 +387,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
raise ConfigEntryNotReady(
|
||||
f"Bluetooth adapter {adapter} with address {address} not found"
|
||||
)
|
||||
passive = entry.options.get(CONF_PASSIVE)
|
||||
adapters = await manager.async_get_bluetooth_adapters()
|
||||
details = adapters[adapter]
|
||||
mode = resolve_scanning_mode(entry.options)
|
||||
# AUTO needs passive scanning support to flip on demand; without it
|
||||
# the scanner would start passive on hardware that can't do passive.
|
||||
if mode is BluetoothScanningMode.AUTO and not details.get(ADAPTER_PASSIVE_SCAN):
|
||||
mode = BluetoothScanningMode.ACTIVE
|
||||
mode = BluetoothScanningMode.PASSIVE if passive else BluetoothScanningMode.ACTIVE
|
||||
scanner = HaScanner(mode, adapter, address)
|
||||
scanner.async_setup()
|
||||
details = adapters[adapter]
|
||||
if entry.title == address:
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, title=adapter_title(adapter, details)
|
||||
|
||||
@@ -130,26 +130,17 @@ def async_register_callback(
|
||||
callback: BluetoothCallback,
|
||||
match_dict: BluetoothCallbackMatcher | None,
|
||||
mode: BluetoothScanningMode,
|
||||
*,
|
||||
scan_interval: float | None = None,
|
||||
scan_duration: float | None = None,
|
||||
) -> Callable[[], None]:
|
||||
"""Register to receive a callback on bluetooth change.
|
||||
|
||||
When ``mode`` is not PASSIVE and ``match_dict["address"]`` is set,
|
||||
the address is registered with habluetooth's active-scan scheduler
|
||||
so AUTO-mode scanners flip ACTIVE on demand for that device.
|
||||
``scan_interval`` / ``scan_duration`` default to habluetooth's
|
||||
DEFAULT_ACTIVE_SCAN_* (5 minutes / 10 seconds) when not provided;
|
||||
integrations that need a different cadence can pass explicit
|
||||
values. Without an address in the matcher the active-scan request
|
||||
is skipped; the callback itself still fires normally.
|
||||
mode is currently not used as we only support active scanning.
|
||||
Passive scanning will be available in the future. The flag
|
||||
is required to be present to avoid a future breaking change
|
||||
when we support passive scanning.
|
||||
|
||||
Returns a callback that can be used to cancel the registration.
|
||||
"""
|
||||
return _get_manager(hass).async_register_callback(
|
||||
callback, match_dict, mode, scan_interval, scan_duration
|
||||
)
|
||||
return _get_manager(hass).async_register_callback(callback, match_dict)
|
||||
|
||||
|
||||
async def async_process_advertisements(
|
||||
@@ -170,7 +161,7 @@ async def async_process_advertisements(
|
||||
done.set_result(service_info)
|
||||
|
||||
unload = _get_manager(hass).async_register_callback(
|
||||
_async_discovered_device, match_dict, mode, scan_duration=timeout
|
||||
_async_discovered_device, match_dict
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -12,7 +12,7 @@ from bluetooth_adapters import (
|
||||
adapter_model,
|
||||
get_adapters,
|
||||
)
|
||||
from habluetooth import BluetoothScanningMode, get_manager
|
||||
from habluetooth import get_manager
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import onboarding
|
||||
@@ -24,21 +24,14 @@ from homeassistant.config_entries import (
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.schema_config_entry_flow import (
|
||||
SchemaCommonFlowHandler,
|
||||
SchemaFlowFormStep,
|
||||
SchemaOptionsFlowHandler,
|
||||
)
|
||||
from homeassistant.helpers.selector import (
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
)
|
||||
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||
|
||||
from .const import (
|
||||
CONF_ADAPTER,
|
||||
CONF_DETAILS,
|
||||
CONF_MODE,
|
||||
CONF_PASSIVE,
|
||||
CONF_SOURCE,
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID,
|
||||
@@ -47,39 +40,15 @@ from .const import (
|
||||
CONF_SOURCE_MODEL,
|
||||
DOMAIN,
|
||||
)
|
||||
from .util import adapter_title, resolve_scanning_mode
|
||||
from .util import adapter_title
|
||||
|
||||
_MODE_SELECTOR = SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=[
|
||||
BluetoothScanningMode.AUTO.value,
|
||||
BluetoothScanningMode.ACTIVE.value,
|
||||
BluetoothScanningMode.PASSIVE.value,
|
||||
],
|
||||
translation_key="mode",
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
OPTIONS_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PASSIVE, default=False): bool,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def _options_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
|
||||
"""Build the options schema with the saved mode as the default."""
|
||||
current = resolve_scanning_mode(handler.options).value
|
||||
return vol.Schema({vol.Required(CONF_MODE, default=current): _MODE_SELECTOR})
|
||||
|
||||
|
||||
async def _validate_options(
|
||||
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Mirror CONF_MODE into the legacy CONF_PASSIVE for downgrade safety."""
|
||||
user_input[CONF_PASSIVE] = (
|
||||
user_input[CONF_MODE] == BluetoothScanningMode.PASSIVE.value
|
||||
)
|
||||
return user_input
|
||||
|
||||
|
||||
OPTIONS_FLOW = {
|
||||
"init": SchemaFlowFormStep(_options_schema, validate_user_input=_validate_options),
|
||||
"init": SchemaFlowFormStep(OPTIONS_SCHEMA),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,21 +7,14 @@ from habluetooth import ( # noqa: F401
|
||||
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
|
||||
SCANNER_WATCHDOG_INTERVAL,
|
||||
SCANNER_WATCHDOG_TIMEOUT,
|
||||
BluetoothScanningMode,
|
||||
)
|
||||
|
||||
from homeassistant.const import CONF_MODE # noqa: F401
|
||||
|
||||
DOMAIN = "bluetooth"
|
||||
|
||||
CONF_ADAPTER = "adapter"
|
||||
CONF_DETAILS = "details"
|
||||
# CONF_PASSIVE is the legacy boolean option; we keep writing it alongside
|
||||
# CONF_MODE so a downgrade to a pre-AUTO release reads a sensible value.
|
||||
CONF_PASSIVE = "passive"
|
||||
|
||||
DEFAULT_MODE = BluetoothScanningMode.AUTO.value
|
||||
|
||||
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
CONF_SOURCE: Final = "source"
|
||||
|
||||
@@ -202,9 +202,6 @@ class HomeAssistantBluetoothManager(BluetoothManager):
|
||||
self,
|
||||
callback: BluetoothCallback,
|
||||
matcher: BluetoothCallbackMatcher | None,
|
||||
mode: BluetoothScanningMode = BluetoothScanningMode.ACTIVE,
|
||||
scan_interval: float | None = None,
|
||||
scan_duration: float | None = None,
|
||||
) -> Callable[[], None]:
|
||||
"""Register a callback."""
|
||||
callback_matcher = BluetoothCallbackMatcherWithCallback(callback=callback)
|
||||
@@ -219,31 +216,15 @@ class HomeAssistantBluetoothManager(BluetoothManager):
|
||||
connectable = callback_matcher[CONNECTABLE]
|
||||
self._callback_index.add_callback_matcher(callback_matcher)
|
||||
|
||||
# If the matcher targets a specific address and the caller
|
||||
# didn't explicitly ask for PASSIVE, wire it into habluetooth's
|
||||
# active-scan scheduler so AUTO-mode scanners flip ACTIVE on
|
||||
# demand for this device. ``scan_interval``/``scan_duration``
|
||||
# default to habluetooth's DEFAULT_ACTIVE_SCAN_* when None.
|
||||
cancel_active_scan: Callable[[], None] | None = None
|
||||
if (
|
||||
mode is not BluetoothScanningMode.PASSIVE
|
||||
and (address := callback_matcher.get(ADDRESS)) is not None
|
||||
):
|
||||
cancel_active_scan = self.async_register_active_scan(
|
||||
address, scan_interval, scan_duration
|
||||
)
|
||||
|
||||
def _async_remove_callback() -> None:
|
||||
self._callback_index.remove_callback_matcher(callback_matcher)
|
||||
if cancel_active_scan is not None:
|
||||
cancel_active_scan()
|
||||
|
||||
# If we have history for the subscriber, we can trigger the callback
|
||||
# immediately with the last packet so the subscriber can see the
|
||||
# device.
|
||||
history = self._connectable_history if connectable else self._all_history
|
||||
service_infos: Iterable[BluetoothServiceInfoBleak] = []
|
||||
if (address := callback_matcher.get(ADDRESS)) is not None:
|
||||
if address := callback_matcher.get(ADDRESS):
|
||||
if service_info := history.get(address):
|
||||
service_infos = [service_info]
|
||||
else:
|
||||
|
||||
@@ -21,6 +21,6 @@
|
||||
"bluetooth-auto-recovery==1.5.3",
|
||||
"bluetooth-data-tools==1.29.11",
|
||||
"dbus-fast==5.0.3",
|
||||
"habluetooth==6.7.0"
|
||||
"habluetooth==6.4.0"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -48,21 +48,9 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"mode": "Scanning mode"
|
||||
},
|
||||
"data_description": {
|
||||
"mode": "Auto is recommended for most setups. It saves battery on your Bluetooth devices while still catching new devices and updates quickly."
|
||||
"passive": "Passive scanning"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"mode": {
|
||||
"options": {
|
||||
"active": "Active (uses more device battery, fastest updates)",
|
||||
"auto": "Auto (recommended, saves device battery)",
|
||||
"passive": "Passive (lowest device battery use, some details may be missing)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
"""The bluetooth integration utilities."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from bluetooth_adapters import (
|
||||
ADAPTER_ADDRESS,
|
||||
ADAPTER_MANUFACTURER,
|
||||
@@ -13,32 +9,14 @@ from bluetooth_adapters import (
|
||||
adapter_unique_name,
|
||||
)
|
||||
from bluetooth_data_tools import monotonic_time_coarse
|
||||
from habluetooth import BluetoothScanningMode, get_manager
|
||||
from habluetooth import get_manager
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .const import CONF_MODE, CONF_PASSIVE, DEFAULT_MODE
|
||||
from .models import BluetoothServiceInfoBleak
|
||||
from .storage import BluetoothStorage
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def resolve_scanning_mode(options: Mapping[str, Any]) -> BluetoothScanningMode:
|
||||
"""Resolve CONF_MODE, falling back to legacy CONF_PASSIVE or DEFAULT_MODE."""
|
||||
if (mode_value := options.get(CONF_MODE)) is not None:
|
||||
try:
|
||||
return BluetoothScanningMode(mode_value)
|
||||
except TypeError, ValueError:
|
||||
_LOGGER.warning("Unknown bluetooth scanning mode %r", mode_value)
|
||||
return BluetoothScanningMode(DEFAULT_MODE)
|
||||
if (legacy_passive := options.get(CONF_PASSIVE)) is True:
|
||||
return BluetoothScanningMode.PASSIVE
|
||||
if legacy_passive is False:
|
||||
return BluetoothScanningMode.ACTIVE
|
||||
return BluetoothScanningMode(DEFAULT_MODE)
|
||||
|
||||
|
||||
class InvalidConfigEntryID(HomeAssistantError):
|
||||
"""Invalid config entry id."""
|
||||
|
||||
@@ -4,10 +4,10 @@ from functools import partial
|
||||
|
||||
from aioesphomeapi import (
|
||||
AlarmControlPanelCommand,
|
||||
AlarmControlPanelEntityFeature as ESPHomeAlarmControlPanelEntityFeature,
|
||||
AlarmControlPanelEntityState as ESPHomeAlarmControlPanelEntityState,
|
||||
AlarmControlPanelInfo,
|
||||
AlarmControlPanelState as ESPHomeAlarmControlPanelState,
|
||||
APIIntEnum,
|
||||
EntityInfo,
|
||||
)
|
||||
|
||||
@@ -50,28 +50,16 @@ _ESPHOME_ACP_STATE_TO_HASS_STATE: EsphomeEnumMapper[
|
||||
}
|
||||
)
|
||||
|
||||
_FEATURES: dict[
|
||||
ESPHomeAlarmControlPanelEntityFeature, AlarmControlPanelEntityFeature
|
||||
] = {
|
||||
ESPHomeAlarmControlPanelEntityFeature.ARM_HOME: (
|
||||
AlarmControlPanelEntityFeature.ARM_HOME
|
||||
),
|
||||
ESPHomeAlarmControlPanelEntityFeature.ARM_AWAY: (
|
||||
AlarmControlPanelEntityFeature.ARM_AWAY
|
||||
),
|
||||
ESPHomeAlarmControlPanelEntityFeature.ARM_NIGHT: (
|
||||
AlarmControlPanelEntityFeature.ARM_NIGHT
|
||||
),
|
||||
ESPHomeAlarmControlPanelEntityFeature.TRIGGER: (
|
||||
AlarmControlPanelEntityFeature.TRIGGER
|
||||
),
|
||||
ESPHomeAlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS: (
|
||||
AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
|
||||
),
|
||||
ESPHomeAlarmControlPanelEntityFeature.ARM_VACATION: (
|
||||
AlarmControlPanelEntityFeature.ARM_VACATION
|
||||
),
|
||||
}
|
||||
|
||||
class EspHomeACPFeatures(APIIntEnum):
|
||||
"""ESPHome AlarmControlPanel feature numbers."""
|
||||
|
||||
ARM_HOME = 1
|
||||
ARM_AWAY = 2
|
||||
ARM_NIGHT = 4
|
||||
TRIGGER = 8
|
||||
ARM_CUSTOM_BYPASS = 16
|
||||
ARM_VACATION = 32
|
||||
|
||||
|
||||
class EsphomeAlarmControlPanel(
|
||||
@@ -85,14 +73,20 @@ class EsphomeAlarmControlPanel(
|
||||
"""Set attrs from static info."""
|
||||
super()._on_static_info_update(static_info)
|
||||
static_info = self._static_info
|
||||
esp_flags = ESPHomeAlarmControlPanelEntityFeature(
|
||||
static_info.supported_features
|
||||
)
|
||||
flags = AlarmControlPanelEntityFeature(0)
|
||||
for esp_flag in esp_flags:
|
||||
if (flag := _FEATURES.get(esp_flag)) is not None:
|
||||
flags |= flag
|
||||
self._attr_supported_features = flags
|
||||
feature = 0
|
||||
if static_info.supported_features & EspHomeACPFeatures.ARM_HOME:
|
||||
feature |= AlarmControlPanelEntityFeature.ARM_HOME
|
||||
if static_info.supported_features & EspHomeACPFeatures.ARM_AWAY:
|
||||
feature |= AlarmControlPanelEntityFeature.ARM_AWAY
|
||||
if static_info.supported_features & EspHomeACPFeatures.ARM_NIGHT:
|
||||
feature |= AlarmControlPanelEntityFeature.ARM_NIGHT
|
||||
if static_info.supported_features & EspHomeACPFeatures.TRIGGER:
|
||||
feature |= AlarmControlPanelEntityFeature.TRIGGER
|
||||
if static_info.supported_features & EspHomeACPFeatures.ARM_CUSTOM_BYPASS:
|
||||
feature |= AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
|
||||
if static_info.supported_features & EspHomeACPFeatures.ARM_VACATION:
|
||||
feature |= AlarmControlPanelEntityFeature.ARM_VACATION
|
||||
self._attr_supported_features = AlarmControlPanelEntityFeature(feature)
|
||||
self._attr_code_format = (
|
||||
CodeFormat.NUMBER if static_info.requires_code else None
|
||||
)
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
"mqtt": ["esphome/discover/#"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": [
|
||||
"aioesphomeapi==45.2.0",
|
||||
"aioesphomeapi==45.1.0",
|
||||
"esphome-dashboard-api==1.3.0",
|
||||
"bleak-esphome==3.9.1"
|
||||
"bleak-esphome==3.8.1"
|
||||
],
|
||||
"zeroconf": ["_esphomelib._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -53,8 +53,6 @@ class FritzDeviceBase(CoordinatorEntity[AvmWrapper]):
|
||||
class FritzBoxBaseEntity:
|
||||
"""Fritz host entity base class."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, avm_wrapper: AvmWrapper, device_name: str) -> None:
|
||||
"""Init device info class."""
|
||||
self._avm_wrapper = avm_wrapper
|
||||
|
||||
@@ -76,6 +76,7 @@ class FritzGuestWifiQRImage(FritzBoxBaseEntity, ImageEntity):
|
||||
|
||||
_attr_content_type = "image/png"
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = True
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -170,6 +170,7 @@ class SwitchInfo(TypedDict):
|
||||
"""FRITZ!Box switch info class."""
|
||||
|
||||
description: str
|
||||
friendly_name: str
|
||||
icon: str
|
||||
type: str
|
||||
callback_update: Callable
|
||||
|
||||
@@ -380,18 +380,44 @@ class FritzBoxBaseSwitch(FritzBoxBaseEntity, SwitchEntity):
|
||||
"""Init Fritzbox base switch."""
|
||||
super().__init__(avm_wrapper, device_friendly_name)
|
||||
|
||||
description = switch_info["description"]
|
||||
|
||||
self._description = switch_info["description"]
|
||||
self._friendly_name = switch_info["friendly_name"]
|
||||
self._icon = switch_info["icon"]
|
||||
self._type = switch_info["type"]
|
||||
self._update = switch_info["callback_update"]
|
||||
self._switch = switch_info["callback_switch"]
|
||||
|
||||
self._attr_icon = switch_info["icon"]
|
||||
self._attr_is_on = switch_info["init_state"]
|
||||
self._attr_name = description
|
||||
self._attr_unique_id = f"{self._avm_wrapper.unique_id}-{slugify(description)}"
|
||||
self._attr_extra_state_attributes: dict[str, Any | None] = {}
|
||||
self._attr_available = True
|
||||
|
||||
self._name = f"{self._friendly_name} {self._description}"
|
||||
self._unique_id = f"{self._avm_wrapper.unique_id}-{slugify(self._description)}"
|
||||
|
||||
self._attributes: dict[str, str | None] = {}
|
||||
self._is_available = True
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
"""Return icon."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return unique id."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return availability."""
|
||||
return self._is_available
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, str | None]:
|
||||
"""Return device attributes."""
|
||||
return self._attributes
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update data."""
|
||||
@@ -412,6 +438,7 @@ class FritzBoxBaseSwitch(FritzBoxBaseEntity, SwitchEntity):
|
||||
self._attr_is_on = turn_on
|
||||
|
||||
|
||||
# pylint: disable-next=home-assistant-missing-has-entity-name
|
||||
class FritzBoxPortSwitch(FritzBoxBaseSwitch):
|
||||
"""Defines a FRITZ!Box Tools PortForward switch."""
|
||||
|
||||
@@ -425,6 +452,9 @@ class FritzBoxPortSwitch(FritzBoxBaseSwitch):
|
||||
connection_type: str,
|
||||
) -> None:
|
||||
"""Init Fritzbox port switch."""
|
||||
self._avm_wrapper = avm_wrapper
|
||||
|
||||
self._attributes = {}
|
||||
self.connection_type = connection_type
|
||||
# dict in the format as it comes from fritzconnection,
|
||||
# eg: {"NewRemoteHost": "0.0.0.0", "NewExternalPort": 22, ...}
|
||||
@@ -434,6 +464,7 @@ class FritzBoxPortSwitch(FritzBoxBaseSwitch):
|
||||
|
||||
switch_info = SwitchInfo(
|
||||
description=f"Port forward {port_name}",
|
||||
friendly_name=device_friendly_name,
|
||||
icon="mdi:check-network",
|
||||
type=SWITCH_TYPE_PORTFORWARD,
|
||||
callback_update=self._async_fetch_update,
|
||||
@@ -452,11 +483,11 @@ class FritzBoxPortSwitch(FritzBoxBaseSwitch):
|
||||
"Specific %s response: %s", SWITCH_TYPE_PORTFORWARD, self.port_mapping
|
||||
)
|
||||
if not self.port_mapping:
|
||||
self._attr_available = False
|
||||
self._is_available = False
|
||||
return
|
||||
|
||||
self._attr_is_on = self.port_mapping["NewEnabled"] is True
|
||||
self._attr_available = True
|
||||
self._is_available = True
|
||||
|
||||
attributes_dict = {
|
||||
"NewInternalClient": "internal_ip",
|
||||
@@ -467,7 +498,7 @@ class FritzBoxPortSwitch(FritzBoxBaseSwitch):
|
||||
}
|
||||
|
||||
for key, attr in attributes_dict.items():
|
||||
self._attr_extra_state_attributes[attr] = self.port_mapping[key]
|
||||
self._attributes[attr] = self.port_mapping[key]
|
||||
|
||||
async def _async_switch_on_off_executor(self, turn_on: bool) -> None:
|
||||
self.port_mapping["NewEnabled"] = "1" if turn_on else "0"
|
||||
@@ -574,6 +605,7 @@ class FritzBoxProfileSwitch(FritzBoxBaseCoordinatorSwitch):
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
# pylint: disable-next=home-assistant-missing-has-entity-name
|
||||
class FritzBoxWifiSwitch(FritzBoxBaseSwitch):
|
||||
"""Defines a FRITZ!Box Tools Wifi switch."""
|
||||
|
||||
@@ -585,8 +617,10 @@ class FritzBoxWifiSwitch(FritzBoxBaseSwitch):
|
||||
network_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Init Fritz Wifi switch."""
|
||||
self._avm_wrapper = avm_wrapper
|
||||
self._wifi_info = network_data
|
||||
|
||||
self._attributes = {}
|
||||
self._attr_entity_category = EntityCategory.CONFIG
|
||||
self._attr_entity_registry_enabled_default = (
|
||||
avm_wrapper.mesh_role is not MeshRoles.SLAVE
|
||||
@@ -598,13 +632,14 @@ class FritzBoxWifiSwitch(FritzBoxBaseSwitch):
|
||||
|
||||
switch_info = SwitchInfo(
|
||||
description=description,
|
||||
friendly_name=device_friendly_name,
|
||||
icon="mdi:wifi",
|
||||
type=SWITCH_TYPE_WIFINETWORK,
|
||||
callback_update=self._async_fetch_update,
|
||||
callback_switch=self._async_switch_on_off_executor,
|
||||
init_state=network_data["NewEnable"],
|
||||
)
|
||||
super().__init__(avm_wrapper, device_friendly_name, switch_info)
|
||||
super().__init__(self._avm_wrapper, device_friendly_name, switch_info)
|
||||
|
||||
async def _async_fetch_update(self) -> None:
|
||||
"""Fetch updates."""
|
||||
@@ -617,16 +652,16 @@ class FritzBoxWifiSwitch(FritzBoxBaseSwitch):
|
||||
)
|
||||
|
||||
if not wifi_info:
|
||||
self._attr_available = False
|
||||
self._is_available = False
|
||||
return
|
||||
|
||||
self._attr_is_on = wifi_info["NewEnable"] is True
|
||||
self._attr_available = True
|
||||
self._is_available = True
|
||||
|
||||
std = wifi_info["NewStandard"]
|
||||
self._attr_extra_state_attributes["standard"] = std or None
|
||||
self._attr_extra_state_attributes["bssid"] = wifi_info["NewBSSID"]
|
||||
self._attr_extra_state_attributes["mac_address_control"] = wifi_info[
|
||||
self._attributes["standard"] = std or None
|
||||
self._attributes["bssid"] = wifi_info["NewBSSID"]
|
||||
self._attributes["mac_address_control"] = wifi_info[
|
||||
"NewMACAddressControlEnabled"
|
||||
]
|
||||
self._wifi_info = wifi_info
|
||||
|
||||
@@ -44,13 +44,13 @@ rules:
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: done
|
||||
discovery-update-info: done
|
||||
discovery: done
|
||||
diagnostics: todo
|
||||
discovery-update-info: todo
|
||||
discovery: todo
|
||||
docs-data-update: todo
|
||||
docs-examples: todo
|
||||
docs-known-limitations: done
|
||||
docs-supported-devices: done
|
||||
docs-known-limitations: todo
|
||||
docs-supported-devices: todo
|
||||
docs-supported-functions: todo
|
||||
docs-troubleshooting: done
|
||||
docs-use-cases: todo
|
||||
@@ -62,11 +62,9 @@ rules:
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: done
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: |
|
||||
The integration currently does not have any known issues.
|
||||
repair-issues: todo
|
||||
stale-devices: done
|
||||
|
||||
# Platinum
|
||||
async-dependency: todo
|
||||
inject-websession: todo
|
||||
|
||||
@@ -35,7 +35,7 @@ file-read-backwards==2.0.0
|
||||
fnv-hash-fast==2.0.2
|
||||
go2rtc-client==0.4.0
|
||||
ha-ffmpeg==3.2.2
|
||||
habluetooth==6.7.0
|
||||
habluetooth==6.4.0
|
||||
hass-nabucasa==2.2.0
|
||||
hassil==3.5.0
|
||||
home-assistant-bluetooth==2.0.0
|
||||
|
||||
Generated
+4
-4
@@ -254,7 +254,7 @@ aioelectricitymaps==1.1.1
|
||||
aioemonitor==1.0.5
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==45.2.0
|
||||
aioesphomeapi==45.1.0
|
||||
|
||||
# homeassistant.components.matrix
|
||||
# homeassistant.components.slack
|
||||
@@ -603,7 +603,7 @@ avea==1.8.0
|
||||
# avion==0.10
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==72
|
||||
axis==71
|
||||
|
||||
# homeassistant.components.fujitsu_fglair
|
||||
ayla-iot-unofficial==1.4.7
|
||||
@@ -648,7 +648,7 @@ beautifulsoup4==4.13.3
|
||||
bizkaibus==0.1.1
|
||||
|
||||
# homeassistant.components.esphome
|
||||
bleak-esphome==3.9.1
|
||||
bleak-esphome==3.8.1
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bleak-retry-connector==4.6.0
|
||||
@@ -1210,7 +1210,7 @@ ha-xthings-cloud==1.0.5
|
||||
habiticalib==0.4.7
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
habluetooth==6.7.0
|
||||
habluetooth==6.4.0
|
||||
|
||||
# homeassistant.components.hanna
|
||||
hanna-cloud==0.0.7
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Test the bluetooth config flow."""
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
from bluetooth_adapters import DEFAULT_ADDRESS, AdapterDetails
|
||||
@@ -11,7 +10,6 @@ from homeassistant.components.bluetooth import HaBluetoothConnector
|
||||
from homeassistant.components.bluetooth.const import (
|
||||
CONF_ADAPTER,
|
||||
CONF_DETAILS,
|
||||
CONF_MODE,
|
||||
CONF_PASSIVE,
|
||||
CONF_SOURCE,
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID,
|
||||
@@ -349,11 +347,10 @@ async def test_async_step_integration_discovery_already_exists(
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ["auto", "active", "passive"])
|
||||
@pytest.mark.usefixtures(
|
||||
"one_adapter", "mock_bleak_scanner_start", "mock_bluetooth_adapters"
|
||||
)
|
||||
async def test_options_flow_linux(hass: HomeAssistant, mode: str) -> None:
|
||||
async def test_options_flow_linux(hass: HomeAssistant) -> None:
|
||||
"""Test options on Linux."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
@@ -373,50 +370,32 @@ async def test_options_flow_linux(hass: HomeAssistant, mode: str) -> None:
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_MODE: mode},
|
||||
user_input={
|
||||
CONF_PASSIVE: True,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"][CONF_MODE] == mode
|
||||
assert result["data"][CONF_PASSIVE] is (mode == "passive")
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert result["data"][CONF_PASSIVE] is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("options", "expected_default"),
|
||||
[
|
||||
({}, "auto"),
|
||||
({CONF_PASSIVE: True}, "passive"),
|
||||
({CONF_PASSIVE: False}, "active"),
|
||||
({CONF_MODE: "passive"}, "passive"),
|
||||
],
|
||||
ids=["fresh", "legacy_passive_true", "legacy_passive_false", "explicit_mode"],
|
||||
)
|
||||
@pytest.mark.usefixtures(
|
||||
"one_adapter", "mock_bleak_scanner_start", "mock_bluetooth_adapters"
|
||||
)
|
||||
async def test_options_flow_default_reflects_existing_options(
|
||||
hass: HomeAssistant, options: dict[str, Any], expected_default: str
|
||||
) -> None:
|
||||
"""Options form preselects the current mode, including legacy CONF_PASSIVE."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={},
|
||||
options=options,
|
||||
unique_id="00:00:00:00:00:01",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
# Verify we can change it to False
|
||||
|
||||
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
schema = result["data_schema"].schema
|
||||
mode_key = next(k for k in schema if k == CONF_MODE)
|
||||
assert mode_key.default() == expected_default
|
||||
assert result["step_id"] == "init"
|
||||
assert result["errors"] is None
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_PASSIVE: False,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"][CONF_PASSIVE] is False
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -178,22 +178,6 @@ async def test_diagnostics(
|
||||
"timings": {},
|
||||
},
|
||||
"all_history": [],
|
||||
"auto_scheduler": {
|
||||
"monotonic_time": ANY,
|
||||
"requests": {},
|
||||
"running": True,
|
||||
"workers": {
|
||||
"00:00:00:00:00:02": {
|
||||
"failed_window": False,
|
||||
"name": "hci1 (00:00:00:00:00:02)",
|
||||
"next_event_at": ANY,
|
||||
"next_sweep_at": ANY,
|
||||
"sweep_last_completed": ANY,
|
||||
"warned_no_fallback": False,
|
||||
"window_end": 0.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
"connectable_history": [],
|
||||
"scanners": [
|
||||
{
|
||||
@@ -255,11 +239,11 @@ async def test_diagnostics(
|
||||
"type": "FakeHaScanner",
|
||||
"current_mode": {
|
||||
"__type": "<enum 'BluetoothScanningMode'>",
|
||||
"repr": "<BluetoothScanningMode.AUTO: 'auto'>",
|
||||
"repr": "<BluetoothScanningMode.ACTIVE: 'active'>",
|
||||
},
|
||||
"requested_mode": {
|
||||
"__type": "<enum 'BluetoothScanningMode'>",
|
||||
"repr": "<BluetoothScanningMode.AUTO: 'auto'>",
|
||||
"repr": "<BluetoothScanningMode.ACTIVE: 'active'>",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -360,12 +344,6 @@ async def test_diagnostics_macos(
|
||||
"sources": {"44:44:33:11:23:45": "local"},
|
||||
"timings": {"44:44:33:11:23:45": [ANY]},
|
||||
},
|
||||
"auto_scheduler": {
|
||||
"monotonic_time": ANY,
|
||||
"requests": {},
|
||||
"running": True,
|
||||
"workers": {},
|
||||
},
|
||||
"all_history": [
|
||||
{
|
||||
"address": "44:44:33:11:23:45",
|
||||
@@ -576,12 +554,6 @@ async def test_diagnostics_remote_adapter(
|
||||
"sources": {"44:44:33:11:23:45": "esp32"},
|
||||
"timings": {"44:44:33:11:23:45": [ANY]},
|
||||
},
|
||||
"auto_scheduler": {
|
||||
"monotonic_time": ANY,
|
||||
"requests": {},
|
||||
"running": True,
|
||||
"workers": {},
|
||||
},
|
||||
"all_history": [
|
||||
{
|
||||
"address": "44:44:33:11:23:45",
|
||||
|
||||
@@ -26,7 +26,6 @@ from homeassistant.components.bluetooth import (
|
||||
)
|
||||
from homeassistant.components.bluetooth.const import (
|
||||
BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS,
|
||||
CONF_MODE,
|
||||
CONF_PASSIVE,
|
||||
CONF_SOURCE,
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID,
|
||||
@@ -96,26 +95,15 @@ async def test_setup_and_stop(
|
||||
assert len(mock_bleak_scanner_start.mock_calls) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"options",
|
||||
[{CONF_MODE: "passive"}, {CONF_PASSIVE: True}],
|
||||
ids=["mode_passive", "legacy_passive_true"],
|
||||
)
|
||||
@pytest.mark.usefixtures("one_adapter")
|
||||
async def test_setup_and_stop_passive(
|
||||
hass: HomeAssistant,
|
||||
mock_bleak_scanner_start: MagicMock,
|
||||
options: dict[str, Any],
|
||||
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock
|
||||
) -> None:
|
||||
"""Test we set up and stop the scanner in passive mode.
|
||||
|
||||
Covers both the new CONF_MODE key and the legacy CONF_PASSIVE boolean
|
||||
so the fallback path in async_setup_entry stays exercised.
|
||||
"""
|
||||
"""Test we and setup and stop the scanner the passive scanner."""
|
||||
entry = MockConfigEntry(
|
||||
domain=bluetooth.DOMAIN,
|
||||
data={},
|
||||
options=options,
|
||||
options={CONF_PASSIVE: True},
|
||||
unique_id="00:00:00:00:00:01",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
@@ -163,7 +151,7 @@ async def test_setup_and_stop_old_bluez(
|
||||
mock_bleak_scanner_start: MagicMock,
|
||||
one_adapter_old_bluez: None,
|
||||
) -> None:
|
||||
"""Default AUTO falls back to active on adapters without passive scan support."""
|
||||
"""Test we and setup and stop the scanner the passive scanner with older bluez."""
|
||||
entry = MockConfigEntry(
|
||||
domain=bluetooth.DOMAIN,
|
||||
data={},
|
||||
@@ -1654,117 +1642,6 @@ async def test_register_callback_by_address(
|
||||
assert service_info.manufacturer_id == 89
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("matcher", "mode", "kwargs", "expected_args"),
|
||||
[
|
||||
pytest.param(
|
||||
{"address": "44:44:33:11:23:45"},
|
||||
BluetoothScanningMode.ACTIVE,
|
||||
{"scan_interval": 300.0, "scan_duration": 5.0},
|
||||
("44:44:33:11:23:45", 300.0, 5.0),
|
||||
id="active_with_interval_and_duration",
|
||||
),
|
||||
pytest.param(
|
||||
{"address": "44:44:33:11:23:45"},
|
||||
BluetoothScanningMode.ACTIVE,
|
||||
{"scan_interval": 300.0},
|
||||
("44:44:33:11:23:45", 300.0, None),
|
||||
id="active_with_interval_default_duration",
|
||||
),
|
||||
pytest.param(
|
||||
{"address": "44:44:33:11:23:45"},
|
||||
BluetoothScanningMode.ACTIVE,
|
||||
{},
|
||||
("44:44:33:11:23:45", None, None),
|
||||
id="active_with_address_default_cadence",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("enable_bluetooth", "mock_bleak_scanner_start")
|
||||
async def test_register_callback_registers_active_scan(
|
||||
hass: HomeAssistant,
|
||||
matcher: dict[str, str],
|
||||
mode: BluetoothScanningMode,
|
||||
kwargs: dict[str, float],
|
||||
expected_args: tuple[str, float | None, float | None],
|
||||
) -> None:
|
||||
"""An address matcher in non-PASSIVE mode registers an active-scan request."""
|
||||
mock_bt: list[Any] = []
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
||||
):
|
||||
await async_setup_with_default_adapter(hass)
|
||||
|
||||
with patch.object(hass.config_entries.flow, "async_init"):
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
def _cb(_si: BluetoothServiceInfo, _ch: BluetoothChange) -> None:
|
||||
return None
|
||||
|
||||
mock_cancel = Mock()
|
||||
with patch.object(
|
||||
HomeAssistantBluetoothManager,
|
||||
"async_register_active_scan",
|
||||
return_value=mock_cancel,
|
||||
) as mock_register:
|
||||
cancel = bluetooth.async_register_callback(
|
||||
hass, _cb, matcher, mode, **kwargs
|
||||
)
|
||||
mock_register.assert_called_once_with(*expected_args)
|
||||
mock_cancel.assert_not_called()
|
||||
cancel()
|
||||
mock_cancel.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("matcher", "mode", "kwargs"),
|
||||
[
|
||||
pytest.param(
|
||||
{"address": "44:44:33:11:23:45"},
|
||||
BluetoothScanningMode.PASSIVE,
|
||||
{"scan_interval": 300.0},
|
||||
id="passive_mode",
|
||||
),
|
||||
pytest.param(
|
||||
{SERVICE_UUID: "cba20d00-224d-11e6-9fb8-0002a5d5c51b"},
|
||||
BluetoothScanningMode.ACTIVE,
|
||||
{"scan_interval": 300.0},
|
||||
id="no_address_in_matcher",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("enable_bluetooth", "mock_bleak_scanner_start")
|
||||
async def test_register_callback_skips_active_scan(
|
||||
hass: HomeAssistant,
|
||||
matcher: dict[str, str],
|
||||
mode: BluetoothScanningMode,
|
||||
kwargs: dict[str, float],
|
||||
) -> None:
|
||||
"""PASSIVE mode or a matcher without an address never registers an active scan."""
|
||||
mock_bt: list[Any] = []
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
||||
):
|
||||
await async_setup_with_default_adapter(hass)
|
||||
|
||||
with patch.object(hass.config_entries.flow, "async_init"):
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
def _cb(_si: BluetoothServiceInfo, _ch: BluetoothChange) -> None:
|
||||
return None
|
||||
|
||||
with patch.object(
|
||||
HomeAssistantBluetoothManager, "async_register_active_scan"
|
||||
) as mock_register:
|
||||
cancel = bluetooth.async_register_callback(
|
||||
hass, _cb, matcher, mode, **kwargs
|
||||
)
|
||||
mock_register.assert_not_called()
|
||||
cancel()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_bluetooth")
|
||||
async def test_register_callback_by_address_connectable_only(
|
||||
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock
|
||||
@@ -2659,35 +2536,6 @@ async def test_process_advertisements_timeout(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_bluetooth", "mock_bleak_scanner_start")
|
||||
async def test_process_advertisements_wires_timeout_as_scan_duration(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""async_process_advertisements forwards its timeout as scan_duration."""
|
||||
|
||||
def _callback(service_info: BluetoothServiceInfo) -> bool:
|
||||
return False
|
||||
|
||||
mock_cancel = Mock()
|
||||
with (
|
||||
patch.object(
|
||||
HomeAssistantBluetoothManager,
|
||||
"async_register_active_scan",
|
||||
return_value=mock_cancel,
|
||||
) as mock_register,
|
||||
pytest.raises(TimeoutError),
|
||||
):
|
||||
await async_process_advertisements(
|
||||
hass,
|
||||
_callback,
|
||||
{"address": "aa:44:33:11:23:45"},
|
||||
BluetoothScanningMode.ACTIVE,
|
||||
0,
|
||||
)
|
||||
mock_register.assert_called_once_with("aa:44:33:11:23:45", None, 0)
|
||||
mock_cancel.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_bluetooth")
|
||||
async def test_wrapped_instance_with_filter(
|
||||
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock
|
||||
|
||||
@@ -461,7 +461,7 @@ async def test_subscribe_scanner_state(
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
|
||||
# hci0 has passive_scan=False so AUTO falls back to ACTIVE.
|
||||
# Should receive initial state for existing scanner
|
||||
async with asyncio.timeout(1):
|
||||
response = await client.receive_json()
|
||||
assert response["event"] == {
|
||||
|
||||
@@ -4,7 +4,6 @@ from unittest.mock import call
|
||||
|
||||
from aioesphomeapi import (
|
||||
AlarmControlPanelCommand,
|
||||
AlarmControlPanelEntityFeature as ESPHomeAlarmControlPanelEntityFeature,
|
||||
AlarmControlPanelEntityState as ESPHomeAlarmEntityState,
|
||||
AlarmControlPanelInfo,
|
||||
AlarmControlPanelState as ESPHomeAlarmState,
|
||||
@@ -23,6 +22,7 @@ from homeassistant.components.alarm_control_panel import (
|
||||
SERVICE_ALARM_TRIGGER,
|
||||
AlarmControlPanelState,
|
||||
)
|
||||
from homeassistant.components.esphome.alarm_control_panel import EspHomeACPFeatures
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -40,12 +40,12 @@ async def test_generic_alarm_control_panel_requires_code(
|
||||
object_id="myalarm_control_panel",
|
||||
key=1,
|
||||
name="my alarm_control_panel",
|
||||
supported_features=ESPHomeAlarmControlPanelEntityFeature.ARM_AWAY
|
||||
| ESPHomeAlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
|
||||
| ESPHomeAlarmControlPanelEntityFeature.ARM_HOME
|
||||
| ESPHomeAlarmControlPanelEntityFeature.ARM_NIGHT
|
||||
| ESPHomeAlarmControlPanelEntityFeature.ARM_VACATION
|
||||
| ESPHomeAlarmControlPanelEntityFeature.TRIGGER,
|
||||
supported_features=EspHomeACPFeatures.ARM_AWAY
|
||||
| EspHomeACPFeatures.ARM_CUSTOM_BYPASS
|
||||
| EspHomeACPFeatures.ARM_HOME
|
||||
| EspHomeACPFeatures.ARM_NIGHT
|
||||
| EspHomeACPFeatures.ARM_VACATION
|
||||
| EspHomeACPFeatures.TRIGGER,
|
||||
requires_code=True,
|
||||
requires_code_to_arm=True,
|
||||
)
|
||||
@@ -172,12 +172,12 @@ async def test_generic_alarm_control_panel_no_code(
|
||||
object_id="myalarm_control_panel",
|
||||
key=1,
|
||||
name="my alarm_control_panel",
|
||||
supported_features=ESPHomeAlarmControlPanelEntityFeature.ARM_AWAY
|
||||
| ESPHomeAlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
|
||||
| ESPHomeAlarmControlPanelEntityFeature.ARM_HOME
|
||||
| ESPHomeAlarmControlPanelEntityFeature.ARM_NIGHT
|
||||
| ESPHomeAlarmControlPanelEntityFeature.ARM_VACATION
|
||||
| ESPHomeAlarmControlPanelEntityFeature.TRIGGER,
|
||||
supported_features=EspHomeACPFeatures.ARM_AWAY
|
||||
| EspHomeACPFeatures.ARM_CUSTOM_BYPASS
|
||||
| EspHomeACPFeatures.ARM_HOME
|
||||
| EspHomeACPFeatures.ARM_NIGHT
|
||||
| EspHomeACPFeatures.ARM_VACATION
|
||||
| EspHomeACPFeatures.TRIGGER,
|
||||
requires_code=False,
|
||||
requires_code_to_arm=False,
|
||||
)
|
||||
@@ -217,12 +217,12 @@ async def test_generic_alarm_control_panel_missing_state(
|
||||
object_id="myalarm_control_panel",
|
||||
key=1,
|
||||
name="my alarm_control_panel",
|
||||
supported_features=ESPHomeAlarmControlPanelEntityFeature.ARM_AWAY
|
||||
| ESPHomeAlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
|
||||
| ESPHomeAlarmControlPanelEntityFeature.ARM_HOME
|
||||
| ESPHomeAlarmControlPanelEntityFeature.ARM_NIGHT
|
||||
| ESPHomeAlarmControlPanelEntityFeature.ARM_VACATION
|
||||
| ESPHomeAlarmControlPanelEntityFeature.TRIGGER,
|
||||
supported_features=EspHomeACPFeatures.ARM_AWAY
|
||||
| EspHomeACPFeatures.ARM_CUSTOM_BYPASS
|
||||
| EspHomeACPFeatures.ARM_HOME
|
||||
| EspHomeACPFeatures.ARM_NIGHT
|
||||
| EspHomeACPFeatures.ARM_VACATION
|
||||
| EspHomeACPFeatures.TRIGGER,
|
||||
requires_code=False,
|
||||
requires_code_to_arm=False,
|
||||
)
|
||||
|
||||
@@ -14,19 +14,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_port_forward_test_port_mapping',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Port forward Test Port Mapping',
|
||||
'object_id_base': 'Mock Title Port forward Test Port Mapping',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:check-network',
|
||||
'original_name': 'Port forward Test Port Mapping',
|
||||
'original_name': 'Mock Title Port forward Test Port Mapping',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -65,19 +65,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_port_forward_test_port_mapping_81',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Port forward Test Port Mapping 81',
|
||||
'object_id_base': 'Mock Title Port forward Test Port Mapping 81',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:check-network',
|
||||
'original_name': 'Port forward Test Port Mapping 81',
|
||||
'original_name': 'Mock Title Port forward Test Port Mapping 81',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -116,19 +116,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_guest',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Wi-Fi Guest',
|
||||
'object_id_base': 'Mock Title Wi-Fi Guest',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Wi-Fi Guest',
|
||||
'original_name': 'Mock Title Wi-Fi Guest',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -167,19 +167,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_main_2_4ghz',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Wi-Fi Main 2.4Ghz',
|
||||
'object_id_base': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Wi-Fi Main 2.4Ghz',
|
||||
'original_name': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -268,19 +268,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_port_forward_test_port_mapping',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Port forward Test Port Mapping',
|
||||
'object_id_base': 'Mock Title Port forward Test Port Mapping',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:check-network',
|
||||
'original_name': 'Port forward Test Port Mapping',
|
||||
'original_name': 'Mock Title Port forward Test Port Mapping',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -319,19 +319,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_port_forward_test_port_mapping_81',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Port forward Test Port Mapping 81',
|
||||
'object_id_base': 'Mock Title Port forward Test Port Mapping 81',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:check-network',
|
||||
'original_name': 'Port forward Test Port Mapping 81',
|
||||
'original_name': 'Mock Title Port forward Test Port Mapping 81',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -370,19 +370,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_guest',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Wi-Fi Guest',
|
||||
'object_id_base': 'Mock Title Wi-Fi Guest',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Wi-Fi Guest',
|
||||
'original_name': 'Mock Title Wi-Fi Guest',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -421,19 +421,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_main_2_4ghz',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Wi-Fi Main 2.4Ghz',
|
||||
'object_id_base': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Wi-Fi Main 2.4Ghz',
|
||||
'original_name': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -522,19 +522,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_port_forward_test_port_mapping',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Port forward Test Port Mapping',
|
||||
'object_id_base': 'Mock Title Port forward Test Port Mapping',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:check-network',
|
||||
'original_name': 'Port forward Test Port Mapping',
|
||||
'original_name': 'Mock Title Port forward Test Port Mapping',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -573,19 +573,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_port_forward_test_port_mapping_81',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Port forward Test Port Mapping 81',
|
||||
'object_id_base': 'Mock Title Port forward Test Port Mapping 81',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:check-network',
|
||||
'original_name': 'Port forward Test Port Mapping 81',
|
||||
'original_name': 'Mock Title Port forward Test Port Mapping 81',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -624,19 +624,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_guest',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Wi-Fi Guest',
|
||||
'object_id_base': 'Mock Title Wi-Fi Guest',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Wi-Fi Guest',
|
||||
'original_name': 'Mock Title Wi-Fi Guest',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -675,19 +675,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_main_2_4ghz',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Wi-Fi Main 2.4Ghz',
|
||||
'object_id_base': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Wi-Fi Main 2.4Ghz',
|
||||
'original_name': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -833,19 +833,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_port_forward_test_port_mapping',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Port forward Test Port Mapping',
|
||||
'object_id_base': 'Mock Title Port forward Test Port Mapping',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:check-network',
|
||||
'original_name': 'Port forward Test Port Mapping',
|
||||
'original_name': 'Mock Title Port forward Test Port Mapping',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -884,19 +884,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_port_forward_test_port_mapping_81',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Port forward Test Port Mapping 81',
|
||||
'object_id_base': 'Mock Title Port forward Test Port Mapping 81',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:check-network',
|
||||
'original_name': 'Port forward Test Port Mapping 81',
|
||||
'original_name': 'Mock Title Port forward Test Port Mapping 81',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -935,19 +935,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_guest',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Wi-Fi Guest',
|
||||
'object_id_base': 'Mock Title Wi-Fi Guest',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Wi-Fi Guest',
|
||||
'original_name': 'Mock Title Wi-Fi Guest',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
@@ -986,19 +986,19 @@
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_main_2_4ghz',
|
||||
'has_entity_name': True,
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Wi-Fi Main 2.4Ghz',
|
||||
'object_id_base': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Wi-Fi Main 2.4Ghz',
|
||||
'original_name': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
|
||||
Reference in New Issue
Block a user