Compare commits

..

17 Commits

Author SHA1 Message Date
hanwg
98e918cd8a Improve polling error messages for Telegram bot (#160675) 2026-01-11 06:54:50 +01:00
J. Nick Koston
1efc87bfef Bump easyenergy to 2.2.0 (#160709) 2026-01-10 18:54:50 -10:00
Simon Delberghe
b4360ccbd9 Move condition to prioritize preset mode (eco/comfort...) instead of program name in Overkiz (#160189) 2026-01-10 23:58:19 +01:00
Ernst Klamer
ce234d69a7 Revert bthome-ble back to 3.16.0 to fix missing data (#160694) 2026-01-10 09:47:30 -10:00
Álvaro Fernández Rojas
b2a198e230 Update aioairzone to v1.0.5 (#160688) 2026-01-10 20:43:10 +01:00
Michael Hansen
538009d2df Bump pysilero-vad to 3.2.0 (#160691) 2026-01-10 13:35:46 -06:00
Clifford Roche
99329851a2 Bump greeclimate to 2.1.1 (#160683) 2026-01-10 19:51:04 +01:00
DeerMaximum
f8ec395e96 Use snapshots for binary sensor tests in Nina (#160532) 2026-01-10 17:47:29 +01:00
mettolen
98fe189edf Add recalibrate CO2 button to Airobot (#160679) 2026-01-10 17:37:14 +01:00
Samuel Xiao
7b413e3fd3 Bumb switchbot api to v2.10.0 (#160657) 2026-01-10 13:01:55 +01:00
Paul Tarjan
00ca5473d4 Bump pyhik to 0.4.0 (#160654) 2026-01-10 08:04:29 +01:00
Martin Hjelmare
33c808713e Fix Z-Wave creating notification binary sensor for idle state (#160604) 2026-01-10 02:43:13 +01:00
Sid
c97437fbf3 Add the professionel5e filter series to eheimdigital (#155550) 2026-01-09 21:24:01 +01:00
Jordan Harvey
ad8f14fec1 Bump pynintendoparental to 2.3.2 (#160626)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-01-09 20:09:31 +01:00
karwosts
7df586eff1 Use duration selector for timer service (#160391) 2026-01-09 20:07:32 +01:00
Manu
f6fa95d2f7 Rename Namecheap FreeDNS to Dynamic DNS (#160625) 2026-01-09 19:37:03 +01:00
Tero Paloheimo
23a8300012 Add Ruuvi IAQS to Ruuvi BLE (#160529) 2026-01-09 19:04:30 +01:00
59 changed files with 2309 additions and 632 deletions

View File

@@ -43,6 +43,13 @@ BUTTON_TYPES: tuple[AirobotButtonEntityDescription, ...] = (
entity_category=EntityCategory.CONFIG,
press_fn=lambda coordinator: coordinator.client.reboot_thermostat(),
),
AirobotButtonEntityDescription(
key="recalibrate_co2",
translation_key="recalibrate_co2",
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
press_fn=lambda coordinator: coordinator.client.recalibrate_co2_sensor(),
),
)

View File

@@ -1,5 +1,10 @@
{
"entity": {
"button": {
"recalibrate_co2": {
"default": "mdi:molecule-co2"
}
},
"number": {
"hysteresis_band": {
"default": "mdi:delta"

View File

@@ -59,6 +59,11 @@
}
},
"entity": {
"button": {
"recalibrate_co2": {
"name": "Recalibrate CO2 sensor"
}
},
"number": {
"hysteresis_band": {
"name": "Hysteresis band"

View File

@@ -12,5 +12,5 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["aioairzone"],
"requirements": ["aioairzone==1.0.4"]
"requirements": ["aioairzone==1.0.5"]
}

View File

@@ -8,5 +8,5 @@
"integration_type": "system",
"iot_class": "local_push",
"quality_scale": "internal",
"requirements": ["pysilero-vad==3.1.0", "pyspeex-noise==1.0.2"]
"requirements": ["pysilero-vad==3.2.0", "pyspeex-noise==1.0.2"]
}

View File

@@ -20,5 +20,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/bthome",
"iot_class": "local_push",
"requirements": ["bthome-ble==3.17.0"]
"requirements": ["bthome-ble==3.16.0"]
}

View File

@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/easyenergy",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["easyenergy==2.1.2"],
"requirements": ["easyenergy==2.2.0"],
"single_config_entry": true
}

View File

@@ -37,7 +37,7 @@ class EheimDigitalEntity[_DeviceT: EheimDigitalDevice](
name=device.name,
connections={(CONNECTION_NETWORK_MAC, device.mac_address)},
manufacturer="EHEIM",
model=device.device_type.model_name,
model=device.model_name,
identifiers={(DOMAIN, device.mac_address)},
suggested_area=device.aquarium_name,
sw_version=device.sw_version,
@@ -59,9 +59,9 @@ class EheimDigitalEntity[_DeviceT: EheimDigitalDevice](
def exception_handler[_EntityT: EheimDigitalEntity[EheimDigitalDevice], **_P](
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
"""Decorate AirGradient calls to handle exceptions.
"""Decorate eheimdigital calls to handle exceptions.
A decorator that wraps the passed in function, catches AirGradient errors.
A decorator that wraps the passed in function, catches eheimdigital errors.
"""
async def handler(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:

View File

@@ -6,6 +6,7 @@ from typing import Any, override
from eheimdigital.classic_vario import EheimDigitalClassicVario
from eheimdigital.device import EheimDigitalDevice
from eheimdigital.filter import EheimDigitalFilter
from eheimdigital.heater import EheimDigitalHeater
from eheimdigital.types import HeaterUnit
@@ -21,6 +22,7 @@ from homeassistant.const import (
PRECISION_WHOLE,
EntityCategory,
UnitOfTemperature,
UnitOfTime,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -42,6 +44,34 @@ class EheimDigitalNumberDescription[_DeviceT: EheimDigitalDevice](
uom_fn: Callable[[_DeviceT], str] | None = None
FILTER_DESCRIPTIONS: tuple[EheimDigitalNumberDescription[EheimDigitalFilter], ...] = (
EheimDigitalNumberDescription[EheimDigitalFilter](
key="high_pulse_time",
translation_key="high_pulse_time",
entity_category=EntityCategory.CONFIG,
native_step=PRECISION_WHOLE,
native_unit_of_measurement=UnitOfTime.SECONDS,
device_class=NumberDeviceClass.DURATION,
native_min_value=5,
native_max_value=200000,
value_fn=lambda device: device.high_pulse_time,
set_value_fn=lambda device, value: device.set_high_pulse_time(int(value)),
),
EheimDigitalNumberDescription[EheimDigitalFilter](
key="low_pulse_time",
translation_key="low_pulse_time",
entity_category=EntityCategory.CONFIG,
native_step=PRECISION_WHOLE,
native_unit_of_measurement=UnitOfTime.SECONDS,
device_class=NumberDeviceClass.DURATION,
native_min_value=5,
native_max_value=200000,
value_fn=lambda device: device.low_pulse_time,
set_value_fn=lambda device, value: device.set_low_pulse_time(int(value)),
),
)
CLASSICVARIO_DESCRIPTIONS: tuple[
EheimDigitalNumberDescription[EheimDigitalClassicVario], ...
] = (
@@ -145,6 +175,13 @@ async def async_setup_entry(
)
for description in CLASSICVARIO_DESCRIPTIONS
)
if isinstance(device, EheimDigitalFilter):
entities.extend(
EheimDigitalNumber[EheimDigitalFilter](
coordinator, device, description
)
for description in FILTER_DESCRIPTIONS
)
if isinstance(device, EheimDigitalHeater):
entities.extend(
EheimDigitalNumber[EheimDigitalHeater](

View File

@@ -2,13 +2,19 @@
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any, override
from typing import Any, Literal, override
from eheimdigital.classic_vario import EheimDigitalClassicVario
from eheimdigital.device import EheimDigitalDevice
from eheimdigital.types import FilterMode
from eheimdigital.filter import EheimDigitalFilter
from eheimdigital.types import (
FilterMode,
FilterModeProf,
UnitOfMeasurement as EheimDigitalUnitOfMeasurement,
)
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import EntityCategory, UnitOfFrequency, UnitOfVolumeFlowRate
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -24,8 +30,109 @@ class EheimDigitalSelectDescription[_DeviceT: EheimDigitalDevice](
):
"""Class describing EHEIM Digital select entities."""
options_fn: Callable[[_DeviceT], list[str]] | None = None
use_api_unit: Literal[True] | None = None
value_fn: Callable[[_DeviceT], str | None]
set_value_fn: Callable[[_DeviceT, str], Awaitable[None]]
set_value_fn: Callable[[_DeviceT, str], Awaitable[None] | None]
FILTER_DESCRIPTIONS: tuple[EheimDigitalSelectDescription[EheimDigitalFilter], ...] = (
EheimDigitalSelectDescription[EheimDigitalFilter](
key="filter_mode",
translation_key="filter_mode",
entity_category=EntityCategory.CONFIG,
options=[item.lower() for item in FilterModeProf._member_names_],
value_fn=lambda device: device.filter_mode.name.lower(),
set_value_fn=lambda device, value: device.set_filter_mode(
FilterModeProf[value.upper()]
),
),
EheimDigitalSelectDescription[EheimDigitalFilter](
key="manual_speed",
translation_key="manual_speed",
entity_category=EntityCategory.CONFIG,
unit_of_measurement=UnitOfFrequency.HERTZ,
options_fn=lambda device: [str(i) for i in device.filter_manual_values],
value_fn=lambda device: str(device.manual_speed),
set_value_fn=lambda device, value: device.set_manual_speed(float(value)),
),
EheimDigitalSelectDescription[EheimDigitalFilter](
key="const_flow_speed",
translation_key="const_flow_speed",
entity_category=EntityCategory.CONFIG,
use_api_unit=True,
unit_of_measurement=UnitOfVolumeFlowRate.LITERS_PER_HOUR,
options_fn=lambda device: [str(i) for i in device.filter_const_flow_values],
value_fn=lambda device: str(device.filter_const_flow_values[device.const_flow]),
set_value_fn=(
lambda device, value: device.set_const_flow(
device.filter_const_flow_values.index(int(value))
)
),
),
EheimDigitalSelectDescription[EheimDigitalFilter](
key="day_speed",
translation_key="day_speed",
entity_category=EntityCategory.CONFIG,
use_api_unit=True,
unit_of_measurement=UnitOfVolumeFlowRate.LITERS_PER_HOUR,
options_fn=lambda device: [str(i) for i in device.filter_const_flow_values],
value_fn=lambda device: str(device.filter_const_flow_values[device.day_speed]),
set_value_fn=(
lambda device, value: device.set_day_speed(
device.filter_const_flow_values.index(int(value))
)
),
),
EheimDigitalSelectDescription[EheimDigitalFilter](
key="night_speed",
translation_key="night_speed",
entity_category=EntityCategory.CONFIG,
use_api_unit=True,
unit_of_measurement=UnitOfVolumeFlowRate.LITERS_PER_HOUR,
options_fn=lambda device: [str(i) for i in device.filter_const_flow_values],
value_fn=lambda device: str(
device.filter_const_flow_values[device.night_speed]
),
set_value_fn=(
lambda device, value: device.set_night_speed(
device.filter_const_flow_values.index(int(value))
)
),
),
EheimDigitalSelectDescription[EheimDigitalFilter](
key="high_pulse_speed",
translation_key="high_pulse_speed",
entity_category=EntityCategory.CONFIG,
use_api_unit=True,
unit_of_measurement=UnitOfVolumeFlowRate.LITERS_PER_HOUR,
options_fn=lambda device: [str(i) for i in device.filter_const_flow_values],
value_fn=lambda device: str(
device.filter_const_flow_values[device.high_pulse_speed]
),
set_value_fn=(
lambda device, value: device.set_high_pulse_speed(
device.filter_const_flow_values.index(int(value))
)
),
),
EheimDigitalSelectDescription[EheimDigitalFilter](
key="low_pulse_speed",
translation_key="low_pulse_speed",
entity_category=EntityCategory.CONFIG,
use_api_unit=True,
unit_of_measurement=UnitOfVolumeFlowRate.LITERS_PER_HOUR,
options_fn=lambda device: [str(i) for i in device.filter_const_flow_values],
value_fn=lambda device: str(
device.filter_const_flow_values[device.low_pulse_speed]
),
set_value_fn=(
lambda device, value: device.set_low_pulse_speed(
device.filter_const_flow_values.index(int(value))
)
),
),
)
CLASSICVARIO_DESCRIPTIONS: tuple[
@@ -34,11 +141,7 @@ CLASSICVARIO_DESCRIPTIONS: tuple[
EheimDigitalSelectDescription[EheimDigitalClassicVario](
key="filter_mode",
translation_key="filter_mode",
value_fn=(
lambda device: device.filter_mode.name.lower()
if device.filter_mode is not None
else None
),
value_fn=lambda device: device.filter_mode.name.lower(),
set_value_fn=(
lambda device, value: device.set_filter_mode(FilterMode[value.upper()])
),
@@ -68,6 +171,11 @@ async def async_setup_entry(
)
for description in CLASSICVARIO_DESCRIPTIONS
)
if isinstance(device, EheimDigitalFilter):
entities.extend(
EheimDigitalFilterSelect(coordinator, device, description)
for description in FILTER_DESCRIPTIONS
)
async_add_entities(entities)
@@ -82,6 +190,8 @@ class EheimDigitalSelect[_DeviceT: EheimDigitalDevice](
entity_description: EheimDigitalSelectDescription[_DeviceT]
_attr_options: list[str]
def __init__(
self,
coordinator: EheimDigitalUpdateCoordinator,
@@ -91,13 +201,49 @@ class EheimDigitalSelect[_DeviceT: EheimDigitalDevice](
"""Initialize an EHEIM Digital select entity."""
super().__init__(coordinator, device)
self.entity_description = description
if description.options_fn is not None:
self._attr_options = description.options_fn(device)
elif description.options is not None:
self._attr_options = description.options
self._attr_unique_id = f"{self._device_address}_{description.key}"
@override
@exception_handler
async def async_select_option(self, option: str) -> None:
return await self.entity_description.set_value_fn(self._device, option)
if await_return := self.entity_description.set_value_fn(self._device, option):
return await await_return
return None
@override
def _async_update_attrs(self) -> None:
self._attr_current_option = self.entity_description.value_fn(self._device)
class EheimDigitalFilterSelect(EheimDigitalSelect[EheimDigitalFilter]):
"""Represent an EHEIM Digital Filter select entity."""
entity_description: EheimDigitalSelectDescription[EheimDigitalFilter]
_attr_native_unit_of_measurement: str | None
@override
def _async_update_attrs(self) -> None:
if (
self.entity_description.options is None
and self.entity_description.options_fn is not None
):
self._attr_options = self.entity_description.options_fn(self._device)
if self.entity_description.use_api_unit:
if (
self.entity_description.unit_of_measurement
== UnitOfVolumeFlowRate.LITERS_PER_HOUR
and self._device.usrdta["unit"]
== int(EheimDigitalUnitOfMeasurement.US_CUSTOMARY)
):
self._attr_native_unit_of_measurement = (
UnitOfVolumeFlowRate.GALLONS_PER_HOUR
)
else:
self._attr_native_unit_of_measurement = (
self.entity_description.unit_of_measurement
)
super()._async_update_attrs()

View File

@@ -6,6 +6,7 @@ from typing import Any, override
from eheimdigital.classic_vario import EheimDigitalClassicVario
from eheimdigital.device import EheimDigitalDevice
from eheimdigital.filter import EheimDigitalFilter
from eheimdigital.types import FilterErrorCode
from homeassistant.components.sensor import (
@@ -13,7 +14,7 @@ from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTime
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfFrequency, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -33,6 +34,27 @@ class EheimDigitalSensorDescription[_DeviceT: EheimDigitalDevice](
value_fn: Callable[[_DeviceT], float | str | None]
FILTER_DESCRIPTIONS: tuple[EheimDigitalSensorDescription[EheimDigitalFilter], ...] = (
EheimDigitalSensorDescription[EheimDigitalFilter](
key="current_speed",
translation_key="current_speed",
value_fn=lambda device: device.current_speed,
device_class=SensorDeviceClass.FREQUENCY,
suggested_display_precision=1,
native_unit_of_measurement=UnitOfFrequency.HERTZ,
),
EheimDigitalSensorDescription[EheimDigitalFilter](
key="service_hours",
translation_key="service_hours",
value_fn=lambda device: device.service_hours,
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.HOURS,
suggested_unit_of_measurement=UnitOfTime.DAYS,
entity_category=EntityCategory.DIAGNOSTIC,
),
)
CLASSICVARIO_DESCRIPTIONS: tuple[
EheimDigitalSensorDescription[EheimDigitalClassicVario], ...
] = (
@@ -54,11 +76,7 @@ CLASSICVARIO_DESCRIPTIONS: tuple[
EheimDigitalSensorDescription[EheimDigitalClassicVario](
key="error_code",
translation_key="error_code",
value_fn=(
lambda device: device.error_code.name.lower()
if device.error_code is not None
else None
),
value_fn=lambda device: device.error_code.name.lower(),
device_class=SensorDeviceClass.ENUM,
options=[name.lower() for name in FilterErrorCode._member_names_],
entity_category=EntityCategory.DIAGNOSTIC,
@@ -80,6 +98,13 @@ async def async_setup_entry(
"""Set up the light entities for one or multiple devices."""
entities: list[EheimDigitalSensor[Any]] = []
for device in device_address.values():
if isinstance(device, EheimDigitalFilter):
entities += [
EheimDigitalSensor[EheimDigitalFilter](
coordinator, device, description
)
for description in FILTER_DESCRIPTIONS
]
if isinstance(device, EheimDigitalClassicVario):
entities += [
EheimDigitalSensor[EheimDigitalClassicVario](

View File

@@ -61,6 +61,12 @@
"day_speed": {
"name": "Day speed"
},
"high_pulse_time": {
"name": "High pulse duration"
},
"low_pulse_time": {
"name": "Low pulse duration"
},
"manual_speed": {
"name": "Manual speed"
},
@@ -78,13 +84,32 @@
}
},
"select": {
"const_flow_speed": {
"name": "Constant flow speed"
},
"day_speed": {
"name": "Day speed"
},
"filter_mode": {
"name": "Filter mode",
"state": {
"bio": "Bio",
"constant_flow": "Constant flow",
"manual": "Manual",
"pulse": "Pulse"
}
},
"high_pulse_speed": {
"name": "High pulse speed"
},
"low_pulse_speed": {
"name": "Low pulse speed"
},
"manual_speed": {
"name": "Manual speed"
},
"night_speed": {
"name": "Night speed"
}
},
"sensor": {
@@ -99,8 +124,17 @@
"rotor_stuck": "Rotor stuck"
}
},
"operating_time": {
"name": "Operating time"
},
"service_hours": {
"name": "Remaining hours until service"
},
"turn_feeding_time": {
"name": "Remaining off time after feeding"
},
"turn_off_time": {
"name": "Remaining off time"
}
},
"time": {

View File

@@ -4,6 +4,7 @@ from typing import Any, override
from eheimdigital.classic_vario import EheimDigitalClassicVario
from eheimdigital.device import EheimDigitalDevice
from eheimdigital.filter import EheimDigitalFilter
from homeassistant.components.switch import SwitchEntity
from homeassistant.core import HomeAssistant
@@ -30,8 +31,8 @@ async def async_setup_entry(
"""Set up the switch entities for one or multiple devices."""
entities: list[SwitchEntity] = []
for device in device_address.values():
if isinstance(device, EheimDigitalClassicVario):
entities.append(EheimDigitalClassicVarioSwitch(coordinator, device)) # noqa: PERF401
if isinstance(device, (EheimDigitalClassicVario, EheimDigitalFilter)):
entities.append(EheimDigitalFilterSwitch(coordinator, device)) # noqa: PERF401
async_add_entities(entities)
@@ -39,10 +40,10 @@ async def async_setup_entry(
async_setup_device_entities(coordinator.hub.devices)
class EheimDigitalClassicVarioSwitch(
EheimDigitalEntity[EheimDigitalClassicVario], SwitchEntity
class EheimDigitalFilterSwitch(
EheimDigitalEntity[EheimDigitalClassicVario | EheimDigitalFilter], SwitchEntity
):
"""Represent an EHEIM Digital classicVARIO switch entity."""
"""Represent an EHEIM Digital classicVARIO or filter switch entity."""
_attr_translation_key = "filter_active"
_attr_name = None
@@ -50,9 +51,9 @@ class EheimDigitalClassicVarioSwitch(
def __init__(
self,
coordinator: EheimDigitalUpdateCoordinator,
device: EheimDigitalClassicVario,
device: EheimDigitalClassicVario | EheimDigitalFilter,
) -> None:
"""Initialize an EHEIM Digital classicVARIO switch entity."""
"""Initialize an EHEIM Digital classicVARIO or filter switch entity."""
super().__init__(coordinator, device)
self._attr_unique_id = device.mac_address
self._async_update_attrs()

View File

@@ -7,6 +7,7 @@ from typing import Any, final, override
from eheimdigital.classic_vario import EheimDigitalClassicVario
from eheimdigital.device import EheimDigitalDevice
from eheimdigital.filter import EheimDigitalFilter
from eheimdigital.heater import EheimDigitalHeater
from homeassistant.components.time import TimeEntity, TimeEntityDescription
@@ -28,6 +29,23 @@ class EheimDigitalTimeDescription[_DeviceT: EheimDigitalDevice](TimeEntityDescri
set_value_fn: Callable[[_DeviceT, time], Awaitable[None]]
FILTER_DESCRIPTIONS: tuple[EheimDigitalTimeDescription[EheimDigitalFilter], ...] = (
EheimDigitalTimeDescription[EheimDigitalFilter](
key="day_start_time",
translation_key="day_start_time",
entity_category=EntityCategory.CONFIG,
value_fn=lambda device: device.day_start_time,
set_value_fn=lambda device, value: device.set_day_start_time(value),
),
EheimDigitalTimeDescription[EheimDigitalFilter](
key="night_start_time",
translation_key="night_start_time",
entity_category=EntityCategory.CONFIG,
value_fn=lambda device: device.night_start_time,
set_value_fn=lambda device, value: device.set_night_start_time(value),
),
)
CLASSICVARIO_DESCRIPTIONS: tuple[
EheimDigitalTimeDescription[EheimDigitalClassicVario], ...
] = (
@@ -79,6 +97,13 @@ async def async_setup_entry(
"""Set up the time entities for one or multiple devices."""
entities: list[EheimDigitalTime[Any]] = []
for device in device_address.values():
if isinstance(device, EheimDigitalFilter):
entities.extend(
EheimDigitalTime[EheimDigitalFilter](
coordinator, device, description
)
for description in FILTER_DESCRIPTIONS
)
if isinstance(device, EheimDigitalClassicVario):
entities.extend(
EheimDigitalTime[EheimDigitalClassicVario](

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/gree",
"iot_class": "local_polling",
"loggers": ["greeclimate"],
"requirements": ["greeclimate==2.1.0"]
"requirements": ["greeclimate==2.1.1"]
}

View File

@@ -16,7 +16,7 @@ from aiohasupervisor.models import GreenOptions, YellowOptions # noqa: F401
import voluptuous as vol
from homeassistant.auth.const import GROUP_ID_ADMIN
from homeassistant.components import network, panel_custom
from homeassistant.components import panel_custom
from homeassistant.components.homeassistant import async_set_stop_handler
from homeassistant.components.http import StaticPathConfig
from homeassistant.config_entries import SOURCE_SYSTEM, ConfigEntry
@@ -41,7 +41,6 @@ from homeassistant.helpers import (
issue_registry as ir,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.issue_registry import IssueSeverity
from homeassistant.helpers.typing import ConfigType
@@ -79,7 +78,6 @@ from .const import (
ATTR_LOCATION,
ATTR_PASSWORD,
ATTR_SLUG,
ATTR_WS_EVENT,
DATA_COMPONENT,
DATA_CONFIG_STORE,
DATA_CORE_INFO,
@@ -91,8 +89,6 @@ from .const import (
DATA_STORE,
DATA_SUPERVISOR_INFO,
DOMAIN,
EVENT_NETWORK_CHANGED,
EVENT_SUPERVISOR_EVENT,
HASSIO_UPDATE_INTERVAL,
)
from .coordinator import (
@@ -384,16 +380,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
hass.data[DATA_KEY_SUPERVISOR_ISSUES] = issues = SupervisorIssues(hass, hassio)
issues_task = hass.async_create_task(issues.setup(), eager_start=True)
@callback
def _async_handle_supervisor_events(event: dict[str, Any]) -> None:
"""Handle supervisor events for network changes."""
if event.get(ATTR_WS_EVENT) == EVENT_NETWORK_CHANGED:
hass.async_create_task(network.async_notify_network_change(hass))
async_dispatcher_connect(
hass, EVENT_SUPERVISOR_EVENT, _async_handle_supervisor_events
)
async def async_service_handler(service: ServiceCall) -> None:
"""Handle service calls for Hass.io."""
api_endpoint = MAP_SERVICE_API[service.service]

View File

@@ -70,7 +70,6 @@ EVENT_HEALTH_CHANGED = "health_changed"
EVENT_SUPPORTED_CHANGED = "supported_changed"
EVENT_ISSUE_CHANGED = "issue_changed"
EVENT_ISSUE_REMOVED = "issue_removed"
EVENT_NETWORK_CHANGED = "network_changed"
EVENT_JOB = "job"
UPDATE_KEY_SUPERVISOR = "supervisor"

View File

@@ -8,5 +8,5 @@
"iot_class": "local_push",
"loggers": ["pyhik"],
"quality_scale": "legacy",
"requirements": ["pyHik==0.3.4"]
"requirements": ["pyHik==0.4.0"]
}

View File

@@ -1,6 +1,6 @@
{
"domain": "namecheapdns",
"name": "Namecheap FreeDNS",
"name": "Namecheap DynamicDNS",
"codeowners": [],
"documentation": "https://www.home-assistant.io/integrations/namecheapdns",
"iot_class": "cloud_push",

View File

@@ -2,7 +2,6 @@
from __future__ import annotations
from collections.abc import Callable
from ipaddress import IPv4Address, IPv6Address, ip_interface
import logging
from pathlib import Path
@@ -23,12 +22,7 @@ from .const import (
PUBLIC_TARGET_IP,
)
from .models import Adapter
from .network import (
Network,
NetworkChangeCallback,
async_get_loaded_network,
async_get_network,
)
from .network import Network, async_get_loaded_network, async_get_network
_LOGGER = logging.getLogger(__name__)
@@ -178,32 +172,6 @@ async def async_get_announce_addresses(hass: HomeAssistant) -> list[str]:
return list(addresses)
@callback
def async_register_network_change_callback(
hass: HomeAssistant, callback_fn: NetworkChangeCallback
) -> Callable[[], None]:
"""Register a callback to be called when network adapters change.
Returns a function to unregister the callback.
The callback will be called with the new list of adapters when
a network change is detected.
"""
network: Network = async_get_loaded_network(hass)
return network.async_register_change_callback(callback_fn)
async def async_notify_network_change(hass: HomeAssistant) -> None:
"""Notify the network integration of a network change.
This will reload network adapters and notify all registered callbacks.
This should be called when external systems (like the supervisor) detect
a network change.
"""
network: Network = await async_get_network(hass)
await network.async_notify_network_change()
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up network for Home Assistant."""
# Avoid circular issue: http->network->websocket_api->http

View File

@@ -2,7 +2,6 @@
from __future__ import annotations
from collections.abc import Callable
import logging
from typing import Any
@@ -24,8 +23,6 @@ from .util import async_load_adapters, enable_adapters, enable_auto_detected_ada
_LOGGER = logging.getLogger(__name__)
type NetworkChangeCallback = Callable[[list[Adapter]], None]
DATA_NETWORK: HassKey[Network] = HassKey(DOMAIN)
@@ -51,13 +48,11 @@ class Network:
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the Network class."""
self._hass = hass
self._store = Store[dict[str, list[str]]](
hass, STORAGE_VERSION, STORAGE_KEY, atomic_writes=True
)
self._data: dict[str, list[str]] = {}
self.adapters: list[Adapter] = []
self._change_callbacks: list[NetworkChangeCallback] = []
@property
def configured_adapters(self) -> list[str]:
@@ -90,34 +85,3 @@ class Network:
async def _async_save(self) -> None:
"""Save preferences."""
await self._store.async_save(self._data)
@callback
def async_register_change_callback(
self, callback_fn: NetworkChangeCallback
) -> Callable[[], None]:
"""Register a callback to be called when network adapters change.
Returns a function to unregister the callback.
"""
self._change_callbacks.append(callback_fn)
@callback
def unregister() -> None:
"""Unregister the callback."""
self._change_callbacks.remove(callback_fn)
return unregister
async def async_notify_network_change(self) -> None:
"""Notify listeners of a network change.
This reloads network adapters and calls all registered callbacks.
"""
old_adapters = self.adapters
self.adapters = await async_load_adapters()
self.async_configure()
if old_adapters != self.adapters:
_LOGGER.info("Network adapters changed: %s", self.adapters)
for callback_fn in self._change_callbacks:
callback_fn(self.adapters)

View File

@@ -50,7 +50,6 @@ rules:
Use load_json_object_fixture in tests
Patch the library instead of the HTTP requests
Create a shared fixture for the mock config entry
Use snapshots for binary sensor tests
Use init_integration in tests
Evaluate the need of test_config_entry_not_ready

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["pynintendoauth", "pynintendoparental"],
"quality_scale": "bronze",
"requirements": ["pynintendoauth==1.0.2", "pynintendoparental==2.3.0"]
"requirements": ["pynintendoauth==1.0.2", "pynintendoparental==2.3.2"]
}

View File

@@ -128,15 +128,15 @@ class AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint(
states = self.device.states
if (
operating_mode := states[OverkizState.CORE_OPERATING_MODE]
) and operating_mode.value_as_str == OverkizCommandParam.EXTERNAL:
return PRESET_EXTERNAL
if (
state := states[OverkizState.IO_TARGET_HEATING_LEVEL]
) and state.value_as_str:
return OVERKIZ_TO_PRESET_MODE[state.value_as_str]
if (
operating_mode := states[OverkizState.CORE_OPERATING_MODE]
) and operating_mode.value_as_str == OverkizCommandParam.EXTERNAL:
return PRESET_EXTERNAL
return None
async def async_set_preset_mode(self, preset_mode: str) -> None:

View File

@@ -151,6 +151,12 @@ SENSOR_DESCRIPTIONS = {
translation_key="nox_index",
state_class=SensorStateClass.MEASUREMENT,
),
"iaqs": SensorEntityDescription(
key="iaqs",
translation_key="iaqs",
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
}

View File

@@ -33,6 +33,9 @@
"acceleration_z": {
"name": "Acceleration Z"
},
"iaqs": {
"name": "Indoor air quality score"
},
"movement_counter": {
"name": "Movement counter"
},

View File

@@ -13,5 +13,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["switchbot_api"],
"requirements": ["switchbot-api==2.9.0"]
"requirements": ["switchbot-api==2.10.0"]
}

View File

@@ -24,13 +24,13 @@ async def async_setup_platform(
return pollbot
async def process_error(update: object, context: CallbackContext) -> None:
async def process_error(bot: Bot, update: object, context: CallbackContext) -> None:
"""Telegram bot error handler."""
if context.error:
error_callback(context.error, update)
error_callback(bot, context.error, update)
def error_callback(error: Exception, update: object | None = None) -> None:
def error_callback(bot: Bot, error: Exception, update: object | None = None) -> None:
"""Log the error."""
try:
raise error
@@ -39,9 +39,17 @@ def error_callback(error: Exception, update: object | None = None) -> None:
pass
except TelegramError:
if update is not None:
_LOGGER.error('Update "%s" caused error: "%s"', update, error)
_LOGGER.error(
'[%s %s] Update "%s" caused error: "%s"',
bot.username,
bot.id,
update,
error,
)
else:
_LOGGER.error("%s: %s", error.__class__.__name__, error)
_LOGGER.error(
"[%s %s] %s: %s", bot.username, bot.id, error.__class__.__name__, error
)
class PollBot(BaseTelegramBot):
@@ -58,7 +66,9 @@ class PollBot(BaseTelegramBot):
self.bot = bot
self.application = ApplicationBuilder().bot(self.bot).build()
self.application.add_handler(TypeHandler(Update, self.handle_update))
self.application.add_error_handler(process_error)
self.application.add_error_handler(
lambda update, context: process_error(self.bot, update, context)
)
async def shutdown(self) -> None:
"""Shutdown the app."""
@@ -66,16 +76,18 @@ class PollBot(BaseTelegramBot):
async def start_polling(self) -> None:
"""Start the polling task."""
_LOGGER.debug("Starting polling")
await self.application.initialize()
if self.application.updater:
await self.application.updater.start_polling(error_callback=error_callback)
await self.application.updater.start_polling(
error_callback=lambda error: error_callback(self.bot, error, None)
)
await self.application.start()
_LOGGER.info("[%s %s] Started polling", self.bot.username, self.bot.id)
async def stop_polling(self) -> None:
"""Stop the polling task."""
_LOGGER.debug("Stopping polling")
if self.application.updater:
await self.application.updater.stop()
await self.application.stop()
await self.application.shutdown()
_LOGGER.info("[%s %s] Stopped polling", self.bot.username, self.bot.id)

View File

@@ -8,7 +8,7 @@ start:
duration:
example: "00:01:00 or 60"
selector:
text:
duration:
pause:
target:

View File

@@ -654,6 +654,7 @@ DISCOVERY_SCHEMAS: list[NewZWaveDiscoverySchema] = [
key=NOTIFICATION_SMOKE_ALARM,
entity_category=EntityCategory.DIAGNOSTIC,
not_states={
0,
SmokeAlarmNotificationEvent.SENSOR_STATUS_SMOKE_DETECTED_LOCATION_PROVIDED,
SmokeAlarmNotificationEvent.SENSOR_STATUS_SMOKE_DETECTED,
SmokeAlarmNotificationEvent.MAINTENANCE_STATUS_REPLACEMENT_REQUIRED,

View File

@@ -4342,7 +4342,7 @@
"iot_class": "local_polling"
},
"namecheapdns": {
"name": "Namecheap FreeDNS",
"name": "Namecheap DynamicDNS",
"integration_type": "hub",
"config_flow": false,
"iot_class": "cloud_push"

View File

@@ -56,7 +56,7 @@ PyJWT==2.10.1
PyNaCl==1.6.0
pyOpenSSL==25.3.0
pyserial==3.5
pysilero-vad==3.1.0
pysilero-vad==3.2.0
pyspeex-noise==1.0.2
python-slugify==8.0.4
PyTurboJPEG==1.8.0

2
requirements.txt generated
View File

@@ -40,7 +40,7 @@ propcache==0.4.1
psutil-home-assistant==0.0.1
PyJWT==2.10.1
pyOpenSSL==25.3.0
pysilero-vad==3.1.0
pysilero-vad==3.2.0
pyspeex-noise==1.0.2
python-slugify==8.0.4
PyTurboJPEG==1.8.0

16
requirements_all.txt generated
View File

@@ -187,7 +187,7 @@ aioairq==0.4.7
aioairzone-cloud==0.7.2
# homeassistant.components.airzone
aioairzone==1.0.4
aioairzone==1.0.5
# homeassistant.components.alexa_devices
aioamazondevices==11.0.2
@@ -703,7 +703,7 @@ brottsplatskartan==1.0.5
brunt==1.2.0
# homeassistant.components.bthome
bthome-ble==3.17.0
bthome-ble==3.16.0
# homeassistant.components.bt_home_hub_5
bthomehub5-devicelist==0.1.1
@@ -842,7 +842,7 @@ dynalite-panel==0.0.4
eagle100==0.1.1
# homeassistant.components.easyenergy
easyenergy==2.1.2
easyenergy==2.2.0
# homeassistant.components.ebusd
ebusdpy==0.0.17
@@ -1127,7 +1127,7 @@ gpiozero==1.6.2
gps3==0.33.3
# homeassistant.components.gree
greeclimate==2.1.0
greeclimate==2.1.1
# homeassistant.components.greeneye_monitor
greeneye_monitor==3.0.3
@@ -1855,7 +1855,7 @@ pyElectra==1.2.4
pyEmby==1.10
# homeassistant.components.hikvision
pyHik==0.3.4
pyHik==0.4.0
# homeassistant.components.homee
pyHomee==1.3.8
@@ -2238,7 +2238,7 @@ pynina==0.3.6
pynintendoauth==1.0.2
# homeassistant.components.nintendo_parental_controls
pynintendoparental==2.3.0
pynintendoparental==2.3.2
# homeassistant.components.nobo_hub
pynobo==1.8.1
@@ -2409,7 +2409,7 @@ pysiaalarm==3.1.1
pysignalclirestapi==0.3.24
# homeassistant.components.assist_pipeline
pysilero-vad==3.1.0
pysilero-vad==3.2.0
# homeassistant.components.sky_hub
pyskyqhub==0.1.4
@@ -2956,7 +2956,7 @@ surepy==0.9.0
swisshydrodata==0.1.0
# homeassistant.components.switchbot_cloud
switchbot-api==2.9.0
switchbot-api==2.10.0
# homeassistant.components.synology_srm
synology-srm==0.2.0

View File

@@ -178,7 +178,7 @@ aioairq==0.4.7
aioairzone-cloud==0.7.2
# homeassistant.components.airzone
aioairzone==1.0.4
aioairzone==1.0.5
# homeassistant.components.alexa_devices
aioamazondevices==11.0.2
@@ -633,7 +633,7 @@ brottsplatskartan==1.0.5
brunt==1.2.0
# homeassistant.components.bthome
bthome-ble==3.17.0
bthome-ble==3.16.0
# homeassistant.components.buienradar
buienradar==1.0.6
@@ -748,7 +748,7 @@ dynalite-panel==0.0.4
eagle100==0.1.1
# homeassistant.components.easyenergy
easyenergy==2.1.2
easyenergy==2.2.0
# homeassistant.components.egauge
egauge-async==0.4.0
@@ -1000,7 +1000,7 @@ govee-local-api==2.3.0
gps3==0.33.3
# homeassistant.components.gree
greeclimate==2.1.0
greeclimate==2.1.1
# homeassistant.components.greeneye_monitor
greeneye_monitor==3.0.3
@@ -1589,7 +1589,7 @@ pyDuotecno==2024.10.1
pyElectra==1.2.4
# homeassistant.components.hikvision
pyHik==0.3.4
pyHik==0.4.0
# homeassistant.components.homee
pyHomee==1.3.8
@@ -1891,7 +1891,7 @@ pynina==0.3.6
pynintendoauth==1.0.2
# homeassistant.components.nintendo_parental_controls
pynintendoparental==2.3.0
pynintendoparental==2.3.2
# homeassistant.components.nobo_hub
pynobo==1.8.1
@@ -2035,7 +2035,7 @@ pysiaalarm==3.1.1
pysignalclirestapi==0.3.24
# homeassistant.components.assist_pipeline
pysilero-vad==3.1.0
pysilero-vad==3.2.0
# homeassistant.components.sma
pysma==1.1.0
@@ -2477,7 +2477,7 @@ subarulink==0.7.15
surepy==0.9.0
# homeassistant.components.switchbot_cloud
switchbot-api==2.9.0
switchbot-api==2.10.0
# homeassistant.components.system_bridge
systembridgeconnector==5.3.1

View File

@@ -1,4 +1,52 @@
# serializer version: 1
# name: test_buttons[button.test_thermostat_recalibrate_co2_sensor-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'button.test_thermostat_recalibrate_co2_sensor',
'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': 'Recalibrate CO2 sensor',
'platform': 'airobot',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'recalibrate_co2',
'unique_id': 'T01A1B2C3_recalibrate_co2',
'unit_of_measurement': None,
})
# ---
# name: test_buttons[button.test_thermostat_recalibrate_co2_sensor-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Thermostat Recalibrate CO2 sensor',
}),
'context': <ANY>,
'entity_id': 'button.test_thermostat_recalibrate_co2_sensor',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_buttons[button.test_thermostat_restart-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -25,7 +25,7 @@ def platforms() -> list[Platform]:
return [Platform.BUTTON]
@pytest.mark.usefixtures("init_integration")
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
async def test_buttons(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
@@ -93,3 +93,38 @@ async def test_restart_button_connection_errors(
)
mock_airobot_client.reboot_thermostat.assert_called_once()
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
async def test_recalibrate_co2_button(
hass: HomeAssistant,
mock_airobot_client: AsyncMock,
) -> None:
"""Test recalibrate CO2 sensor button."""
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: "button.test_thermostat_recalibrate_co2_sensor"},
blocking=True,
)
mock_airobot_client.recalibrate_co2_sensor.assert_called_once()
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
async def test_recalibrate_co2_button_error(
hass: HomeAssistant,
mock_airobot_client: AsyncMock,
) -> None:
"""Test recalibrate CO2 sensor button error handling."""
mock_airobot_client.recalibrate_co2_sensor.side_effect = AirobotError("Test error")
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: "button.test_thermostat_recalibrate_co2_sensor"},
blocking=True,
)
mock_airobot_client.recalibrate_co2_sensor.assert_called_once()

View File

@@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
from eheimdigital.classic_led_ctrl import EheimDigitalClassicLEDControl
from eheimdigital.classic_vario import EheimDigitalClassicVario
from eheimdigital.filter import EheimDigitalFilter
from eheimdigital.heater import EheimDigitalHeater
from eheimdigital.hub import EheimDigitalHub
from eheimdigital.types import (
@@ -13,6 +14,7 @@ from eheimdigital.types import (
ClassicVarioDataPacket,
ClockPacket,
CloudPacket,
FilterDataPacket,
MoonPacket,
UsrDtaPacket,
)
@@ -82,11 +84,25 @@ def classic_vario_mock():
return classic_vario
@pytest.fixture
def filter_mock():
"""Mock a filter device."""
eheim_filter = EheimDigitalFilter(
MagicMock(spec=EheimDigitalHub),
UsrDtaPacket(load_json_object_fixture("filter/usrdta.json", DOMAIN)),
)
eheim_filter.filter_data = FilterDataPacket(
load_json_object_fixture("filter/filter_data.json", DOMAIN)
)
return eheim_filter
@pytest.fixture
def eheimdigital_hub_mock(
classic_led_ctrl_mock: MagicMock,
heater_mock: MagicMock,
classic_vario_mock: MagicMock,
filter_mock: MagicMock,
) -> Generator[AsyncMock]:
"""Mock eheimdigital hub."""
with (
@@ -103,6 +119,7 @@ def eheimdigital_hub_mock(
"00:00:00:00:00:01": classic_led_ctrl_mock,
"00:00:00:00:00:02": heater_mock,
"00:00:00:00:00:03": classic_vario_mock,
"00:00:00:00:00:04": filter_mock,
}
eheimdigital_hub_mock.return_value.main = classic_led_ctrl_mock
yield eheimdigital_hub_mock

View File

@@ -0,0 +1,32 @@
{
"title": "FILTER_DATA",
"from": "00:00:00:00:00:04",
"minFreq": 3500,
"maxFreq": 6700,
"maxFreqRglOff": 7200,
"freq": 7200,
"freqSoll": 7200,
"dfs": 0,
"dfsFaktor": 0,
"sollStep": 14,
"rotSpeed": 43,
"pumpMode": 16,
"sync": "",
"partnerName": "",
"filterActive": 1,
"runTime": 106435,
"actualTime": 106435,
"serviceHour": 36774,
"pm_dfs_soll_high": 10,
"pm_dfs_soll_low": 1,
"pm_time_high": 10,
"pm_time_low": 10,
"nm_dfs_soll_day": 10,
"nm_dfs_soll_night": 4,
"end_time_night_mode": 420,
"start_time_night_mode": 1140,
"version": 74,
"isEheim": 1,
"turnOffTime": 0,
"turnTimeFeeding": 0
}

View File

@@ -0,0 +1,34 @@
{
"title": "USRDTA",
"from": "00:00:00:00:00:04",
"name": "Mock filter",
"aqName": "Mock Aquarium",
"version": 4,
"language": "DE",
"timezone": 60,
"tID": 30,
"dst": 1,
"tankconfig": "WITHOUT_THERMO",
"power": "",
"netmode": "ST",
"host": "eheimdigital",
"groupID": 0,
"meshing": 1,
"firstStart": 0,
"remote": 0,
"revision": [1026, 1030],
"build": ["1752053563000", "1752045985672"],
"latestAvailableRevision": [1026, 1030, 2038, 2038],
"firmwareAvailable": 0,
"emailAddr": "",
"stMail": 0,
"stMailMode": 0,
"fstTime": 720,
"sstTime": 0,
"liveTime": 103680,
"usrName": "",
"unit": 0,
"demoUse": 0,
"sysLED": 45,
"to": "USER"
}

View File

@@ -236,6 +236,85 @@
'version': 18,
}),
}),
'00:00:00:00:00:04': dict({
'filter_data': dict({
'actualTime': 106435,
'dfs': 0,
'dfsFaktor': 0,
'end_time_night_mode': 420,
'filterActive': 1,
'freq': 7200,
'freqSoll': 7200,
'from': '00:00:00:00:00:04',
'isEheim': 1,
'maxFreq': 6700,
'maxFreqRglOff': 7200,
'minFreq': 3500,
'nm_dfs_soll_day': 10,
'nm_dfs_soll_night': 4,
'partnerName': '',
'pm_dfs_soll_high': 10,
'pm_dfs_soll_low': 1,
'pm_time_high': 10,
'pm_time_low': 10,
'pumpMode': 16,
'rotSpeed': 43,
'runTime': 106435,
'serviceHour': 36774,
'sollStep': 14,
'start_time_night_mode': 1140,
'sync': '',
'title': 'FILTER_DATA',
'turnOffTime': 0,
'turnTimeFeeding': 0,
'version': 74,
}),
'usrdta': dict({
'aqName': 'Mock Aquarium',
'build': list([
'1752053563000',
'1752045985672',
]),
'demoUse': 0,
'dst': 1,
'emailAddr': '',
'firmwareAvailable': 0,
'firstStart': 0,
'from': '00:00:00:00:00:04',
'fstTime': 720,
'groupID': 0,
'host': 'eheimdigital',
'language': 'DE',
'latestAvailableRevision': list([
1026,
1030,
2038,
2038,
]),
'liveTime': 103680,
'meshing': 1,
'name': 'Mock filter',
'netmode': 'ST',
'power': '',
'remote': 0,
'revision': list([
1026,
1030,
]),
'sstTime': 0,
'stMail': 0,
'stMailMode': 0,
'sysLED': 45,
'tID': 30,
'tankconfig': 'WITHOUT_THERMO',
'timezone': 60,
'title': 'USRDTA',
'to': 'USER',
'unit': 0,
'usrName': '',
'version': 4,
}),
}),
}),
'entry': dict({
'data': dict({

View File

@@ -289,6 +289,182 @@
'state': 'unknown',
})
# ---
# name: test_setup[number.mock_filter_high_pulse_duration-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 200000,
'min': 5,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.mock_filter_high_pulse_duration',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <NumberDeviceClass.DURATION: 'duration'>,
'original_icon': None,
'original_name': 'High pulse duration',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'high_pulse_time',
'unique_id': '00:00:00:00:00:04_high_pulse_time',
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
})
# ---
# name: test_setup[number.mock_filter_high_pulse_duration-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'Mock filter High pulse duration',
'max': 200000,
'min': 5,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
}),
'context': <ANY>,
'entity_id': 'number.mock_filter_high_pulse_duration',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[number.mock_filter_low_pulse_duration-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 200000,
'min': 5,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.mock_filter_low_pulse_duration',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <NumberDeviceClass.DURATION: 'duration'>,
'original_icon': None,
'original_name': 'Low pulse duration',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'low_pulse_time',
'unique_id': '00:00:00:00:00:04_low_pulse_time',
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
})
# ---
# name: test_setup[number.mock_filter_low_pulse_duration-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'Mock filter Low pulse duration',
'max': 200000,
'min': 5,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
}),
'context': <ANY>,
'entity_id': 'number.mock_filter_low_pulse_duration',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[number.mock_filter_system_led_brightness-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.mock_filter_system_led_brightness',
'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': 'System LED brightness',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'system_led',
'unique_id': '00:00:00:00:00:04_system_led',
'unit_of_measurement': '%',
})
# ---
# name: test_setup[number.mock_filter_system_led_brightness-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock filter System LED brightness',
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'number.mock_filter_system_led_brightness',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[number.mock_heater_night_temperature_offset-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -58,3 +58,568 @@
'state': 'unknown',
})
# ---
# name: test_setup[select.mock_filter_constant_flow_speed-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'400',
'440',
'480',
'515',
'550',
'585',
'620',
'650',
'680',
'710',
'740',
'770',
'800',
'830',
'860',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.mock_filter_constant_flow_speed',
'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': 'Constant flow speed',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'const_flow_speed',
'unique_id': '00:00:00:00:00:04_const_flow_speed',
'unit_of_measurement': <UnitOfVolumeFlowRate.LITERS_PER_HOUR: 'L/h'>,
})
# ---
# name: test_setup[select.mock_filter_constant_flow_speed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock filter Constant flow speed',
'options': list([
'400',
'440',
'480',
'515',
'550',
'585',
'620',
'650',
'680',
'710',
'740',
'770',
'800',
'830',
'860',
]),
'unit_of_measurement': <UnitOfVolumeFlowRate.LITERS_PER_HOUR: 'L/h'>,
}),
'context': <ANY>,
'entity_id': 'select.mock_filter_constant_flow_speed',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[select.mock_filter_day_speed-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'400',
'440',
'480',
'515',
'550',
'585',
'620',
'650',
'680',
'710',
'740',
'770',
'800',
'830',
'860',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.mock_filter_day_speed',
'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': 'Day speed',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'day_speed',
'unique_id': '00:00:00:00:00:04_day_speed',
'unit_of_measurement': <UnitOfVolumeFlowRate.LITERS_PER_HOUR: 'L/h'>,
})
# ---
# name: test_setup[select.mock_filter_day_speed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock filter Day speed',
'options': list([
'400',
'440',
'480',
'515',
'550',
'585',
'620',
'650',
'680',
'710',
'740',
'770',
'800',
'830',
'860',
]),
'unit_of_measurement': <UnitOfVolumeFlowRate.LITERS_PER_HOUR: 'L/h'>,
}),
'context': <ANY>,
'entity_id': 'select.mock_filter_day_speed',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[select.mock_filter_filter_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'manual',
'constant_flow',
'pulse',
'bio',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.mock_filter_filter_mode',
'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': 'Filter mode',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'filter_mode',
'unique_id': '00:00:00:00:00:04_filter_mode',
'unit_of_measurement': None,
})
# ---
# name: test_setup[select.mock_filter_filter_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock filter Filter mode',
'options': list([
'manual',
'constant_flow',
'pulse',
'bio',
]),
}),
'context': <ANY>,
'entity_id': 'select.mock_filter_filter_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[select.mock_filter_high_pulse_speed-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'400',
'440',
'480',
'515',
'550',
'585',
'620',
'650',
'680',
'710',
'740',
'770',
'800',
'830',
'860',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.mock_filter_high_pulse_speed',
'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': 'High pulse speed',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'high_pulse_speed',
'unique_id': '00:00:00:00:00:04_high_pulse_speed',
'unit_of_measurement': <UnitOfVolumeFlowRate.LITERS_PER_HOUR: 'L/h'>,
})
# ---
# name: test_setup[select.mock_filter_high_pulse_speed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock filter High pulse speed',
'options': list([
'400',
'440',
'480',
'515',
'550',
'585',
'620',
'650',
'680',
'710',
'740',
'770',
'800',
'830',
'860',
]),
'unit_of_measurement': <UnitOfVolumeFlowRate.LITERS_PER_HOUR: 'L/h'>,
}),
'context': <ANY>,
'entity_id': 'select.mock_filter_high_pulse_speed',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[select.mock_filter_low_pulse_speed-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'400',
'440',
'480',
'515',
'550',
'585',
'620',
'650',
'680',
'710',
'740',
'770',
'800',
'830',
'860',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.mock_filter_low_pulse_speed',
'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': 'Low pulse speed',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'low_pulse_speed',
'unique_id': '00:00:00:00:00:04_low_pulse_speed',
'unit_of_measurement': <UnitOfVolumeFlowRate.LITERS_PER_HOUR: 'L/h'>,
})
# ---
# name: test_setup[select.mock_filter_low_pulse_speed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock filter Low pulse speed',
'options': list([
'400',
'440',
'480',
'515',
'550',
'585',
'620',
'650',
'680',
'710',
'740',
'770',
'800',
'830',
'860',
]),
'unit_of_measurement': <UnitOfVolumeFlowRate.LITERS_PER_HOUR: 'L/h'>,
}),
'context': <ANY>,
'entity_id': 'select.mock_filter_low_pulse_speed',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[select.mock_filter_manual_speed-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'35',
'37.5',
'40.5',
'43',
'45.5',
'48',
'51',
'53.5',
'56',
'59',
'61.5',
'64',
'66.5',
'69.5',
'72',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.mock_filter_manual_speed',
'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': 'Manual speed',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'manual_speed',
'unique_id': '00:00:00:00:00:04_manual_speed',
'unit_of_measurement': <UnitOfFrequency.HERTZ: 'Hz'>,
})
# ---
# name: test_setup[select.mock_filter_manual_speed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock filter Manual speed',
'options': list([
'35',
'37.5',
'40.5',
'43',
'45.5',
'48',
'51',
'53.5',
'56',
'59',
'61.5',
'64',
'66.5',
'69.5',
'72',
]),
'unit_of_measurement': <UnitOfFrequency.HERTZ: 'Hz'>,
}),
'context': <ANY>,
'entity_id': 'select.mock_filter_manual_speed',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[select.mock_filter_night_speed-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'400',
'440',
'480',
'515',
'550',
'585',
'620',
'650',
'680',
'710',
'740',
'770',
'800',
'830',
'860',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.mock_filter_night_speed',
'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': 'Night speed',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'night_speed',
'unique_id': '00:00:00:00:00:04_night_speed',
'unit_of_measurement': <UnitOfVolumeFlowRate.LITERS_PER_HOUR: 'L/h'>,
})
# ---
# name: test_setup[select.mock_filter_night_speed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock filter Night speed',
'options': list([
'400',
'440',
'480',
'515',
'550',
'585',
'620',
'650',
'680',
'710',
'740',
'770',
'800',
'830',
'860',
]),
'unit_of_measurement': <UnitOfVolumeFlowRate.LITERS_PER_HOUR: 'L/h'>,
}),
'context': <ANY>,
'entity_id': 'select.mock_filter_night_speed',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---

View File

@@ -1,5 +1,5 @@
# serializer version: 1
# name: test_setup_classic_vario[sensor.mock_classicvario_current_speed-entry]
# name: test_setup[sensor.mock_classicvario_current_speed-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -34,7 +34,7 @@
'unit_of_measurement': '%',
})
# ---
# name: test_setup_classic_vario[sensor.mock_classicvario_current_speed-state]
# name: test_setup[sensor.mock_classicvario_current_speed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock classicVARIO Current speed',
@@ -48,7 +48,7 @@
'state': 'unknown',
})
# ---
# name: test_setup_classic_vario[sensor.mock_classicvario_error_code-entry]
# name: test_setup[sensor.mock_classicvario_error_code-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -89,7 +89,7 @@
'unit_of_measurement': None,
})
# ---
# name: test_setup_classic_vario[sensor.mock_classicvario_error_code-state]
# name: test_setup[sensor.mock_classicvario_error_code-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
@@ -108,7 +108,7 @@
'state': 'unknown',
})
# ---
# name: test_setup_classic_vario[sensor.mock_classicvario_remaining_hours_until_service-entry]
# name: test_setup[sensor.mock_classicvario_remaining_hours_until_service-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -149,7 +149,7 @@
'unit_of_measurement': <UnitOfTime.DAYS: 'd'>,
})
# ---
# name: test_setup_classic_vario[sensor.mock_classicvario_remaining_hours_until_service-state]
# name: test_setup[sensor.mock_classicvario_remaining_hours_until_service-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
@@ -164,3 +164,112 @@
'state': 'unknown',
})
# ---
# name: test_setup[sensor.mock_filter_current_speed-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'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.mock_filter_current_speed',
'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.FREQUENCY: 'frequency'>,
'original_icon': None,
'original_name': 'Current speed',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'current_speed',
'unique_id': '00:00:00:00:00:04_current_speed',
'unit_of_measurement': <UnitOfFrequency.HERTZ: 'Hz'>,
})
# ---
# name: test_setup[sensor.mock_filter_current_speed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'frequency',
'friendly_name': 'Mock filter Current speed',
'unit_of_measurement': <UnitOfFrequency.HERTZ: 'Hz'>,
}),
'context': <ANY>,
'entity_id': 'sensor.mock_filter_current_speed',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[sensor.mock_filter_remaining_hours_until_service-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'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.mock_filter_remaining_hours_until_service',
'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.DAYS: 'd'>,
}),
}),
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
'original_icon': None,
'original_name': 'Remaining hours until service',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'service_hours',
'unique_id': '00:00:00:00:00:04_service_hours',
'unit_of_measurement': <UnitOfTime.DAYS: 'd'>,
})
# ---
# name: test_setup[sensor.mock_filter_remaining_hours_until_service-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'Mock filter Remaining hours until service',
'unit_of_measurement': <UnitOfTime.DAYS: 'd'>,
}),
'context': <ANY>,
'entity_id': 'sensor.mock_filter_remaining_hours_until_service',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---

View File

@@ -1,5 +1,5 @@
# serializer version: 1
# name: test_setup_classic_vario[switch.mock_classicvario-entry]
# name: test_setup[switch.mock_classicvario-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -34,7 +34,7 @@
'unit_of_measurement': None,
})
# ---
# name: test_setup_classic_vario[switch.mock_classicvario-state]
# name: test_setup[switch.mock_classicvario-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock classicVARIO',
@@ -47,3 +47,51 @@
'state': 'on',
})
# ---
# name: test_setup[switch.mock_filter-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.mock_filter',
'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': None,
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'filter_active',
'unique_id': '00:00:00:00:00:04',
'unit_of_measurement': None,
})
# ---
# name: test_setup[switch.mock_filter-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock filter',
}),
'context': <ANY>,
'entity_id': 'switch.mock_filter',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---

View File

@@ -95,6 +95,102 @@
'state': 'unknown',
})
# ---
# name: test_setup[time.mock_filter_day_start_time-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'time',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'time.mock_filter_day_start_time',
'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': 'Day start time',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'day_start_time',
'unique_id': '00:00:00:00:00:04_day_start_time',
'unit_of_measurement': None,
})
# ---
# name: test_setup[time.mock_filter_day_start_time-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock filter Day start time',
}),
'context': <ANY>,
'entity_id': 'time.mock_filter_day_start_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[time.mock_filter_night_start_time-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'time',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'time.mock_filter_night_start_time',
'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': 'Night start time',
'platform': 'eheimdigital',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'night_start_time',
'unique_id': '00:00:00:00:00:04_night_start_time',
'unit_of_measurement': None,
})
# ---
# name: test_setup[time.mock_filter_night_start_time-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock filter Night start time',
}),
'context': <ANY>,
'entity_id': 'time.mock_filter_night_start_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup[time.mock_heater_day_start_time-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -19,7 +19,6 @@ from .conftest import init_integration
from tests.common import MockConfigEntry, snapshot_platform
@pytest.mark.usefixtures("classic_vario_mock", "heater_mock")
async def test_setup(
hass: HomeAssistant,
eheimdigital_hub_mock: MagicMock,
@@ -48,7 +47,6 @@ async def test_setup(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.usefixtures("classic_vario_mock", "heater_mock")
@pytest.mark.parametrize(
("device_name", "entity_list"),
[
@@ -104,6 +102,23 @@ async def test_setup(
),
],
),
(
"filter_mock",
[
(
"number.mock_filter_low_pulse_duration",
20,
"time_low",
20,
),
(
"number.mock_filter_high_pulse_duration",
20,
"time_high",
20,
),
],
),
],
)
async def test_set_value(
@@ -135,7 +150,6 @@ async def test_set_value(
assert calls[-1][1][0][item[2]] == item[3]
@pytest.mark.usefixtures("classic_vario_mock", "heater_mock")
@pytest.mark.parametrize(
("device_name", "entity_list"),
[
@@ -198,6 +212,32 @@ async def test_set_value(
),
],
),
(
"filter_mock",
[
(
"number.mock_filter_low_pulse_duration",
"filter_data",
"pm_time_low",
20,
20,
),
(
"number.mock_filter_high_pulse_duration",
"filter_data",
"pm_time_high",
20,
20,
),
(
"number.mock_filter_system_led_brightness",
"usrdta",
"sysLED",
20,
20,
),
],
),
],
)
async def test_state_update(

View File

@@ -2,7 +2,7 @@
from unittest.mock import AsyncMock, MagicMock, patch
from eheimdigital.types import FilterMode
from eheimdigital.types import FilterMode, FilterModeProf
import pytest
from syrupy.assertion import SnapshotAssertion
@@ -20,7 +20,6 @@ from .conftest import init_integration
from tests.common import MockConfigEntry, snapshot_platform
@pytest.mark.usefixtures("classic_vario_mock")
async def test_setup(
hass: HomeAssistant,
eheimdigital_hub_mock: MagicMock,
@@ -49,7 +48,6 @@ async def test_setup(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.usefixtures("classic_vario_mock")
@pytest.mark.parametrize(
("device_name", "entity_list"),
[
@@ -64,6 +62,28 @@ async def test_setup(
),
],
),
(
"filter_mock",
[
(
"select.mock_filter_filter_mode",
"constant_flow",
"title",
"START_FILTER_NORMAL_MODE_WITH_COMP",
),
(
"select.mock_filter_manual_speed",
"61.5",
"frequency",
int(61.5 * 100),
),
("select.mock_filter_constant_flow_speed", "440", "flow_rate", 1),
("select.mock_filter_day_speed", "480", "dfs_soll_day", 2),
("select.mock_filter_night_speed", "860", "dfs_soll_night", 14),
("select.mock_filter_high_pulse_speed", "620", "dfs_soll_high", 6),
("select.mock_filter_low_pulse_speed", "770", "dfs_soll_low", 11),
],
),
],
)
async def test_set_value(
@@ -111,6 +131,60 @@ async def test_set_value(
),
],
),
(
"filter_mock",
[
(
"select.mock_filter_filter_mode",
"filter_data",
"pumpMode",
int(FilterModeProf.CONSTANT_FLOW),
"constant_flow",
),
(
"select.mock_filter_manual_speed",
"filter_data",
"freqSoll",
int(61.5 * 100),
"61.5",
),
(
"select.mock_filter_constant_flow_speed",
"filter_data",
"sollStep",
1,
"440",
),
(
"select.mock_filter_day_speed",
"filter_data",
"nm_dfs_soll_day",
2,
"480",
),
(
"select.mock_filter_night_speed",
"filter_data",
"nm_dfs_soll_night",
14,
"860",
),
(
"select.mock_filter_high_pulse_speed",
"filter_data",
"pm_dfs_soll_high",
6,
"620",
),
(
"select.mock_filter_low_pulse_speed",
"filter_data",
"pm_dfs_soll_low",
11,
"770",
),
],
),
],
)
async def test_state_update(

View File

@@ -2,7 +2,7 @@
from unittest.mock import AsyncMock, MagicMock, patch
from eheimdigital.types import EheimDeviceType, FilterErrorCode
from eheimdigital.types import FilterErrorCode
import pytest
from syrupy.assertion import SnapshotAssertion
@@ -15,8 +15,7 @@ from .conftest import init_integration
from tests.common import MockConfigEntry, get_sensor_display_state, snapshot_platform
@pytest.mark.usefixtures("classic_vario_mock")
async def test_setup_classic_vario(
async def test_setup(
hass: HomeAssistant,
eheimdigital_hub_mock: MagicMock,
mock_config_entry: MockConfigEntry,
@@ -35,15 +34,15 @@ async def test_setup_classic_vario(
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
"00:00:00:00:00:03", EheimDeviceType.VERSION_EHEIM_CLASSIC_VARIO
)
await hass.async_block_till_done()
for device in eheimdigital_hub_mock.return_value.devices:
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
device, eheimdigital_hub_mock.return_value.devices[device].device_type
)
await hass.async_block_till_done()
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.usefixtures("classic_vario_mock")
@pytest.mark.parametrize(
("device_name", "entity_list"),
[
@@ -73,6 +72,25 @@ async def test_setup_classic_vario(
),
],
),
(
"filter_mock",
[
(
"sensor.mock_filter_current_speed",
"filter_data",
"freq",
7200,
str(round(72.0, 1)),
),
(
"sensor.mock_filter_remaining_hours_until_service",
"filter_data",
"serviceHour",
100,
str(round(100 / 24, 2)),
),
],
),
],
)
async def test_state_update(

View File

@@ -2,7 +2,6 @@
from unittest.mock import AsyncMock, MagicMock, patch
from eheimdigital.types import EheimDeviceType
import pytest
from syrupy.assertion import SnapshotAssertion
@@ -21,8 +20,7 @@ from .conftest import init_integration
from tests.common import MockConfigEntry, snapshot_platform
@pytest.mark.usefixtures("classic_vario_mock")
async def test_setup_classic_vario(
async def test_setup(
hass: HomeAssistant,
eheimdigital_hub_mock: MagicMock,
mock_config_entry: MockConfigEntry,
@@ -41,10 +39,11 @@ async def test_setup_classic_vario(
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
"00:00:00:00:00:03", EheimDeviceType.VERSION_EHEIM_CLASSIC_VARIO
)
await hass.async_block_till_done()
for device in eheimdigital_hub_mock.return_value.devices:
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
device, eheimdigital_hub_mock.return_value.devices[device].device_type
)
await hass.async_block_till_done()
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@@ -52,37 +51,45 @@ async def test_setup_classic_vario(
@pytest.mark.parametrize(
("service", "active"), [(SERVICE_TURN_OFF, False), (SERVICE_TURN_ON, True)]
)
@pytest.mark.parametrize(
("device_name", "entity_id", "property_name"),
[
("classic_vario_mock", "switch.mock_classicvario", "filterActive"),
("filter_mock", "switch.mock_filter", "active"),
],
)
async def test_turn_on_off(
hass: HomeAssistant,
eheimdigital_hub_mock: MagicMock,
mock_config_entry: MockConfigEntry,
classic_vario_mock: MagicMock,
device_name: str,
entity_id: str,
property_name: str,
service: str,
active: bool,
request: pytest.FixtureRequest,
) -> None:
"""Test turning on/off the switch."""
device: MagicMock = request.getfixturevalue(device_name)
await init_integration(hass, mock_config_entry)
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
"00:00:00:00:00:03", EheimDeviceType.VERSION_EHEIM_CLASSIC_VARIO
device.mac_address, device.device_type
)
await hass.async_block_till_done()
await hass.services.async_call(
SWITCH_DOMAIN,
service,
{ATTR_ENTITY_ID: "switch.mock_classicvario"},
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
calls = [
call for call in classic_vario_mock.hub.mock_calls if call[0] == "send_packet"
]
assert len(calls) == 1
assert calls[0][1][0].get("filterActive") == int(active)
calls = [call for call in device.hub.mock_calls if call[0] == "send_packet"]
assert calls[-1][1][0][property_name] == int(active)
@pytest.mark.usefixtures("classic_vario_mock")
@pytest.mark.usefixtures("classic_vario_mock", "filter_mock")
@pytest.mark.parametrize(
("device_name", "entity_list"),
[
@@ -105,6 +112,25 @@ async def test_turn_on_off(
),
],
),
(
"filter_mock",
[
(
"switch.mock_filter",
"filter_data",
"filterActive",
1,
"on",
),
(
"switch.mock_filter",
"filter_data",
"filterActive",
0,
"off",
),
],
),
],
)
async def test_state_update(

View File

@@ -20,7 +20,6 @@ from .conftest import init_integration
from tests.common import MockConfigEntry, snapshot_platform
@pytest.mark.usefixtures("classic_vario_mock", "heater_mock")
async def test_setup(
hass: HomeAssistant,
eheimdigital_hub_mock: MagicMock,
@@ -49,7 +48,6 @@ async def test_setup(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.usefixtures("classic_vario_mock", "heater_mock")
@pytest.mark.parametrize(
("device_name", "entity_list"),
[
@@ -87,6 +85,23 @@ async def test_setup(
),
],
),
(
"filter_mock",
[
(
"time.mock_filter_day_start_time",
time(9, 0, tzinfo=timezone(timedelta(hours=1))),
"end_time_night_mode",
9 * 60,
),
(
"time.mock_filter_night_start_time",
time(19, 0, tzinfo=timezone(timedelta(hours=1))),
"start_time_night_mode",
19 * 60,
),
],
),
],
)
async def test_set_value(
@@ -118,7 +133,6 @@ async def test_set_value(
assert calls[-1][1][0][item[2]] == item[3]
@pytest.mark.usefixtures("classic_vario_mock", "heater_mock")
@pytest.mark.parametrize(
("device_name", "entity_list"),
[
@@ -160,6 +174,25 @@ async def test_set_value(
),
],
),
(
"filter_mock",
[
(
"time.mock_filter_day_start_time",
"filter_data",
"end_time_night_mode",
540,
time(9, 0, tzinfo=timezone(timedelta(hours=1))).isoformat(),
),
(
"time.mock_filter_night_start_time",
"filter_data",
"start_time_night_mode",
1320,
time(22, 0, tzinfo=timezone(timedelta(hours=1))).isoformat(),
),
],
),
],
)
async def test_state_update(

View File

@@ -22,16 +22,12 @@ from homeassistant.components.hassio import (
)
from homeassistant.components.hassio.config import STORAGE_KEY
from homeassistant.components.hassio.const import (
ATTR_WS_EVENT,
EVENT_NETWORK_CHANGED,
EVENT_SUPERVISOR_EVENT,
HASSIO_UPDATE_INTERVAL,
REQUEST_REFRESH_DELAY,
)
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, issue_registry as ir
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.hassio import is_hassio
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
@@ -1392,26 +1388,3 @@ async def test_deprecated_installation_issue_supported_board(
await hass.async_block_till_done()
assert len(issue_registry.issues) == 0
async def test_network_change_event(hass: HomeAssistant) -> None:
"""Test network change event from supervisor triggers network notification."""
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.hassio.HassIO.is_connected",
return_value=True,
),
patch(
"homeassistant.components.network.async_notify_network_change"
) as mock_notify,
):
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
async_dispatcher_send(
hass, EVENT_SUPERVISOR_EVENT, {ATTR_WS_EVENT: EVENT_NETWORK_CHANGED}
)
await hass.async_block_till_done()
mock_notify.assert_called_once_with(hass)

View File

@@ -850,77 +850,3 @@ async def test_repair_docker_host_network_without_host_networking(
assert (issue := issue_registry.async_get_issue(DOMAIN, "docker_host_network"))
assert issue == snapshot
@pytest.mark.parametrize(
"mock_socket",
[
[
(MDNS_TARGET_IP, _mock_cond_socket(NO_LOOPBACK_IPADDR)),
]
],
indirect=True,
)
@pytest.mark.usefixtures("mock_socket")
async def test_network_change_callbacks(hass: HomeAssistant) -> None:
"""Test network change callbacks are called only when adapters change."""
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done()
callback_calls = []
def mock_callback(adapters):
"""Mock callback to track calls."""
callback_calls.append(adapters)
# Register callback
unregister = network.async_register_network_change_callback(hass, mock_callback)
# Get initial adapters
network_obj = hass.data[DOMAIN]
initial_adapters = network_obj.adapters
# Trigger network change with same adapters - callback should NOT be called
with patch(
"homeassistant.components.network.util.async_load_adapters",
return_value=initial_adapters,
):
await network.async_notify_network_change(hass)
await hass.async_block_till_done()
assert len(callback_calls) == 0
# Trigger network change with different adapters - callback SHOULD be called
new_adapters = [
{
"index": 2,
"auto": True,
"default": True,
"enabled": True,
"ipv4": [{"address": "192.168.2.10", "network_prefix": 24}],
"ipv6": [],
"name": "eth1",
}
]
with patch(
"homeassistant.components.network.util.async_load_adapters",
return_value=new_adapters,
):
await network.async_notify_network_change(hass)
await hass.async_block_till_done()
assert len(callback_calls) == 1
assert callback_calls[0] == new_adapters
# Unregister callback
unregister()
# Trigger another change - callback should NOT be called
with patch(
"homeassistant.components.network.util.async_load_adapters",
return_value=initial_adapters,
):
await network.async_notify_network_change(hass)
await hass.async_block_till_done()
assert len(callback_calls) == 1

View File

@@ -2,8 +2,24 @@
import json
from typing import Any
from unittest.mock import patch
from tests.common import load_fixture
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, load_fixture
async def setup_platform(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Set up the NINA platform."""
with patch(
"pynina.baseApi.BaseAPI._makeRequest",
wraps=mocked_request_function,
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
def mocked_request_function(url: str) -> dict[str, Any]:

View File

@@ -0,0 +1,257 @@
# serializer version: 1
# name: test_sensors[binary_sensor.nina_warning_aach_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.nina_warning_aach_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.SAFETY: 'safety'>,
'original_icon': None,
'original_name': 'Warning: Aach 1',
'platform': 'nina',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '095760000000-1',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[binary_sensor.nina_warning_aach_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'affected_areas': 'Gemeinde Oberreichenbach, Gemeinde Neuweiler, Stadt Nagold, Stadt Neubulach, Gemeinde Schömberg, Gemeinde Simmersfeld, Gemeinde Simmozheim, Gemeinde Rohrdorf, Gemeinde Ostelsheim, Gemeinde Ebhausen, Gemeinde Egenhausen, Gemeinde Dobel, Stadt Bad Liebenzell, Stadt Solingen, Stadt Haiterbach, Stadt Bad Herrenalb, Gemeinde Höfen an der Enz, Gemeinde Gechingen, Gemeinde Enzklösterle, Gemeinde Gutach (Schwarzwaldbahn) und 3392 weitere.',
'description': 'Es treten Sturmböen mit Geschwindigkeiten zwischen 70 km/h (20m/s, 38kn, Bft 8) und 85 km/h (24m/s, 47kn, Bft 9) aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit schweren Sturmböen bis 90 km/h (25m/s, 48kn, Bft 10) gerechnet werden.',
'device_class': 'safety',
'expires': '3021-11-22T05:19:00+01:00',
'friendly_name': 'NINA Warning: Aach 1',
'headline': 'Ausfall Notruf 112',
'id': 'mow.DE-NW-BN-SE030-20201014-30-000',
'recommended_actions': '',
'sender': 'Deutscher Wetterdienst',
'sent': '2021-10-11T05:20:00+01:00',
'severity': 'Minor',
'start': '2021-11-01T05:20:00+01:00',
'web': 'https://www.wettergefahren.de',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.nina_warning_aach_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_sensors[binary_sensor.nina_warning_aach_2-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.nina_warning_aach_2',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.SAFETY: 'safety'>,
'original_icon': None,
'original_name': 'Warning: Aach 2',
'platform': 'nina',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '095760000000-2',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[binary_sensor.nina_warning_aach_2-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'safety',
'friendly_name': 'NINA Warning: Aach 2',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.nina_warning_aach_2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_sensors[binary_sensor.nina_warning_aach_3-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.nina_warning_aach_3',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.SAFETY: 'safety'>,
'original_icon': None,
'original_name': 'Warning: Aach 3',
'platform': 'nina',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '095760000000-3',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[binary_sensor.nina_warning_aach_3-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'safety',
'friendly_name': 'NINA Warning: Aach 3',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.nina_warning_aach_3',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_sensors[binary_sensor.nina_warning_aach_4-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.nina_warning_aach_4',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.SAFETY: 'safety'>,
'original_icon': None,
'original_name': 'Warning: Aach 4',
'platform': 'nina',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '095760000000-4',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[binary_sensor.nina_warning_aach_4-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'safety',
'friendly_name': 'NINA Warning: Aach 4',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.nina_warning_aach_4',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_sensors[binary_sensor.nina_warning_aach_5-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.nina_warning_aach_5',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.SAFETY: 'safety'>,
'original_icon': None,
'original_name': 'Warning: Aach 5',
'platform': 'nina',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '095760000000-5',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[binary_sensor.nina_warning_aach_5-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'safety',
'friendly_name': 'NINA Warning: Aach 5',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.nina_warning_aach_5',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---

View File

@@ -3,40 +3,17 @@
from __future__ import annotations
from typing import Any
from unittest.mock import patch
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.nina.const import (
ATTR_AFFECTED_AREAS,
ATTR_DESCRIPTION,
ATTR_EXPIRES,
ATTR_HEADLINE,
ATTR_ID,
ATTR_RECOMMENDED_ACTIONS,
ATTR_SENDER,
ATTR_SENT,
ATTR_SEVERITY,
ATTR_START,
ATTR_WEB,
DOMAIN,
)
from homeassistant.config_entries import ConfigEntryState
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.nina.const import ATTR_HEADLINE, DOMAIN
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import mocked_request_function
from . import setup_platform
from tests.common import MockConfigEntry
ENTRY_DATA: dict[str, Any] = {
"slots": 5,
"regions": {"083350000000": "Aach, Stadt"},
"filters": {
"headline_filter": ".*corona.*",
"area_filter": ".*",
},
}
from tests.common import MockConfigEntry, snapshot_platform
ENTRY_DATA_NO_CORONA: dict[str, Any] = {
"slots": 5,
@@ -47,7 +24,7 @@ ENTRY_DATA_NO_CORONA: dict[str, Any] = {
},
}
ENTRY_DATA_NO_AREA: dict[str, Any] = {
ENTRY_DATA_SPECIFIC_AREA: dict[str, Any] = {
"slots": 5,
"regions": {"083350000000": "Aach, Stadt"},
"filters": {
@@ -57,321 +34,93 @@ ENTRY_DATA_NO_AREA: dict[str, Any] = {
}
async def test_sensors(hass: HomeAssistant, entity_registry: er.EntityRegistry) -> None:
async def test_sensors(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test the creation and values of the NINA sensors."""
with patch(
"pynina.baseApi.BaseAPI._makeRequest",
wraps=mocked_request_function,
):
conf_entry: MockConfigEntry = MockConfigEntry(
domain=DOMAIN, title="NINA", data=ENTRY_DATA, version=1, minor_version=3
)
conf_entry.add_to_hass(hass)
await hass.config_entries.async_setup(conf_entry.entry_id)
await hass.async_block_till_done()
assert conf_entry.state is ConfigEntryState.LOADED
state_w1 = hass.states.get("binary_sensor.nina_warning_aach_stadt_1")
entry_w1 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_1")
assert state_w1.state == STATE_ON
assert state_w1.attributes.get(ATTR_HEADLINE) == "Ausfall Notruf 112"
assert (
state_w1.attributes.get(ATTR_DESCRIPTION)
== "Es treten Sturmböen mit Geschwindigkeiten zwischen 70 km/h (20m/s, 38kn, Bft 8) und 85 km/h (24m/s, 47kn, Bft 9) aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit schweren Sturmböen bis 90 km/h (25m/s, 48kn, Bft 10) gerechnet werden."
)
assert state_w1.attributes.get(ATTR_SENDER) == "Deutscher Wetterdienst"
assert state_w1.attributes.get(ATTR_SEVERITY) == "Minor"
assert state_w1.attributes.get(ATTR_RECOMMENDED_ACTIONS) == ""
assert state_w1.attributes.get(ATTR_WEB) == "https://www.wettergefahren.de"
assert (
state_w1.attributes.get(ATTR_AFFECTED_AREAS)
== "Gemeinde Oberreichenbach, Gemeinde Neuweiler, Stadt Nagold, Stadt Neubulach, Gemeinde Schömberg, Gemeinde Simmersfeld, Gemeinde Simmozheim, Gemeinde Rohrdorf, Gemeinde Ostelsheim, Gemeinde Ebhausen, Gemeinde Egenhausen, Gemeinde Dobel, Stadt Bad Liebenzell, Stadt Solingen, Stadt Haiterbach, Stadt Bad Herrenalb, Gemeinde Höfen an der Enz, Gemeinde Gechingen, Gemeinde Enzklösterle, Gemeinde Gutach (Schwarzwaldbahn) und 3392 weitere."
)
assert state_w1.attributes.get(ATTR_ID) == "mow.DE-NW-BN-SE030-20201014-30-000"
assert state_w1.attributes.get(ATTR_SENT) == "2021-10-11T05:20:00+01:00"
assert state_w1.attributes.get(ATTR_START) == "2021-11-01T05:20:00+01:00"
assert state_w1.attributes.get(ATTR_EXPIRES) == "3021-11-22T05:19:00+01:00"
assert entry_w1.unique_id == "083350000000-1"
assert state_w1.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
state_w2 = hass.states.get("binary_sensor.nina_warning_aach_stadt_2")
entry_w2 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_2")
assert state_w2.state == STATE_OFF
assert state_w2.attributes.get(ATTR_HEADLINE) is None
assert state_w2.attributes.get(ATTR_DESCRIPTION) is None
assert state_w2.attributes.get(ATTR_SENDER) is None
assert state_w2.attributes.get(ATTR_SEVERITY) is None
assert state_w2.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
assert state_w2.attributes.get(ATTR_WEB) is None
assert state_w2.attributes.get(ATTR_AFFECTED_AREAS) is None
assert state_w2.attributes.get(ATTR_ID) is None
assert state_w2.attributes.get(ATTR_SENT) is None
assert state_w2.attributes.get(ATTR_START) is None
assert state_w2.attributes.get(ATTR_EXPIRES) is None
assert entry_w2.unique_id == "083350000000-2"
assert state_w2.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
state_w3 = hass.states.get("binary_sensor.nina_warning_aach_stadt_3")
entry_w3 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_3")
assert state_w3.state == STATE_OFF
assert state_w3.attributes.get(ATTR_HEADLINE) is None
assert state_w3.attributes.get(ATTR_DESCRIPTION) is None
assert state_w3.attributes.get(ATTR_SENDER) is None
assert state_w3.attributes.get(ATTR_SEVERITY) is None
assert state_w3.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
assert state_w3.attributes.get(ATTR_WEB) is None
assert state_w3.attributes.get(ATTR_AFFECTED_AREAS) is None
assert state_w3.attributes.get(ATTR_ID) is None
assert state_w3.attributes.get(ATTR_SENT) is None
assert state_w3.attributes.get(ATTR_START) is None
assert state_w3.attributes.get(ATTR_EXPIRES) is None
assert entry_w3.unique_id == "083350000000-3"
assert state_w3.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
state_w4 = hass.states.get("binary_sensor.nina_warning_aach_stadt_4")
entry_w4 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_4")
assert state_w4.state == STATE_OFF
assert state_w4.attributes.get(ATTR_HEADLINE) is None
assert state_w4.attributes.get(ATTR_DESCRIPTION) is None
assert state_w4.attributes.get(ATTR_SENDER) is None
assert state_w4.attributes.get(ATTR_SEVERITY) is None
assert state_w4.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
assert state_w4.attributes.get(ATTR_WEB) is None
assert state_w4.attributes.get(ATTR_AFFECTED_AREAS) is None
assert state_w4.attributes.get(ATTR_ID) is None
assert state_w4.attributes.get(ATTR_SENT) is None
assert state_w4.attributes.get(ATTR_START) is None
assert state_w4.attributes.get(ATTR_EXPIRES) is None
assert entry_w4.unique_id == "083350000000-4"
assert state_w4.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
state_w5 = hass.states.get("binary_sensor.nina_warning_aach_stadt_5")
entry_w5 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_5")
assert state_w5.state == STATE_OFF
assert state_w5.attributes.get(ATTR_HEADLINE) is None
assert state_w5.attributes.get(ATTR_DESCRIPTION) is None
assert state_w5.attributes.get(ATTR_SENDER) is None
assert state_w5.attributes.get(ATTR_SEVERITY) is None
assert state_w5.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
assert state_w5.attributes.get(ATTR_WEB) is None
assert state_w5.attributes.get(ATTR_AFFECTED_AREAS) is None
assert state_w5.attributes.get(ATTR_ID) is None
assert state_w5.attributes.get(ATTR_SENT) is None
assert state_w5.attributes.get(ATTR_START) is None
assert state_w5.attributes.get(ATTR_EXPIRES) is None
assert entry_w5.unique_id == "083350000000-5"
assert state_w5.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
await setup_platform(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_sensors_without_corona_filter(
hass: HomeAssistant, entity_registry: er.EntityRegistry
hass: HomeAssistant, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion
) -> None:
"""Test the creation and values of the NINA sensors without the corona filter."""
with patch(
"pynina.baseApi.BaseAPI._makeRequest",
wraps=mocked_request_function,
):
conf_entry: MockConfigEntry = MockConfigEntry(
domain=DOMAIN,
title="NINA",
data=ENTRY_DATA_NO_CORONA,
version=1,
minor_version=3,
)
conf_entry.add_to_hass(hass)
conf_entry: MockConfigEntry = MockConfigEntry(
domain=DOMAIN,
title="NINA",
data=ENTRY_DATA_NO_CORONA,
version=1,
minor_version=3,
)
conf_entry.add_to_hass(hass)
await hass.config_entries.async_setup(conf_entry.entry_id)
await hass.async_block_till_done()
await setup_platform(hass, conf_entry)
assert conf_entry.state is ConfigEntryState.LOADED
state_w1 = hass.states.get("binary_sensor.nina_warning_aach_stadt_1")
state_w1 = hass.states.get("binary_sensor.nina_warning_aach_stadt_1")
entry_w1 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_1")
assert state_w1.state == STATE_ON
assert (
state_w1.attributes.get(ATTR_HEADLINE)
== "Corona-Verordnung des Landes: Warnstufe durch Landesgesundheitsamt ausgerufen"
)
assert state_w1.state == STATE_ON
assert (
state_w1.attributes.get(ATTR_HEADLINE)
== "Corona-Verordnung des Landes: Warnstufe durch Landesgesundheitsamt ausgerufen"
)
assert (
state_w1.attributes.get(ATTR_DESCRIPTION)
== "Die Zahl der mit dem Corona-Virus infizierten Menschen steigt gegenwärtig stark an. Es wächst daher die Gefahr einer weiteren Verbreitung der Infektion und - je nach Einzelfall - auch von schweren Erkrankungen."
)
assert state_w1.attributes.get(ATTR_SENDER) == ""
assert state_w1.attributes.get(ATTR_SEVERITY) == "Minor"
assert (
state_w1.attributes.get(ATTR_RECOMMENDED_ACTIONS)
== "Waschen sich regelmäßig und gründlich die Hände."
)
assert state_w1.attributes.get(ATTR_WEB) == ""
assert (
state_w1.attributes.get(ATTR_AFFECTED_AREAS)
== "Bundesland: Freie Hansestadt Bremen, Land Berlin, Land Hessen, Land Nordrhein-Westfalen, Land Brandenburg, Freistaat Bayern, Land Mecklenburg-Vorpommern, Land Rheinland-Pfalz, Freistaat Sachsen, Land Schleswig-Holstein, Freie und Hansestadt Hamburg, Freistaat Thüringen, Land Niedersachsen, Land Saarland, Land Sachsen-Anhalt, Land Baden-Württemberg"
)
assert state_w1.attributes.get(ATTR_ID) == "mow.DE-BW-S-SE018-20211102-18-001"
assert state_w1.attributes.get(ATTR_SENT) == "2021-11-02T20:07:16+01:00"
assert state_w1.attributes.get(ATTR_START) == ""
assert state_w1.attributes.get(ATTR_EXPIRES) == ""
state_w2 = hass.states.get("binary_sensor.nina_warning_aach_stadt_2")
assert entry_w1.unique_id == "083350000000-1"
assert state_w1.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
assert state_w2.state == STATE_ON
assert state_w2.attributes.get(ATTR_HEADLINE) == "Ausfall Notruf 112"
state_w2 = hass.states.get("binary_sensor.nina_warning_aach_stadt_2")
entry_w2 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_2")
state_w3 = hass.states.get("binary_sensor.nina_warning_aach_stadt_3")
assert state_w2.state == STATE_ON
assert state_w2.attributes.get(ATTR_HEADLINE) == "Ausfall Notruf 112"
assert (
state_w2.attributes.get(ATTR_DESCRIPTION)
== "Es treten Sturmböen mit Geschwindigkeiten zwischen 70 km/h (20m/s, 38kn, Bft 8) und 85 km/h (24m/s, 47kn, Bft 9) aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit schweren Sturmböen bis 90 km/h (25m/s, 48kn, Bft 10) gerechnet werden."
)
assert (
state_w2.attributes.get(ATTR_AFFECTED_AREAS)
== "Gemeinde Oberreichenbach, Gemeinde Neuweiler, Stadt Nagold, Stadt Neubulach, Gemeinde Schömberg, Gemeinde Simmersfeld, Gemeinde Simmozheim, Gemeinde Rohrdorf, Gemeinde Ostelsheim, Gemeinde Ebhausen, Gemeinde Egenhausen, Gemeinde Dobel, Stadt Bad Liebenzell, Stadt Solingen, Stadt Haiterbach, Stadt Bad Herrenalb, Gemeinde Höfen an der Enz, Gemeinde Gechingen, Gemeinde Enzklösterle, Gemeinde Gutach (Schwarzwaldbahn) und 3392 weitere."
)
assert state_w2.attributes.get(ATTR_SENDER) == "Deutscher Wetterdienst"
assert state_w2.attributes.get(ATTR_SEVERITY) == "Minor"
assert state_w2.attributes.get(ATTR_RECOMMENDED_ACTIONS) == ""
assert state_w2.attributes.get(ATTR_WEB) == "https://www.wettergefahren.de"
assert state_w2.attributes.get(ATTR_ID) == "mow.DE-NW-BN-SE030-20201014-30-000"
assert state_w2.attributes.get(ATTR_SENT) == "2021-10-11T05:20:00+01:00"
assert state_w2.attributes.get(ATTR_START) == "2021-11-01T05:20:00+01:00"
assert state_w2.attributes.get(ATTR_EXPIRES) == "3021-11-22T05:19:00+01:00"
assert state_w3.state == STATE_OFF
assert entry_w2.unique_id == "083350000000-2"
assert state_w2.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
state_w4 = hass.states.get("binary_sensor.nina_warning_aach_stadt_4")
state_w3 = hass.states.get("binary_sensor.nina_warning_aach_stadt_3")
entry_w3 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_3")
assert state_w4.state == STATE_OFF
assert state_w3.state == STATE_OFF
assert state_w3.attributes.get(ATTR_HEADLINE) is None
assert state_w3.attributes.get(ATTR_DESCRIPTION) is None
assert state_w3.attributes.get(ATTR_SENDER) is None
assert state_w3.attributes.get(ATTR_SEVERITY) is None
assert state_w3.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
assert state_w3.attributes.get(ATTR_WEB) is None
assert state_w3.attributes.get(ATTR_AFFECTED_AREAS) is None
assert state_w3.attributes.get(ATTR_ID) is None
assert state_w3.attributes.get(ATTR_SENT) is None
assert state_w3.attributes.get(ATTR_START) is None
assert state_w3.attributes.get(ATTR_EXPIRES) is None
state_w5 = hass.states.get("binary_sensor.nina_warning_aach_stadt_5")
assert entry_w3.unique_id == "083350000000-3"
assert state_w3.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
state_w4 = hass.states.get("binary_sensor.nina_warning_aach_stadt_4")
entry_w4 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_4")
assert state_w4.state == STATE_OFF
assert state_w4.attributes.get(ATTR_HEADLINE) is None
assert state_w4.attributes.get(ATTR_DESCRIPTION) is None
assert state_w4.attributes.get(ATTR_SENDER) is None
assert state_w4.attributes.get(ATTR_SEVERITY) is None
assert state_w4.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
assert state_w4.attributes.get(ATTR_WEB) is None
assert state_w4.attributes.get(ATTR_AFFECTED_AREAS) is None
assert state_w4.attributes.get(ATTR_ID) is None
assert state_w4.attributes.get(ATTR_SENT) is None
assert state_w4.attributes.get(ATTR_START) is None
assert state_w4.attributes.get(ATTR_EXPIRES) is None
assert entry_w4.unique_id == "083350000000-4"
assert state_w4.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
state_w5 = hass.states.get("binary_sensor.nina_warning_aach_stadt_5")
entry_w5 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_5")
assert state_w5.state == STATE_OFF
assert state_w5.attributes.get(ATTR_HEADLINE) is None
assert state_w5.attributes.get(ATTR_DESCRIPTION) is None
assert state_w5.attributes.get(ATTR_SENDER) is None
assert state_w5.attributes.get(ATTR_SEVERITY) is None
assert state_w5.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
assert state_w5.attributes.get(ATTR_WEB) is None
assert state_w5.attributes.get(ATTR_AFFECTED_AREAS) is None
assert state_w5.attributes.get(ATTR_ID) is None
assert state_w5.attributes.get(ATTR_SENT) is None
assert state_w5.attributes.get(ATTR_START) is None
assert state_w5.attributes.get(ATTR_EXPIRES) is None
assert entry_w5.unique_id == "083350000000-5"
assert state_w5.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
assert state_w5.state == STATE_OFF
async def test_sensors_with_area_filter(
hass: HomeAssistant, entity_registry: er.EntityRegistry
hass: HomeAssistant, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion
) -> None:
"""Test the creation and values of the NINA sensors with an area filter."""
"""Test the creation and values of the NINA sensors with a restrictive area filter."""
with patch(
"pynina.baseApi.BaseAPI._makeRequest",
wraps=mocked_request_function,
):
conf_entry: MockConfigEntry = MockConfigEntry(
domain=DOMAIN,
title="NINA",
data=ENTRY_DATA_NO_AREA,
version=1,
minor_version=3,
)
conf_entry.add_to_hass(hass)
conf_entry: MockConfigEntry = MockConfigEntry(
domain=DOMAIN,
title="NINA",
data=ENTRY_DATA_SPECIFIC_AREA,
version=1,
minor_version=3,
)
conf_entry.add_to_hass(hass)
await hass.config_entries.async_setup(conf_entry.entry_id)
await hass.async_block_till_done()
await setup_platform(hass, conf_entry)
assert conf_entry.state is ConfigEntryState.LOADED
state_w1 = hass.states.get("binary_sensor.nina_warning_aach_stadt_1")
state_w1 = hass.states.get("binary_sensor.nina_warning_aach_stadt_1")
entry_w1 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_1")
assert state_w1.state == STATE_ON
assert state_w1.attributes.get(ATTR_HEADLINE) == "Ausfall Notruf 112"
assert state_w1.state == STATE_ON
state_w2 = hass.states.get("binary_sensor.nina_warning_aach_stadt_2")
assert entry_w1.unique_id == "083350000000-1"
assert state_w1.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
assert state_w2.state == STATE_OFF
state_w2 = hass.states.get("binary_sensor.nina_warning_aach_stadt_2")
entry_w2 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_2")
state_w3 = hass.states.get("binary_sensor.nina_warning_aach_stadt_3")
assert state_w2.state == STATE_OFF
assert state_w3.state == STATE_OFF
assert entry_w2.unique_id == "083350000000-2"
assert state_w2.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
state_w4 = hass.states.get("binary_sensor.nina_warning_aach_stadt_4")
state_w3 = hass.states.get("binary_sensor.nina_warning_aach_stadt_3")
entry_w3 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_3")
assert state_w4.state == STATE_OFF
assert state_w3.state == STATE_OFF
state_w5 = hass.states.get("binary_sensor.nina_warning_aach_stadt_5")
assert entry_w3.unique_id == "083350000000-3"
assert state_w3.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
state_w4 = hass.states.get("binary_sensor.nina_warning_aach_stadt_4")
entry_w4 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_4")
assert state_w4.state == STATE_OFF
assert entry_w4.unique_id == "083350000000-4"
assert state_w4.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
state_w5 = hass.states.get("binary_sensor.nina_warning_aach_stadt_5")
entry_w5 = entity_registry.async_get("binary_sensor.nina_warning_aach_stadt_5")
assert state_w5.state == STATE_OFF
assert entry_w5.unique_id == "083350000000-5"
assert state_w5.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
assert state_w5.state == STATE_OFF

View File

@@ -3,7 +3,6 @@
from __future__ import annotations
from copy import deepcopy
import json
from typing import Any
from unittest.mock import AsyncMock, patch
@@ -28,14 +27,10 @@ from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import entity_registry as er
from . import mocked_request_function
from . import mocked_request_function, setup_platform
from .const import DUMMY_CONFIG_ENTRY, DUMMY_USER_INPUT
from tests.common import MockConfigEntry, load_fixture
DUMMY_RESPONSE_REGIONS: dict[str, Any] = json.loads(
load_fixture("sample_regions.json", "nina")
)
from tests.common import MockConfigEntry
def assert_dummy_entry_created(result: dict[str, Any]) -> None:
@@ -141,15 +136,15 @@ async def test_options_flow_init(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test config flow options."""
await setup_platform(hass, mock_config_entry)
with (
patch(
"pynina.baseApi.BaseAPI._makeRequest",
wraps=mocked_request_function,
),
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.options.async_init(
mock_config_entry.entry_id
)
@@ -195,15 +190,15 @@ async def test_options_flow_with_no_selection(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test config flow options with no selection."""
await setup_platform(hass, mock_config_entry)
with (
patch(
"pynina.baseApi.BaseAPI._makeRequest",
wraps=mocked_request_function,
),
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.options.async_init(
mock_config_entry.entry_id
)
@@ -264,13 +259,13 @@ async def test_options_flow_connection_error(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test config flow options but no connection."""
await setup_platform(hass, mock_config_entry)
with patch(
"pynina.baseApi.BaseAPI._makeRequest",
side_effect=ApiError("Could not connect to Api"),
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.options.async_init(
mock_config_entry.entry_id
)
@@ -283,15 +278,15 @@ async def test_options_flow_unexpected_exception(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test config flow options but with an unexpected exception."""
await setup_platform(hass, mock_config_entry)
with (
patch(
"pynina.baseApi.BaseAPI._makeRequest",
side_effect=Exception("DUMMY"),
),
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.options.async_init(
mock_config_entry.entry_id
)
@@ -313,15 +308,14 @@ async def test_options_flow_entity_removal(
)
config_entry.add_to_hass(hass)
await setup_platform(hass, config_entry)
with (
patch(
"pynina.baseApi.BaseAPI._makeRequest",
wraps=mocked_request_function,
),
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.options.async_init(config_entry.entry_id)
result = await hass.config_entries.options.async_configure(

View File

@@ -158,6 +158,57 @@
'state': '13027.0',
})
# ---
# name: test_sensors[e1][sensor.ruuvi_air_884f_indoor_air_quality_score-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.ruuvi_air_884f_indoor_air_quality_score',
'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': 'Indoor air quality score',
'platform': 'ruuvitag_ble',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'iaqs',
'unique_id': '01:03:05:07:12:34-iaqs',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[e1][sensor.ruuvi_air_884f_indoor_air_quality_score-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Ruuvi Air 884F Indoor air quality score',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'context': <ANY>,
'entity_id': 'sensor.ruuvi_air_884f_indoor_air_quality_score',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '81',
})
# ---
# name: test_sensors[e1][sensor.ruuvi_air_884f_nox_index-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -1329,6 +1380,57 @@
'state': '13027',
})
# ---
# name: test_sensors[v6][sensor.ruuvitag_884f_indoor_air_quality_score-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.ruuvitag_884f_indoor_air_quality_score',
'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': 'Indoor air quality score',
'platform': 'ruuvitag_ble',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'iaqs',
'unique_id': '01:03:05:07:12:34-iaqs',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[v6][sensor.ruuvitag_884f_indoor_air_quality_score-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'RuuviTag 884F Indoor air quality score',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'context': <ANY>,
'entity_id': 'sensor.ruuvitag_884f_indoor_air_quality_score',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '81',
})
# ---
# name: test_sensors[v6][sensor.ruuvitag_884f_nox_index-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -383,6 +383,13 @@ async def test_smoke_co_notification_sensors(
assert entity_entry
assert entity_entry.entity_category == EntityCategory.DIAGNOSTIC
# Test that no idle states are created as entities
entity_id = "binary_sensor.zcombo_g_smoke_co_alarm_idle"
state = hass.states.get(entity_id)
assert state is None
entity_entry = entity_registry.async_get(entity_id)
assert entity_entry is None
# Test state updates for smoke alarm
event = Event(
type="value updated",