Compare commits

..

1 Commits

Author SHA1 Message Date
ludeeus 296d625121 Add devcontainer-lock.json file 2026-05-23 18:11:53 +00:00
26 changed files with 220 additions and 487 deletions
+9
View File
@@ -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
+1 -1
View File
@@ -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)
+6 -15
View File
@@ -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"
+1 -20
View File
@@ -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 -23
View File
@@ -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."]
}
-2
View File
@@ -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
+1
View File
@@ -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__(
+1
View File
@@ -170,6 +170,7 @@ class SwitchInfo(TypedDict):
"""FRITZ!Box switch info class."""
description: str
friendly_name: str
icon: str
type: str
callback_update: Callable
+52 -17
View File
@@ -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
+1 -1
View File
@@ -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
+4 -4
View File
@@ -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
+19 -40
View File
@@ -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()
+2 -30
View File
@@ -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",
+4 -156
View File
@@ -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,