mirror of
https://github.com/home-assistant/core.git
synced 2025-12-06 07:58:08 +00:00
Compare commits
16 Commits
whirlpool_
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87b9c3193e | ||
|
|
061c38d2a7 | ||
|
|
e1720be5a4 | ||
|
|
2d13a92496 | ||
|
|
b06bffa815 | ||
|
|
b8f4b9515b | ||
|
|
3c10e9f1c0 | ||
|
|
2dec3befcd | ||
|
|
7d065bf314 | ||
|
|
3315680d0b | ||
|
|
ce48c89a26 | ||
|
|
f67a926f56 | ||
|
|
e0a9d305b2 | ||
|
|
4ff141d35e | ||
|
|
f12a43b2b7 | ||
|
|
35e6f504a3 |
@@ -13,6 +13,7 @@ core: &core
|
||||
|
||||
# Our base platforms, that are used by other integrations
|
||||
base_platforms: &base_platforms
|
||||
- homeassistant/components/ai_task/**
|
||||
- homeassistant/components/air_quality/**
|
||||
- homeassistant/components/alarm_control_panel/**
|
||||
- homeassistant/components/assist_satellite/**
|
||||
|
||||
@@ -101,8 +101,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
vol.Schema({str: STRUCTURE_FIELD_SCHEMA}),
|
||||
_validate_structure_fields,
|
||||
),
|
||||
vol.Optional(ATTR_ATTACHMENTS): vol.All(
|
||||
cv.ensure_list, [selector.MediaSelector({"accept": ["*/*"]})]
|
||||
vol.Optional(ATTR_ATTACHMENTS): selector.MediaSelector(
|
||||
{"accept": ["*/*"], "multiple": True}
|
||||
),
|
||||
}
|
||||
),
|
||||
@@ -118,8 +118,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
vol.Required(ATTR_TASK_NAME): cv.string,
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(ATTR_INSTRUCTIONS): cv.string,
|
||||
vol.Optional(ATTR_ATTACHMENTS): vol.All(
|
||||
cv.ensure_list, [selector.MediaSelector({"accept": ["*/*"]})]
|
||||
vol.Optional(ATTR_ATTACHMENTS): selector.MediaSelector(
|
||||
{"accept": ["*/*"], "multiple": True}
|
||||
),
|
||||
}
|
||||
),
|
||||
|
||||
@@ -7,7 +7,7 @@ from homeassistant.core import HomeAssistant
|
||||
|
||||
from .coordinator import AirobotConfigEntry, AirobotDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.CLIMATE]
|
||||
PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AirobotConfigEntry) -> bool:
|
||||
|
||||
@@ -44,7 +44,7 @@ rules:
|
||||
discovery: done
|
||||
docs-data-update: done
|
||||
docs-examples: todo
|
||||
docs-known-limitations: todo
|
||||
docs-known-limitations: done
|
||||
docs-supported-devices: done
|
||||
docs-supported-functions: done
|
||||
docs-troubleshooting: done
|
||||
@@ -54,7 +54,7 @@ rules:
|
||||
comment: Single device integration, no dynamic device discovery needed.
|
||||
entity-category: done
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: todo
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: todo
|
||||
exception-translations: done
|
||||
icon-translations: todo
|
||||
|
||||
134
homeassistant/components/airobot/sensor.py
Normal file
134
homeassistant/components/airobot/sensor.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""Sensor platform for Airobot thermostat."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from pyairobotrest.models import ThermostatStatus
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from . import AirobotConfigEntry
|
||||
from .entity import AirobotEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AirobotSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes Airobot sensor entity."""
|
||||
|
||||
value_fn: Callable[[ThermostatStatus], StateType]
|
||||
supported_fn: Callable[[ThermostatStatus], bool] = lambda _: True
|
||||
|
||||
|
||||
SENSOR_TYPES: tuple[AirobotSensorEntityDescription, ...] = (
|
||||
AirobotSensorEntityDescription(
|
||||
key="air_temperature",
|
||||
translation_key="air_temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda status: status.temp_air,
|
||||
),
|
||||
AirobotSensorEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda status: status.hum_air,
|
||||
),
|
||||
AirobotSensorEntityDescription(
|
||||
key="floor_temperature",
|
||||
translation_key="floor_temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda status: status.temp_floor,
|
||||
supported_fn=lambda status: status.has_floor_sensor,
|
||||
),
|
||||
AirobotSensorEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda status: status.co2,
|
||||
supported_fn=lambda status: status.has_co2_sensor,
|
||||
),
|
||||
AirobotSensorEntityDescription(
|
||||
key="air_quality_index",
|
||||
device_class=SensorDeviceClass.AQI,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda status: status.aqi,
|
||||
supported_fn=lambda status: status.has_co2_sensor,
|
||||
),
|
||||
AirobotSensorEntityDescription(
|
||||
key="heating_uptime",
|
||||
translation_key="heating_uptime",
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||
suggested_unit_of_measurement=UnitOfTime.HOURS,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda status: status.heating_uptime,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AirobotSensorEntityDescription(
|
||||
key="errors",
|
||||
translation_key="errors",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda status: status.errors,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: AirobotConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Airobot sensor platform."""
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
AirobotSensor(coordinator, description)
|
||||
for description in SENSOR_TYPES
|
||||
if description.supported_fn(coordinator.data.status)
|
||||
)
|
||||
|
||||
|
||||
class AirobotSensor(AirobotEntity, SensorEntity):
|
||||
"""Representation of an Airobot sensor."""
|
||||
|
||||
entity_description: AirobotSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator,
|
||||
description: AirobotSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.data.status.device_id}_{description.key}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.data.status)
|
||||
@@ -43,6 +43,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"air_temperature": {
|
||||
"name": "Air temperature"
|
||||
},
|
||||
"device_uptime": {
|
||||
"name": "Device uptime"
|
||||
},
|
||||
"errors": {
|
||||
"name": "Error count"
|
||||
},
|
||||
"floor_temperature": {
|
||||
"name": "Floor temperature"
|
||||
},
|
||||
"heating_uptime": {
|
||||
"name": "Heating uptime"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"authentication_failed": {
|
||||
"message": "Authentication failed, please reauthenticate."
|
||||
|
||||
@@ -42,14 +42,25 @@ async def get_remotes(client: MozartClient) -> list[PairedRemote]:
|
||||
|
||||
def get_device_buttons(model: BeoModel) -> list[str]:
|
||||
"""Get supported buttons for a given model."""
|
||||
# Beoconnect Core does not have any buttons
|
||||
if model == BeoModel.BEOCONNECT_CORE:
|
||||
return []
|
||||
|
||||
buttons = DEVICE_BUTTONS.copy()
|
||||
|
||||
# Beosound Premiere does not have a bluetooth button
|
||||
if model == BeoModel.BEOSOUND_PREMIERE:
|
||||
# Models that don't have a microphone button
|
||||
if model in (
|
||||
BeoModel.BEOSOUND_A5,
|
||||
BeoModel.BEOSOUND_A9,
|
||||
BeoModel.BEOSOUND_PREMIERE,
|
||||
):
|
||||
buttons.remove(BeoButtons.MICROPHONE)
|
||||
|
||||
# Models that don't have a Bluetooth button
|
||||
if model in (
|
||||
BeoModel.BEOSOUND_A9,
|
||||
BeoModel.BEOSOUND_PREMIERE,
|
||||
):
|
||||
buttons.remove(BeoButtons.BLUETOOTH)
|
||||
|
||||
# Beoconnect Core does not have any buttons
|
||||
elif model == BeoModel.BEOCONNECT_CORE:
|
||||
buttons = []
|
||||
|
||||
return buttons
|
||||
|
||||
@@ -56,7 +56,6 @@ class DeviceAutomationConditionProtocol(Protocol):
|
||||
class DeviceCondition(Condition):
|
||||
"""Device condition."""
|
||||
|
||||
_hass: HomeAssistant
|
||||
_config: ConfigType
|
||||
|
||||
@classmethod
|
||||
@@ -87,7 +86,7 @@ class DeviceCondition(Condition):
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
|
||||
"""Initialize condition."""
|
||||
self._hass = hass
|
||||
super().__init__(hass, config)
|
||||
assert config.options is not None
|
||||
self._config = config.options
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/dnsip",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["aiodns==3.5.0"]
|
||||
"requirements": ["aiodns==3.6.0"]
|
||||
}
|
||||
|
||||
@@ -102,6 +102,12 @@ class ConfiguredDoorBird:
|
||||
"""Get token for device."""
|
||||
return self._token
|
||||
|
||||
def _get_hass_url(self) -> str:
|
||||
"""Get the Home Assistant URL for this device."""
|
||||
if custom_url := self.custom_url:
|
||||
return custom_url
|
||||
return get_url(self._hass, prefer_external=False)
|
||||
|
||||
async def async_register_events(self) -> None:
|
||||
"""Register events on device."""
|
||||
if not self.door_station_events:
|
||||
@@ -146,13 +152,7 @@ class ConfiguredDoorBird:
|
||||
|
||||
async def _async_register_events(self) -> dict[str, Any]:
|
||||
"""Register events on device."""
|
||||
# Override url if another is specified in the configuration
|
||||
if custom_url := self.custom_url:
|
||||
hass_url = custom_url
|
||||
else:
|
||||
# Get the URL of this server
|
||||
hass_url = get_url(self._hass, prefer_external=False)
|
||||
|
||||
hass_url = self._get_hass_url()
|
||||
http_fav = await self._async_get_http_favorites()
|
||||
if any(
|
||||
# Note that a list comp is used here to ensure all
|
||||
@@ -191,10 +191,14 @@ class ConfiguredDoorBird:
|
||||
self._get_event_name(event): event_type
|
||||
for event, event_type in DEFAULT_EVENT_TYPES
|
||||
}
|
||||
hass_url = self._get_hass_url()
|
||||
for identifier, data in http_fav.items():
|
||||
title: str | None = data.get("title")
|
||||
if not title or not title.startswith("Home Assistant"):
|
||||
continue
|
||||
value: str | None = data.get("value")
|
||||
if not value or not value.startswith(hass_url):
|
||||
continue # Not our favorite - different HA instance or stale
|
||||
event = title.partition("(")[2].strip(")")
|
||||
if input_type := favorite_input_type.get(identifier):
|
||||
events.append(DoorbirdEvent(event, input_type))
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
"winter_mode": {}
|
||||
},
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20251203.0"]
|
||||
"requirements": ["home-assistant-frontend==20251203.1"]
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
}
|
||||
),
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
description_placeholders={"example_image_path": "/config/www/image.jpg"},
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
"fields": {
|
||||
"filenames": {
|
||||
"description": "Attachments to add to the prompt (images, PDFs, etc)",
|
||||
"example": "/config/www/image.jpg",
|
||||
"example": "{example_image_path}",
|
||||
"name": "Attachment filenames"
|
||||
},
|
||||
"prompt": {
|
||||
|
||||
@@ -159,4 +159,5 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
_async_handle_upload,
|
||||
schema=UPLOAD_SERVICE_SCHEMA,
|
||||
supports_response=SupportsResponse.OPTIONAL,
|
||||
description_placeholders={"example_image_path": "/config/www/image.jpg"},
|
||||
)
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
},
|
||||
"filename": {
|
||||
"description": "Path to the image or video to upload.",
|
||||
"example": "/config/www/image.jpg",
|
||||
"example": "{example_image_path}",
|
||||
"name": "Filename"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -108,6 +108,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
SERVICE_MESSAGE,
|
||||
_async_service_message,
|
||||
schema=SERVICE_MESSAGE_SCHEMA,
|
||||
description_placeholders={"icons_url": "https://developer.lametric.com/icons"},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@
|
||||
"name": "[%key:common::config_flow::data::device%]"
|
||||
},
|
||||
"icon": {
|
||||
"description": "The ID number of the icon or animation to display. List of all icons and their IDs can be found at: https://developer.lametric.com/icons.",
|
||||
"description": "The ID number of the icon or animation to display. List of all icons and their IDs can be found at: {icons_url}.",
|
||||
"name": "Icon ID"
|
||||
},
|
||||
"icon_type": {
|
||||
|
||||
@@ -52,7 +52,7 @@ class StateConditionBase(Condition):
|
||||
self, hass: HomeAssistant, config: ConditionConfig, state: str
|
||||
) -> None:
|
||||
"""Initialize condition."""
|
||||
self._hass = hass
|
||||
super().__init__(hass, config)
|
||||
if TYPE_CHECKING:
|
||||
assert config.target
|
||||
assert config.options
|
||||
|
||||
@@ -30,9 +30,7 @@ rules:
|
||||
integration-owner: done
|
||||
log-when-unavailable: todo
|
||||
parallel-updates: todo
|
||||
reauthentication-flow:
|
||||
status: exempt
|
||||
comment: Devices don't require authentication
|
||||
reauthentication-flow: done
|
||||
test-coverage: todo
|
||||
|
||||
# Gold
|
||||
|
||||
@@ -129,4 +129,5 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
async_handle_upload,
|
||||
schema=UPLOAD_SERVICE_SCHEMA,
|
||||
supports_response=SupportsResponse.OPTIONAL,
|
||||
description_placeholders={"example_image_path": "/config/www/image.jpg"},
|
||||
)
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
},
|
||||
"filename": {
|
||||
"description": "Path to the file to upload.",
|
||||
"example": "/config/www/image.jpg",
|
||||
"example": "{example_image_path}",
|
||||
"name": "Filename"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -65,11 +65,9 @@ rules:
|
||||
exception-translations: done
|
||||
icon-translations: todo
|
||||
reconfiguration-flow: todo
|
||||
repair-issues:
|
||||
status: todo
|
||||
comment: The Cloud vs Local API warning should probably be a repair issue.
|
||||
repair-issues: done
|
||||
stale-devices: done
|
||||
# Platinum
|
||||
async-dependency: todo
|
||||
async-dependency: done
|
||||
inject-websession: done
|
||||
strict-typing: done
|
||||
|
||||
@@ -150,6 +150,7 @@ class SunCondition(Condition):
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
|
||||
"""Initialize condition."""
|
||||
super().__init__(hass, config)
|
||||
assert config.options is not None
|
||||
self._options = config.options
|
||||
|
||||
|
||||
@@ -635,14 +635,14 @@ class AbstractTemplateLight(AbstractTemplateEntity, LightEntity):
|
||||
# Support legacy mireds in template light.
|
||||
temperature = int(render)
|
||||
if (min_kelvin := self._attr_min_color_temp_kelvin) is not None:
|
||||
min_mireds = color_util.color_temperature_kelvin_to_mired(min_kelvin)
|
||||
else:
|
||||
min_mireds = DEFAULT_MIN_MIREDS
|
||||
|
||||
if (max_kelvin := self._attr_max_color_temp_kelvin) is not None:
|
||||
max_mireds = color_util.color_temperature_kelvin_to_mired(max_kelvin)
|
||||
max_mireds = color_util.color_temperature_kelvin_to_mired(min_kelvin)
|
||||
else:
|
||||
max_mireds = DEFAULT_MAX_MIREDS
|
||||
|
||||
if (max_kelvin := self._attr_max_color_temp_kelvin) is not None:
|
||||
min_mireds = color_util.color_temperature_kelvin_to_mired(max_kelvin)
|
||||
else:
|
||||
min_mireds = DEFAULT_MIN_MIREDS
|
||||
if min_mireds <= temperature <= max_mireds:
|
||||
self._attr_color_temp_kelvin = (
|
||||
color_util.color_temperature_mired_to_kelvin(temperature)
|
||||
@@ -856,42 +856,36 @@ class AbstractTemplateLight(AbstractTemplateEntity, LightEntity):
|
||||
|
||||
try:
|
||||
if render in (None, "None", ""):
|
||||
self._attr_max_mireds = DEFAULT_MAX_MIREDS
|
||||
self._attr_max_color_temp_kelvin = None
|
||||
self._attr_min_color_temp_kelvin = None
|
||||
return
|
||||
|
||||
self._attr_max_mireds = max_mireds = int(render)
|
||||
self._attr_max_color_temp_kelvin = (
|
||||
color_util.color_temperature_mired_to_kelvin(max_mireds)
|
||||
self._attr_min_color_temp_kelvin = (
|
||||
color_util.color_temperature_mired_to_kelvin(int(render))
|
||||
)
|
||||
except ValueError:
|
||||
_LOGGER.exception(
|
||||
"Template must supply an integer temperature within the range for"
|
||||
" this light, or 'None'"
|
||||
)
|
||||
self._attr_max_mireds = DEFAULT_MAX_MIREDS
|
||||
self._attr_max_color_temp_kelvin = None
|
||||
self._attr_min_color_temp_kelvin = None
|
||||
|
||||
@callback
|
||||
def _update_min_mireds(self, render):
|
||||
"""Update the min mireds from the template."""
|
||||
try:
|
||||
if render in (None, "None", ""):
|
||||
self._attr_min_mireds = DEFAULT_MIN_MIREDS
|
||||
self._attr_min_color_temp_kelvin = None
|
||||
self._attr_max_color_temp_kelvin = None
|
||||
return
|
||||
|
||||
self._attr_min_mireds = min_mireds = int(render)
|
||||
self._attr_min_color_temp_kelvin = (
|
||||
color_util.color_temperature_mired_to_kelvin(min_mireds)
|
||||
self._attr_max_color_temp_kelvin = (
|
||||
color_util.color_temperature_mired_to_kelvin(int(render))
|
||||
)
|
||||
except ValueError:
|
||||
_LOGGER.exception(
|
||||
"Template must supply an integer temperature within the range for"
|
||||
" this light, or 'None'"
|
||||
)
|
||||
self._attr_min_mireds = DEFAULT_MIN_MIREDS
|
||||
self._attr_min_color_temp_kelvin = None
|
||||
self._attr_max_color_temp_kelvin = None
|
||||
|
||||
@callback
|
||||
def _update_supports_transition(self, render):
|
||||
|
||||
@@ -19,6 +19,7 @@ from homeassistant.components.light import (
|
||||
LightEntityDescription,
|
||||
LightEntityFeature,
|
||||
)
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util.color import rgb_hex_to_rgb_list
|
||||
@@ -117,6 +118,7 @@ ENTITY_DESCRIPTIONS: tuple[UnifiLightEntityDescription, ...] = (
|
||||
UnifiLightEntityDescription[Devices, Device](
|
||||
key="LED control",
|
||||
translation_key="led_control",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
allowed_fn=lambda hub, obj_id: True,
|
||||
api_handler_fn=lambda api: api.devices,
|
||||
available_fn=async_device_available_fn,
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["uiprotect", "unifi_discovery"],
|
||||
"requirements": ["uiprotect==7.31.0", "unifi-discovery==1.2.0"],
|
||||
"requirements": ["uiprotect==7.33.2", "unifi-discovery==1.2.0"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Ubiquiti Networks",
|
||||
|
||||
@@ -114,6 +114,7 @@ class ZoneCondition(Condition):
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
|
||||
"""Initialize condition."""
|
||||
super().__init__(hass, config)
|
||||
assert config.options is not None
|
||||
self._options = config.options
|
||||
|
||||
|
||||
@@ -259,6 +259,8 @@ _CONDITION_SCHEMA = _CONDITION_BASE_SCHEMA.extend(
|
||||
class Condition(abc.ABC):
|
||||
"""Condition class."""
|
||||
|
||||
_hass: HomeAssistant
|
||||
|
||||
@classmethod
|
||||
async def async_validate_complete_config(
|
||||
cls, hass: HomeAssistant, complete_config: ConfigType
|
||||
@@ -293,6 +295,7 @@ class Condition(abc.ABC):
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
|
||||
"""Initialize condition."""
|
||||
self._hass = hass
|
||||
|
||||
@abc.abstractmethod
|
||||
async def async_get_checker(self) -> ConditionCheckerType:
|
||||
|
||||
@@ -124,6 +124,12 @@ BLOCKED_CUSTOM_INTEGRATIONS: dict[str, BlockedIntegration] = {
|
||||
# Added in 2025.10.0 because of
|
||||
# https://github.com/frenck/spook/issues/1066
|
||||
"spook": BlockedIntegration(AwesomeVersion("4.0.0"), "breaks the template engine"),
|
||||
# Added in 2025.12.1 because of
|
||||
# https://github.com/JaccoR/hass-entso-e/issues/263
|
||||
"entsoe": BlockedIntegration(
|
||||
AwesomeVersion("0.7.1"),
|
||||
"crashes Home Assistant when it can't connect to the API",
|
||||
),
|
||||
}
|
||||
|
||||
DATA_COMPONENTS: HassKey[dict[str, ModuleType | ComponentProtocol]] = HassKey(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
aiodhcpwatcher==1.2.1
|
||||
aiodiscover==2.7.1
|
||||
aiodns==3.5.0
|
||||
aiodns==3.6.0
|
||||
aiohasupervisor==0.3.3
|
||||
aiohttp-asyncmdnsresolver==0.1.1
|
||||
aiohttp-fast-zlib==0.3.0
|
||||
@@ -39,7 +39,7 @@ habluetooth==5.8.0
|
||||
hass-nabucasa==1.7.0
|
||||
hassil==3.5.0
|
||||
home-assistant-bluetooth==1.13.1
|
||||
home-assistant-frontend==20251203.0
|
||||
home-assistant-frontend==20251203.1
|
||||
home-assistant-intents==2025.12.2
|
||||
httpx==0.28.1
|
||||
ifaddr==0.2.0
|
||||
|
||||
@@ -24,7 +24,7 @@ classifiers = [
|
||||
]
|
||||
requires-python = ">=3.13.2"
|
||||
dependencies = [
|
||||
"aiodns==3.5.0",
|
||||
"aiodns==3.6.0",
|
||||
# Integrations may depend on hassio integration without listing it to
|
||||
# change behavior based on presence of supervisor. Deprecated with #127228
|
||||
# Lib can be removed with 2025.11
|
||||
|
||||
2
requirements.txt
generated
2
requirements.txt
generated
@@ -3,7 +3,7 @@
|
||||
-c homeassistant/package_constraints.txt
|
||||
|
||||
# Home Assistant Core
|
||||
aiodns==3.5.0
|
||||
aiodns==3.6.0
|
||||
aiohasupervisor==0.3.3
|
||||
aiohttp==3.13.2
|
||||
aiohttp_cors==0.8.1
|
||||
|
||||
6
requirements_all.txt
generated
6
requirements_all.txt
generated
@@ -231,7 +231,7 @@ aiodhcpwatcher==1.2.1
|
||||
aiodiscover==2.7.1
|
||||
|
||||
# homeassistant.components.dnsip
|
||||
aiodns==3.5.0
|
||||
aiodns==3.6.0
|
||||
|
||||
# homeassistant.components.duke_energy
|
||||
aiodukeenergy==0.3.0
|
||||
@@ -1204,7 +1204,7 @@ hole==0.9.0
|
||||
holidays==0.84
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20251203.0
|
||||
home-assistant-frontend==20251203.1
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.12.2
|
||||
@@ -3059,7 +3059,7 @@ typedmonarchmoney==0.4.4
|
||||
uasiren==0.0.1
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
uiprotect==7.31.0
|
||||
uiprotect==7.33.2
|
||||
|
||||
# homeassistant.components.landisgyr_heat_meter
|
||||
ultraheat-api==0.5.7
|
||||
|
||||
6
requirements_test_all.txt
generated
6
requirements_test_all.txt
generated
@@ -222,7 +222,7 @@ aiodhcpwatcher==1.2.1
|
||||
aiodiscover==2.7.1
|
||||
|
||||
# homeassistant.components.dnsip
|
||||
aiodns==3.5.0
|
||||
aiodns==3.6.0
|
||||
|
||||
# homeassistant.components.duke_energy
|
||||
aiodukeenergy==0.3.0
|
||||
@@ -1062,7 +1062,7 @@ hole==0.9.0
|
||||
holidays==0.84
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20251203.0
|
||||
home-assistant-frontend==20251203.1
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.12.2
|
||||
@@ -2547,7 +2547,7 @@ typedmonarchmoney==0.4.4
|
||||
uasiren==0.0.1
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
uiprotect==7.31.0
|
||||
uiprotect==7.33.2
|
||||
|
||||
# homeassistant.components.landisgyr_heat_meter
|
||||
ultraheat-api==0.5.7
|
||||
|
||||
@@ -12,7 +12,13 @@ from pyairobotrest.models import (
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.airobot.const import DOMAIN
|
||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_MAC,
|
||||
CONF_PASSWORD,
|
||||
CONF_USERNAME,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@@ -105,16 +111,24 @@ def mock_config_entry() -> MockConfigEntry:
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Fixture to specify platforms to test."""
|
||||
return [Platform.CLIMATE, Platform.SENSOR]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def init_integration(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_airobot_client: AsyncMock,
|
||||
platforms: list[Platform],
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the Airobot integration for testing."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
with patch("homeassistant.components.airobot.PLATFORMS", platforms):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return mock_config_entry
|
||||
|
||||
220
tests/components/airobot/snapshots/test_sensor.ambr
Normal file
220
tests/components/airobot/snapshots/test_sensor.ambr
Normal file
@@ -0,0 +1,220 @@
|
||||
# serializer version: 1
|
||||
# name: test_sensors[sensor.test_thermostat_air_temperature-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.test_thermostat_air_temperature',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 1,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Air temperature',
|
||||
'platform': 'airobot',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'air_temperature',
|
||||
'unique_id': 'T01A1B2C3_air_temperature',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.test_thermostat_air_temperature-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'temperature',
|
||||
'friendly_name': 'Test Thermostat Air temperature',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_thermostat_air_temperature',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '22.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.test_thermostat_error_count-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.test_thermostat_error_count',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Error count',
|
||||
'platform': 'airobot',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'errors',
|
||||
'unique_id': 'T01A1B2C3_errors',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.test_thermostat_error_count-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Test Thermostat Error count',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_thermostat_error_count',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.test_thermostat_heating_uptime-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.test_thermostat_heating_uptime',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Heating uptime',
|
||||
'platform': 'airobot',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'heating_uptime',
|
||||
'unique_id': 'T01A1B2C3_heating_uptime',
|
||||
'unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.test_thermostat_heating_uptime-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
'friendly_name': 'Test Thermostat Heating uptime',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_thermostat_heating_uptime',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1.38888888888889',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.test_thermostat_humidity-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.test_thermostat_humidity',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.HUMIDITY: 'humidity'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'airobot',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'T01A1B2C3_humidity',
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.test_thermostat_humidity-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'humidity',
|
||||
'friendly_name': 'Test Thermostat Humidity',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_thermostat_humidity',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '45.0',
|
||||
})
|
||||
# ---
|
||||
@@ -17,7 +17,7 @@ from homeassistant.components.climate import (
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
@@ -25,12 +25,19 @@ import homeassistant.helpers.entity_registry as er
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Fixture to specify platforms to test."""
|
||||
return [Platform.CLIMATE]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_climate_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
platforms: list[Platform],
|
||||
) -> None:
|
||||
"""Test climate entities."""
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
38
tests/components/airobot/test_sensor.py
Normal file
38
tests/components/airobot/test_sensor.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""Tests for the Airobot sensor platform."""
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Fixture to specify platforms to test."""
|
||||
return [Platform.SENSOR]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||
async def test_sensors(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the sensor entities."""
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||
async def test_sensor_availability_without_optional_sensors(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test sensors are not created when optional hardware is not present."""
|
||||
# Default mock has no floor sensor, CO2, or AQI - they should not be created
|
||||
assert hass.states.get("sensor.test_thermostat_floor_temperature") is None
|
||||
assert hass.states.get("sensor.test_thermostat_carbon_dioxide") is None
|
||||
assert hass.states.get("sensor.test_thermostat_air_quality_index") is None
|
||||
@@ -37,6 +37,7 @@ from .const import (
|
||||
TEST_DATA_CREATE_ENTRY,
|
||||
TEST_DATA_CREATE_ENTRY_2,
|
||||
TEST_DATA_CREATE_ENTRY_3,
|
||||
TEST_DATA_CREATE_ENTRY_4,
|
||||
TEST_FRIENDLY_NAME,
|
||||
TEST_FRIENDLY_NAME_3,
|
||||
TEST_FRIENDLY_NAME_4,
|
||||
@@ -48,10 +49,12 @@ from .const import (
|
||||
TEST_NAME,
|
||||
TEST_NAME_2,
|
||||
TEST_NAME_3,
|
||||
TEST_NAME_4,
|
||||
TEST_REMOTE_SERIAL,
|
||||
TEST_SERIAL_NUMBER,
|
||||
TEST_SERIAL_NUMBER_2,
|
||||
TEST_SERIAL_NUMBER_3,
|
||||
TEST_SERIAL_NUMBER_4,
|
||||
TEST_SOUND_MODE,
|
||||
TEST_SOUND_MODE_2,
|
||||
TEST_SOUND_MODE_NAME,
|
||||
@@ -93,6 +96,17 @@ def mock_config_entry_premiere() -> MockConfigEntry:
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry_a5() -> MockConfigEntry:
|
||||
"""Mock config entry for Beosound A5."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id=TEST_SERIAL_NUMBER_4,
|
||||
data=TEST_DATA_CREATE_ENTRY_4,
|
||||
title=TEST_NAME_4,
|
||||
)
|
||||
|
||||
|
||||
async def mock_websocket_connection(
|
||||
hass: HomeAssistant, mock_mozart_client: AsyncMock
|
||||
) -> None:
|
||||
|
||||
@@ -42,6 +42,7 @@ TEST_MODEL_CORE = "Beoconnect Core"
|
||||
TEST_MODEL_PREMIERE = "Beosound Premiere"
|
||||
TEST_MODEL_THEATRE = "Beosound Theatre"
|
||||
TEST_MODEL_LEVEL = "Beosound Level"
|
||||
TEST_MODEL_A5 = "Beosound A5"
|
||||
TEST_SERIAL_NUMBER = "11111111"
|
||||
TEST_NAME = f"{TEST_MODEL_BALANCE}-{TEST_SERIAL_NUMBER}"
|
||||
TEST_FRIENDLY_NAME = "Living room Balance"
|
||||
@@ -64,9 +65,11 @@ TEST_JID_3 = f"{TEST_TYPE_NUMBER}.{TEST_ITEM_NUMBER}.{TEST_SERIAL_NUMBER_3}@prod
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID_3 = f"media_player.beosound_premiere_{TEST_SERIAL_NUMBER_3}"
|
||||
TEST_HOST_3 = "192.168.0.3"
|
||||
|
||||
TEST_FRIENDLY_NAME_4 = "Lounge room Balance"
|
||||
TEST_JID_4 = f"{TEST_TYPE_NUMBER}.{TEST_ITEM_NUMBER}.44444444@products.bang-olufsen.com"
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID_4 = "media_player.beosound_balance_44444444"
|
||||
TEST_FRIENDLY_NAME_4 = "Lounge room A5"
|
||||
TEST_SERIAL_NUMBER_4 = "44444444"
|
||||
TEST_NAME_4 = f"{TEST_MODEL_A5}-{TEST_SERIAL_NUMBER_4}"
|
||||
TEST_JID_4 = f"{TEST_TYPE_NUMBER}.{TEST_ITEM_NUMBER}.{TEST_SERIAL_NUMBER_4}@products.bang-olufsen.com"
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID_4 = f"media_player.beosound_a5_{TEST_SERIAL_NUMBER_4}"
|
||||
TEST_HOST_4 = "192.168.0.4"
|
||||
|
||||
# Beoremote One
|
||||
@@ -105,6 +108,13 @@ TEST_DATA_CREATE_ENTRY_3 = {
|
||||
CONF_NAME: TEST_NAME_3,
|
||||
}
|
||||
|
||||
TEST_DATA_CREATE_ENTRY_4 = {
|
||||
CONF_HOST: TEST_HOST_4,
|
||||
CONF_MODEL: TEST_MODEL_A5,
|
||||
CONF_BEOLINK_JID: TEST_JID_4,
|
||||
CONF_NAME: TEST_NAME_4,
|
||||
}
|
||||
|
||||
TEST_DATA_ZEROCONF = ZeroconfServiceInfo(
|
||||
ip_address=IPv4Address(TEST_HOST),
|
||||
ip_addresses=[IPv4Address(TEST_HOST)],
|
||||
|
||||
@@ -45,11 +45,11 @@
|
||||
'beolink': dict({
|
||||
'listeners': dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
'peers': dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
'self': dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
|
||||
@@ -1,4 +1,108 @@
|
||||
# serializer version: 1
|
||||
# name: test_button_event_creation_a5
|
||||
list([
|
||||
'event.beosound_a5_44444444_bluetooth',
|
||||
'event.beosound_a5_44444444_next',
|
||||
'event.beosound_a5_44444444_play_pause',
|
||||
'event.beosound_a5_44444444_favorite_1',
|
||||
'event.beosound_a5_44444444_favorite_2',
|
||||
'event.beosound_a5_44444444_favorite_3',
|
||||
'event.beosound_a5_44444444_favorite_4',
|
||||
'event.beosound_a5_44444444_previous',
|
||||
'event.beosound_a5_44444444_volume',
|
||||
'event.beoremote_one_55555555_44444444_light_blue',
|
||||
'event.beoremote_one_55555555_44444444_light_digit_0',
|
||||
'event.beoremote_one_55555555_44444444_light_digit_1',
|
||||
'event.beoremote_one_55555555_44444444_light_digit_2',
|
||||
'event.beoremote_one_55555555_44444444_light_digit_3',
|
||||
'event.beoremote_one_55555555_44444444_light_digit_4',
|
||||
'event.beoremote_one_55555555_44444444_light_digit_5',
|
||||
'event.beoremote_one_55555555_44444444_light_digit_6',
|
||||
'event.beoremote_one_55555555_44444444_light_digit_7',
|
||||
'event.beoremote_one_55555555_44444444_light_digit_8',
|
||||
'event.beoremote_one_55555555_44444444_light_digit_9',
|
||||
'event.beoremote_one_55555555_44444444_light_down',
|
||||
'event.beoremote_one_55555555_44444444_light_green',
|
||||
'event.beoremote_one_55555555_44444444_light_left',
|
||||
'event.beoremote_one_55555555_44444444_light_play',
|
||||
'event.beoremote_one_55555555_44444444_light_red',
|
||||
'event.beoremote_one_55555555_44444444_light_rewind',
|
||||
'event.beoremote_one_55555555_44444444_light_right',
|
||||
'event.beoremote_one_55555555_44444444_light_select',
|
||||
'event.beoremote_one_55555555_44444444_light_stop',
|
||||
'event.beoremote_one_55555555_44444444_light_up',
|
||||
'event.beoremote_one_55555555_44444444_light_wind',
|
||||
'event.beoremote_one_55555555_44444444_light_yellow',
|
||||
'event.beoremote_one_55555555_44444444_light_function_1',
|
||||
'event.beoremote_one_55555555_44444444_light_function_2',
|
||||
'event.beoremote_one_55555555_44444444_light_function_3',
|
||||
'event.beoremote_one_55555555_44444444_light_function_4',
|
||||
'event.beoremote_one_55555555_44444444_light_function_5',
|
||||
'event.beoremote_one_55555555_44444444_light_function_6',
|
||||
'event.beoremote_one_55555555_44444444_light_function_7',
|
||||
'event.beoremote_one_55555555_44444444_light_function_8',
|
||||
'event.beoremote_one_55555555_44444444_light_function_9',
|
||||
'event.beoremote_one_55555555_44444444_light_function_10',
|
||||
'event.beoremote_one_55555555_44444444_light_function_11',
|
||||
'event.beoremote_one_55555555_44444444_light_function_12',
|
||||
'event.beoremote_one_55555555_44444444_light_function_13',
|
||||
'event.beoremote_one_55555555_44444444_light_function_14',
|
||||
'event.beoremote_one_55555555_44444444_light_function_15',
|
||||
'event.beoremote_one_55555555_44444444_light_function_16',
|
||||
'event.beoremote_one_55555555_44444444_light_function_17',
|
||||
'event.beoremote_one_55555555_44444444_control_blue',
|
||||
'event.beoremote_one_55555555_44444444_control_digit_0',
|
||||
'event.beoremote_one_55555555_44444444_control_digit_1',
|
||||
'event.beoremote_one_55555555_44444444_control_digit_2',
|
||||
'event.beoremote_one_55555555_44444444_control_digit_3',
|
||||
'event.beoremote_one_55555555_44444444_control_digit_4',
|
||||
'event.beoremote_one_55555555_44444444_control_digit_5',
|
||||
'event.beoremote_one_55555555_44444444_control_digit_6',
|
||||
'event.beoremote_one_55555555_44444444_control_digit_7',
|
||||
'event.beoremote_one_55555555_44444444_control_digit_8',
|
||||
'event.beoremote_one_55555555_44444444_control_digit_9',
|
||||
'event.beoremote_one_55555555_44444444_control_down',
|
||||
'event.beoremote_one_55555555_44444444_control_green',
|
||||
'event.beoremote_one_55555555_44444444_control_left',
|
||||
'event.beoremote_one_55555555_44444444_control_play',
|
||||
'event.beoremote_one_55555555_44444444_control_red',
|
||||
'event.beoremote_one_55555555_44444444_control_rewind',
|
||||
'event.beoremote_one_55555555_44444444_control_right',
|
||||
'event.beoremote_one_55555555_44444444_control_select',
|
||||
'event.beoremote_one_55555555_44444444_control_stop',
|
||||
'event.beoremote_one_55555555_44444444_control_up',
|
||||
'event.beoremote_one_55555555_44444444_control_wind',
|
||||
'event.beoremote_one_55555555_44444444_control_yellow',
|
||||
'event.beoremote_one_55555555_44444444_control_function_1',
|
||||
'event.beoremote_one_55555555_44444444_control_function_2',
|
||||
'event.beoremote_one_55555555_44444444_control_function_3',
|
||||
'event.beoremote_one_55555555_44444444_control_function_4',
|
||||
'event.beoremote_one_55555555_44444444_control_function_5',
|
||||
'event.beoremote_one_55555555_44444444_control_function_6',
|
||||
'event.beoremote_one_55555555_44444444_control_function_7',
|
||||
'event.beoremote_one_55555555_44444444_control_function_8',
|
||||
'event.beoremote_one_55555555_44444444_control_function_9',
|
||||
'event.beoremote_one_55555555_44444444_control_function_10',
|
||||
'event.beoremote_one_55555555_44444444_control_function_11',
|
||||
'event.beoremote_one_55555555_44444444_control_function_12',
|
||||
'event.beoremote_one_55555555_44444444_control_function_13',
|
||||
'event.beoremote_one_55555555_44444444_control_function_14',
|
||||
'event.beoremote_one_55555555_44444444_control_function_15',
|
||||
'event.beoremote_one_55555555_44444444_control_function_16',
|
||||
'event.beoremote_one_55555555_44444444_control_function_17',
|
||||
'event.beoremote_one_55555555_44444444_control_function_18',
|
||||
'event.beoremote_one_55555555_44444444_control_function_19',
|
||||
'event.beoremote_one_55555555_44444444_control_function_20',
|
||||
'event.beoremote_one_55555555_44444444_control_function_21',
|
||||
'event.beoremote_one_55555555_44444444_control_function_22',
|
||||
'event.beoremote_one_55555555_44444444_control_function_23',
|
||||
'event.beoremote_one_55555555_44444444_control_function_24',
|
||||
'event.beoremote_one_55555555_44444444_control_function_25',
|
||||
'event.beoremote_one_55555555_44444444_control_function_26',
|
||||
'event.beoremote_one_55555555_44444444_control_function_27',
|
||||
'media_player.beosound_a5_44444444',
|
||||
])
|
||||
# ---
|
||||
# name: test_button_event_creation_balance
|
||||
list([
|
||||
'event.beosound_balance_11111111_bluetooth',
|
||||
@@ -104,9 +208,8 @@
|
||||
'media_player.beosound_balance_11111111',
|
||||
])
|
||||
# ---
|
||||
# name: test_button_event_creation_beosound_premiere
|
||||
# name: test_button_event_creation_premiere
|
||||
list([
|
||||
'event.beosound_premiere_33333333_microphone',
|
||||
'event.beosound_premiere_33333333_next',
|
||||
'event.beosound_premiere_33333333_play_pause',
|
||||
'event.beosound_premiere_33333333_favorite_1',
|
||||
@@ -208,7 +311,7 @@
|
||||
'media_player.beosound_premiere_33333333',
|
||||
])
|
||||
# ---
|
||||
# name: test_no_button_and_remote_key_event_creation
|
||||
# name: test_no_button_and_remote_key_event_creation_core
|
||||
list([
|
||||
'media_player.beoconnect_core_22222222',
|
||||
])
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -53,11 +53,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -102,11 +102,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -151,11 +151,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -200,11 +200,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -249,11 +249,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -297,11 +297,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -345,11 +345,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -393,11 +393,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -441,11 +441,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -489,11 +489,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -537,11 +537,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -585,11 +585,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -634,11 +634,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.22222222@products.bang-olufsen.com',
|
||||
@@ -683,11 +683,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -732,11 +732,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.22222222@products.bang-olufsen.com',
|
||||
@@ -781,11 +781,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -831,11 +831,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.22222222@products.bang-olufsen.com',
|
||||
@@ -880,11 +880,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -929,11 +929,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.22222222@products.bang-olufsen.com',
|
||||
@@ -978,11 +978,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -1029,7 +1029,7 @@
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
@@ -1072,11 +1072,11 @@
|
||||
<BeoAttribute.BEOLINK: 'beolink'>: dict({
|
||||
<BeoAttribute.BEOLINK_LISTENERS: 'listeners'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_PEERS: 'peers'>: dict({
|
||||
'Bedroom Premiere': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
'Lounge room A5': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
<BeoAttribute.BEOLINK_SELF: 'self'>: dict({
|
||||
'Living room Balance': '1111.1111111.22222222@products.bang-olufsen.com',
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from mozart_api.models import BeoRemoteButton, ButtonEvent, PairedRemoteResponse
|
||||
from pytest_unordered import unordered
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.bang_olufsen.const import (
|
||||
@@ -20,37 +21,64 @@ from .const import (
|
||||
TEST_BUTTON_EVENT_ENTITY_ID,
|
||||
TEST_REMOTE_KEY_EVENT_ENTITY_ID,
|
||||
TEST_SERIAL_NUMBER_3,
|
||||
TEST_SERIAL_NUMBER_4,
|
||||
)
|
||||
from .util import (
|
||||
get_a5_entity_ids,
|
||||
get_balance_entity_ids,
|
||||
get_core_entity_ids,
|
||||
get_premiere_entity_ids,
|
||||
get_remote_entity_ids,
|
||||
)
|
||||
from .util import get_button_entity_ids, get_remote_entity_ids
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_button_event_creation_balance(
|
||||
async def _check_button_event_creation(
|
||||
hass: HomeAssistant,
|
||||
integration: None,
|
||||
entity_registry: EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
config_entry: MockConfigEntry,
|
||||
client: AsyncMock,
|
||||
entity_ids: list[str],
|
||||
) -> None:
|
||||
"""Test button event entities are created when using a Balance (Most devices support all buttons like the Balance)."""
|
||||
|
||||
# Add Button Event entity ids
|
||||
entity_ids: list[str] = [*get_button_entity_ids(), *get_remote_entity_ids()]
|
||||
"""Test body for entity creation tests."""
|
||||
# Load entry
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await mock_websocket_connection(hass, client)
|
||||
|
||||
# Check that the entities are available
|
||||
for entity_id in entity_ids:
|
||||
assert entity_registry.async_get(entity_id)
|
||||
|
||||
# Check number of entities
|
||||
# The media_player entity and all of the button event entities should be the only available
|
||||
# Check that no entities other than the expected have been created
|
||||
entity_ids_available = list(entity_registry.entities.keys())
|
||||
assert len(entity_ids_available) == 1 + len(entity_ids)
|
||||
|
||||
# Check snapshot
|
||||
assert entity_ids_available == unordered(entity_ids)
|
||||
assert entity_ids_available == snapshot
|
||||
|
||||
|
||||
async def test_no_button_and_remote_key_event_creation(
|
||||
async def test_button_event_creation_balance(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_mozart_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test button event entities are created when using a Balance (Most devices support all buttons like the Balance)."""
|
||||
|
||||
await _check_button_event_creation(
|
||||
hass,
|
||||
entity_registry,
|
||||
snapshot,
|
||||
mock_config_entry,
|
||||
mock_mozart_client,
|
||||
[*get_balance_entity_ids(), *get_remote_entity_ids()],
|
||||
)
|
||||
|
||||
|
||||
async def test_no_button_and_remote_key_event_creation_core(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry_core: MockConfigEntry,
|
||||
mock_mozart_client: AsyncMock,
|
||||
@@ -62,51 +90,58 @@ async def test_no_button_and_remote_key_event_creation(
|
||||
items=[]
|
||||
)
|
||||
|
||||
# Load entry
|
||||
mock_config_entry_core.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry_core.entry_id)
|
||||
await mock_websocket_connection(hass, mock_mozart_client)
|
||||
|
||||
# Check number of entities
|
||||
# The media_player entity should be the only available
|
||||
entity_ids_available = list(entity_registry.entities.keys())
|
||||
assert len(entity_ids_available) == 1
|
||||
|
||||
# Check snapshot
|
||||
assert entity_ids_available == snapshot
|
||||
await _check_button_event_creation(
|
||||
hass,
|
||||
entity_registry,
|
||||
snapshot,
|
||||
mock_config_entry_core,
|
||||
mock_mozart_client,
|
||||
get_core_entity_ids(),
|
||||
)
|
||||
|
||||
|
||||
async def test_button_event_creation_beosound_premiere(
|
||||
async def test_button_event_creation_premiere(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry_premiere: MockConfigEntry,
|
||||
mock_mozart_client: AsyncMock,
|
||||
entity_registry: EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test Bluetooth button event entity is not created when using a Beosound Premiere."""
|
||||
"""Test Bluetooth and Microphone button event entities are not created when using a Beosound Premiere."""
|
||||
|
||||
# Load entry
|
||||
mock_config_entry_premiere.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry_premiere.entry_id)
|
||||
await mock_websocket_connection(hass, mock_mozart_client)
|
||||
await _check_button_event_creation(
|
||||
hass,
|
||||
entity_registry,
|
||||
snapshot,
|
||||
mock_config_entry_premiere,
|
||||
mock_mozart_client,
|
||||
[
|
||||
*get_premiere_entity_ids(),
|
||||
*get_remote_entity_ids(device_serial=TEST_SERIAL_NUMBER_3),
|
||||
],
|
||||
)
|
||||
|
||||
# Add Button Event entity ids
|
||||
entity_ids = [
|
||||
*get_button_entity_ids("beosound_premiere_33333333"),
|
||||
*get_remote_entity_ids(device_serial=TEST_SERIAL_NUMBER_3),
|
||||
]
|
||||
entity_ids.remove("event.beosound_premiere_33333333_bluetooth")
|
||||
|
||||
# Check that the entities are available
|
||||
for entity_id in entity_ids:
|
||||
assert entity_registry.async_get(entity_id)
|
||||
async def test_button_event_creation_a5(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry_a5: MockConfigEntry,
|
||||
mock_mozart_client: AsyncMock,
|
||||
entity_registry: EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test Microphone button event entity is not created when using a Beosound A5."""
|
||||
|
||||
# Check number of entities
|
||||
# The media_player entity and all of the button event entities (except Bluetooth) should be the only available
|
||||
entity_ids_available = list(entity_registry.entities.keys())
|
||||
assert len(entity_ids_available) == 1 + len(entity_ids)
|
||||
|
||||
assert entity_ids_available == snapshot
|
||||
await _check_button_event_creation(
|
||||
hass,
|
||||
entity_registry,
|
||||
snapshot,
|
||||
mock_config_entry_a5,
|
||||
mock_mozart_client,
|
||||
[
|
||||
*get_a5_entity_ids(),
|
||||
*get_remote_entity_ids(device_serial=TEST_SERIAL_NUMBER_4),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
async def test_button(
|
||||
|
||||
@@ -10,6 +10,7 @@ from mozart_api.models import (
|
||||
WebsocketNotificationTag,
|
||||
)
|
||||
import pytest
|
||||
from pytest_unordered import unordered
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.bang_olufsen.const import (
|
||||
@@ -29,7 +30,7 @@ from .const import (
|
||||
TEST_REMOTE_SERIAL_PAIRED,
|
||||
TEST_SERIAL_NUMBER,
|
||||
)
|
||||
from .util import get_button_entity_ids, get_remote_entity_ids
|
||||
from .util import get_balance_entity_ids, get_remote_entity_ids
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@@ -133,9 +134,8 @@ async def test_on_remote_control_already_added(
|
||||
assert device_registry.async_get_device({(DOMAIN, TEST_REMOTE_SERIAL_PAIRED)})
|
||||
|
||||
# Check number of entities (remote and button events and media_player)
|
||||
assert (
|
||||
len(list(entity_registry.entities.keys()))
|
||||
== len(get_remote_entity_ids()) + len(get_button_entity_ids()) + 1
|
||||
assert list(entity_registry.entities.keys()) == unordered(
|
||||
[*get_balance_entity_ids(), *get_remote_entity_ids()]
|
||||
)
|
||||
remote_callback = mock_mozart_client.get_notification_notifications.call_args[0][0]
|
||||
|
||||
@@ -152,12 +152,11 @@ async def test_on_remote_control_already_added(
|
||||
assert mock_mozart_client.get_bluetooth_remotes.call_count == 2
|
||||
assert device_registry.async_get_device({(DOMAIN, TEST_REMOTE_SERIAL_PAIRED)})
|
||||
|
||||
# Check number of entities
|
||||
# Check number of entities (remote and button events and media_player)
|
||||
entity_ids_available = list(entity_registry.entities.keys())
|
||||
|
||||
assert (
|
||||
len(entity_ids_available)
|
||||
== len(get_remote_entity_ids()) + len(get_button_entity_ids()) + 1
|
||||
assert list(entity_registry.entities.keys()) == unordered(
|
||||
[*get_balance_entity_ids(), *get_remote_entity_ids()]
|
||||
)
|
||||
assert entity_ids_available == snapshot
|
||||
|
||||
@@ -180,10 +179,9 @@ async def test_on_remote_control_paired(
|
||||
assert mock_mozart_client.get_bluetooth_remotes.call_count == 1
|
||||
assert device_registry.async_get_device({(DOMAIN, TEST_REMOTE_SERIAL_PAIRED)})
|
||||
|
||||
# Check number of entities (button events and media_player)
|
||||
assert (
|
||||
len(list(entity_registry.entities.keys()))
|
||||
== len(get_remote_entity_ids()) + len(get_button_entity_ids()) + 1
|
||||
# Check number of entities (button and remote events and media_player)
|
||||
assert list(entity_registry.entities.keys()) == unordered(
|
||||
[*get_balance_entity_ids(), *get_remote_entity_ids()]
|
||||
)
|
||||
# "Pair" a new remote
|
||||
mock_mozart_client.get_bluetooth_remotes.return_value = PairedRemoteResponse(
|
||||
@@ -234,12 +232,12 @@ async def test_on_remote_control_paired(
|
||||
# Check number of entities (remote and button events and media_player)
|
||||
entity_ids_available = list(entity_registry.entities.keys())
|
||||
|
||||
assert (
|
||||
len(entity_ids_available)
|
||||
== len(get_remote_entity_ids())
|
||||
+ len(get_remote_entity_ids())
|
||||
+ len(get_button_entity_ids())
|
||||
+ 1
|
||||
assert entity_ids_available == unordered(
|
||||
[
|
||||
*get_balance_entity_ids(),
|
||||
*get_remote_entity_ids(),
|
||||
*get_remote_entity_ids("66666666"),
|
||||
]
|
||||
)
|
||||
assert entity_ids_available == snapshot
|
||||
|
||||
@@ -262,11 +260,11 @@ async def test_on_remote_control_unpaired(
|
||||
assert mock_mozart_client.get_bluetooth_remotes.call_count == 1
|
||||
assert device_registry.async_get_device({(DOMAIN, TEST_REMOTE_SERIAL_PAIRED)})
|
||||
|
||||
# Check number of entities (button events and media_player)
|
||||
assert (
|
||||
len(list(entity_registry.entities.keys()))
|
||||
== len(get_remote_entity_ids()) + len(get_button_entity_ids()) + 1
|
||||
# Check number of entities (button and remote events and media_player)
|
||||
assert list(entity_registry.entities.keys()) == unordered(
|
||||
[*get_balance_entity_ids(), *get_remote_entity_ids()]
|
||||
)
|
||||
|
||||
# "Unpair" the remote
|
||||
mock_mozart_client.get_bluetooth_remotes.return_value = PairedRemoteResponse(
|
||||
items=[]
|
||||
@@ -296,7 +294,7 @@ async def test_on_remote_control_unpaired(
|
||||
# Check number of entities (button events and media_player)
|
||||
entity_ids_available = list(entity_registry.entities.keys())
|
||||
|
||||
assert len(entity_ids_available) == +len(get_button_entity_ids()) + 1
|
||||
assert entity_ids_available == unordered(get_balance_entity_ids())
|
||||
assert entity_ids_available == snapshot
|
||||
|
||||
|
||||
|
||||
@@ -10,17 +10,58 @@ from homeassistant.components.bang_olufsen.const import (
|
||||
DEVICE_BUTTONS,
|
||||
)
|
||||
|
||||
from .const import TEST_REMOTE_SERIAL, TEST_SERIAL_NUMBER
|
||||
from .const import (
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID,
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID_2,
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID_3,
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID_4,
|
||||
TEST_REMOTE_SERIAL,
|
||||
TEST_SERIAL_NUMBER,
|
||||
)
|
||||
|
||||
|
||||
def get_button_entity_ids(id_prefix: str = "beosound_balance_11111111") -> list[str]:
|
||||
"""Return a list of button entity_ids that Mozart devices (except Beoconnect Core and Beosound Premiere) provides."""
|
||||
def _get_button_entity_ids(id_prefix: str = "beosound_balance_11111111") -> list[str]:
|
||||
"""Return a list of button entity_ids that Mozart devices provide.
|
||||
|
||||
Beoconnect Core, Beosound A5, Beosound A9 and Beosound Premiere do not have (all of the) physical buttons and need filtering.
|
||||
"""
|
||||
return [
|
||||
f"event.{id_prefix}_{underscore(button_type)}".replace("preset", "favorite_")
|
||||
for button_type in DEVICE_BUTTONS
|
||||
]
|
||||
|
||||
|
||||
def get_balance_entity_ids() -> list[str]:
|
||||
"""Return a list of entity_ids that a Beosound Balance provides."""
|
||||
return [TEST_MEDIA_PLAYER_ENTITY_ID, *_get_button_entity_ids()]
|
||||
|
||||
|
||||
def get_premiere_entity_ids() -> list[str]:
|
||||
"""Return a list of entity_ids that a Beosound Premiere provides."""
|
||||
buttons = [
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID_3,
|
||||
*_get_button_entity_ids("beosound_premiere_33333333"),
|
||||
]
|
||||
buttons.remove("event.beosound_premiere_33333333_bluetooth")
|
||||
buttons.remove("event.beosound_premiere_33333333_microphone")
|
||||
return buttons
|
||||
|
||||
|
||||
def get_a5_entity_ids() -> list[str]:
|
||||
"""Return a list of entity_ids that a Beosound A5 provides."""
|
||||
buttons = [
|
||||
TEST_MEDIA_PLAYER_ENTITY_ID_4,
|
||||
*_get_button_entity_ids("beosound_a5_44444444"),
|
||||
]
|
||||
buttons.remove("event.beosound_a5_44444444_microphone")
|
||||
return buttons
|
||||
|
||||
|
||||
def get_core_entity_ids() -> list[str]:
|
||||
"""Return a list of entity_ids that a Beoconnect core provides."""
|
||||
return [TEST_MEDIA_PLAYER_ENTITY_ID_2]
|
||||
|
||||
|
||||
def get_remote_entity_ids(
|
||||
remote_serial: str = TEST_REMOTE_SERIAL, device_serial: str = TEST_SERIAL_NUMBER
|
||||
) -> list[str]:
|
||||
|
||||
@@ -82,6 +82,10 @@ def patch_doorbird_api_entry_points(api: MagicMock) -> Generator[DoorBird]:
|
||||
"homeassistant.components.doorbird.config_flow.DoorBird",
|
||||
return_value=api,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.doorbird.device.get_url",
|
||||
return_value="http://127.0.0.1:8123",
|
||||
),
|
||||
):
|
||||
yield api
|
||||
|
||||
|
||||
@@ -2,15 +2,141 @@
|
||||
|
||||
from copy import deepcopy
|
||||
from http import HTTPStatus
|
||||
from typing import Any
|
||||
|
||||
from doorbirdpy import DoorBirdScheduleEntry
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.doorbird.const import CONF_EVENTS
|
||||
from homeassistant.components.doorbird.const import (
|
||||
CONF_EVENTS,
|
||||
DEFAULT_DOORBELL_EVENT,
|
||||
DEFAULT_MOTION_EVENT,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import VALID_CONFIG
|
||||
from .conftest import DoorbirdMockerType
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def doorbird_favorites_with_stale() -> dict[str, dict[str, Any]]:
|
||||
"""Return favorites fixture with stale favorites from another HA instance.
|
||||
|
||||
Creates favorites where identifier "2" has the same event name as "0"
|
||||
(mydoorbird_doorbell) but points to a different HA instance URL.
|
||||
These stale favorites should be filtered out.
|
||||
"""
|
||||
return {
|
||||
"http": {
|
||||
"0": {
|
||||
"title": "Home Assistant (mydoorbird_doorbell)",
|
||||
"value": "http://127.0.0.1:8123/api/doorbird/mydoorbird_doorbell?token=test-token",
|
||||
},
|
||||
# Stale favorite from a different HA instance - should be filtered out
|
||||
"2": {
|
||||
"title": "Home Assistant (mydoorbird_doorbell)",
|
||||
"value": "http://old-ha-instance:8123/api/doorbird/mydoorbird_doorbell?token=old-token",
|
||||
},
|
||||
"5": {
|
||||
"title": "Home Assistant (mydoorbird_motion)",
|
||||
"value": "http://127.0.0.1:8123/api/doorbird/mydoorbird_motion?token=test-token",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def doorbird_schedule_with_stale() -> list[DoorBirdScheduleEntry]:
|
||||
"""Return schedule fixture with outputs referencing stale favorites.
|
||||
|
||||
Both param "0" and "2" map to doorbell input, but "2" is a stale favorite.
|
||||
"""
|
||||
schedule_data = [
|
||||
{
|
||||
"input": "doorbell",
|
||||
"param": "1",
|
||||
"output": [
|
||||
{
|
||||
"event": "http",
|
||||
"param": "0",
|
||||
"schedule": {"weekdays": [{"to": "107999", "from": "108000"}]},
|
||||
},
|
||||
{
|
||||
"event": "http",
|
||||
"param": "2",
|
||||
"schedule": {"weekdays": [{"to": "107999", "from": "108000"}]},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"input": "motion",
|
||||
"param": "",
|
||||
"output": [
|
||||
{
|
||||
"event": "http",
|
||||
"param": "5",
|
||||
"schedule": {"weekdays": [{"to": "107999", "from": "108000"}]},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
return DoorBirdScheduleEntry.parse_all(schedule_data)
|
||||
|
||||
|
||||
async def test_stale_favorites_filtered_by_url(
|
||||
hass: HomeAssistant,
|
||||
doorbird_mocker: DoorbirdMockerType,
|
||||
doorbird_favorites_with_stale: dict[str, dict[str, Any]],
|
||||
doorbird_schedule_with_stale: list[DoorBirdScheduleEntry],
|
||||
) -> None:
|
||||
"""Test that stale favorites from other HA instances are filtered out."""
|
||||
await doorbird_mocker(
|
||||
favorites=doorbird_favorites_with_stale,
|
||||
schedule=doorbird_schedule_with_stale,
|
||||
)
|
||||
# Should have 2 event entities - stale favorite "2" is filtered out
|
||||
# because its URL doesn't match the current HA instance
|
||||
event_entities = hass.states.async_all("event")
|
||||
assert len(event_entities) == 2
|
||||
|
||||
|
||||
async def test_custom_url_used_for_favorites(
|
||||
hass: HomeAssistant,
|
||||
doorbird_mocker: DoorbirdMockerType,
|
||||
) -> None:
|
||||
"""Test that custom URL override is used instead of get_url."""
|
||||
custom_url = "https://my-custom-url.example.com:8443"
|
||||
favorites = {
|
||||
"http": {
|
||||
"1": {
|
||||
"title": "Home Assistant (mydoorbird_doorbell)",
|
||||
"value": f"{custom_url}/api/doorbird/mydoorbird_doorbell?token=test-token",
|
||||
},
|
||||
"2": {
|
||||
"title": "Home Assistant (mydoorbird_motion)",
|
||||
"value": f"{custom_url}/api/doorbird/mydoorbird_motion?token=test-token",
|
||||
},
|
||||
}
|
||||
}
|
||||
config_with_custom_url = {
|
||||
**VALID_CONFIG,
|
||||
"hass_url_override": custom_url,
|
||||
}
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="1CCAE3AAAAAA",
|
||||
data=config_with_custom_url,
|
||||
options={CONF_EVENTS: [DEFAULT_DOORBELL_EVENT, DEFAULT_MOTION_EVENT]},
|
||||
)
|
||||
await doorbird_mocker(entry=entry, favorites=favorites)
|
||||
|
||||
# Should have 2 event entities using the custom URL
|
||||
event_entities = hass.states.async_all("event")
|
||||
assert len(event_entities) == 2
|
||||
|
||||
|
||||
async def test_no_configured_events(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -2345,19 +2345,20 @@ async def test_effect_template(
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("expected_min_mireds", "attribute_template"),
|
||||
("expected_min_mireds", "expected_max_kelvin", "attribute_template"),
|
||||
[
|
||||
(118, "{{118}}"),
|
||||
(153, "{{x - 12}}"),
|
||||
(153, "None"),
|
||||
(153, "{{ none }}"),
|
||||
(153, ""),
|
||||
(153, "{{ 'a' }}"),
|
||||
(118, 8474, "{{118}}"),
|
||||
(153, 6535, "{{x - 12}}"),
|
||||
(153, 6535, "None"),
|
||||
(153, 6535, "{{ none }}"),
|
||||
(153, 6535, ""),
|
||||
(153, 6535, "{{ 'a' }}"),
|
||||
],
|
||||
)
|
||||
async def test_min_mireds_template(
|
||||
hass: HomeAssistant,
|
||||
expected_min_mireds,
|
||||
expected_min_mireds: int,
|
||||
expected_max_kelvin: int,
|
||||
style: ConfigurationStyle,
|
||||
setup_light_with_mireds,
|
||||
) -> None:
|
||||
@@ -2369,6 +2370,7 @@ async def test_min_mireds_template(
|
||||
state = hass.states.get("light.test_template_light")
|
||||
assert state is not None
|
||||
assert state.attributes.get("min_mireds") == expected_min_mireds
|
||||
assert state.attributes.get("max_color_temp_kelvin") == expected_max_kelvin
|
||||
|
||||
|
||||
@pytest.mark.parametrize("count", [1])
|
||||
@@ -2381,19 +2383,20 @@ async def test_min_mireds_template(
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("expected_max_mireds", "attribute_template"),
|
||||
("expected_max_mireds", "expected_min_kelvin", "attribute_template"),
|
||||
[
|
||||
(488, "{{488}}"),
|
||||
(500, "{{x - 12}}"),
|
||||
(500, "None"),
|
||||
(500, "{{ none }}"),
|
||||
(500, ""),
|
||||
(500, "{{ 'a' }}"),
|
||||
(488, 2049, "{{488}}"),
|
||||
(500, 2000, "{{x - 12}}"),
|
||||
(500, 2000, "None"),
|
||||
(500, 2000, "{{ none }}"),
|
||||
(500, 2000, ""),
|
||||
(500, 2000, "{{ 'a' }}"),
|
||||
],
|
||||
)
|
||||
async def test_max_mireds_template(
|
||||
hass: HomeAssistant,
|
||||
expected_max_mireds,
|
||||
expected_max_mireds: int,
|
||||
expected_min_kelvin: int,
|
||||
style: ConfigurationStyle,
|
||||
setup_light_with_mireds,
|
||||
) -> None:
|
||||
@@ -2405,6 +2408,7 @@ async def test_max_mireds_template(
|
||||
state = hass.states.get("light.test_template_light")
|
||||
assert state is not None
|
||||
assert state.attributes.get("max_mireds") == expected_max_mireds
|
||||
assert state.attributes.get("min_color_temp_kelvin") == expected_min_kelvin
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'light',
|
||||
'entity_category': None,
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'light.device_with_led_led',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
|
||||
@@ -37,7 +37,6 @@ from homeassistant.helpers.automation import move_top_level_schema_fields_to_opt
|
||||
from homeassistant.helpers.condition import (
|
||||
Condition,
|
||||
ConditionCheckerType,
|
||||
ConditionConfig,
|
||||
async_validate_condition_config,
|
||||
)
|
||||
from homeassistant.helpers.template import Template
|
||||
@@ -2124,9 +2123,6 @@ async def test_platform_multiple_conditions(hass: HomeAssistant) -> None:
|
||||
"""Validate config."""
|
||||
return config
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
|
||||
"""Initialize condition."""
|
||||
|
||||
class MockCondition1(MockCondition):
|
||||
"""Mock condition 1."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user