mirror of
https://github.com/home-assistant/core.git
synced 2026-01-20 22:46:58 +01:00
Compare commits
40 Commits
simplify_b
...
homevolt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
205bc0456f | ||
|
|
1730479c8d | ||
|
|
bc28c8fd3c | ||
|
|
c3616fd5df | ||
|
|
6b97f2ac06 | ||
|
|
deefcbcbe4 | ||
|
|
e84aeb9f99 | ||
|
|
ade3d8a657 | ||
|
|
a65d9032ff | ||
|
|
b950a4eaf4 | ||
|
|
3fe91751f5 | ||
|
|
6ee58b96ca | ||
|
|
d1404e7905 | ||
|
|
7c34191813 | ||
|
|
7540d04779 | ||
|
|
d828130670 | ||
|
|
2ec6c08bd7 | ||
|
|
48852bab7a | ||
|
|
7d370f4513 | ||
|
|
9d97791faf | ||
|
|
4fe8982b68 | ||
|
|
5aa32491c8 | ||
|
|
dc2cd2246b | ||
|
|
181037820b | ||
|
|
6cf15bf70c | ||
|
|
5a34c31e42 | ||
|
|
9dcc86f12e | ||
|
|
04429a6eef | ||
|
|
51e2506afb | ||
|
|
e49e5c7c40 | ||
|
|
b8dfc523da | ||
|
|
a25fbf57ef | ||
|
|
dac22002b0 | ||
|
|
e61f00a3ae | ||
|
|
14a67c6b5d | ||
|
|
90ae81f02b | ||
|
|
a741f214da | ||
|
|
21d0bd3ce2 | ||
|
|
d9c1f4850a | ||
|
|
335994af7e |
2
CODEOWNERS
generated
2
CODEOWNERS
generated
@@ -711,6 +711,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/homematic/ @pvizeli
|
||||
/homeassistant/components/homematicip_cloud/ @hahn-th
|
||||
/tests/components/homematicip_cloud/ @hahn-th
|
||||
/homeassistant/components/homevolt/ @danielhiversen
|
||||
/tests/components/homevolt/ @danielhiversen
|
||||
/homeassistant/components/homewizard/ @DCSBL
|
||||
/tests/components/homewizard/ @DCSBL
|
||||
/homeassistant/components/honeywell/ @rdfurman @mkmer
|
||||
|
||||
@@ -52,7 +52,7 @@ class AdGuardHomeEntity(Entity):
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device information about this AdGuard Home instance."""
|
||||
if self._entry.source == SOURCE_HASSIO:
|
||||
config_url = "homeassistant://hassio/ingress/a0d7b954_adguard"
|
||||
config_url = "homeassistant://app/a0d7b954_adguard"
|
||||
elif self.adguard.tls:
|
||||
config_url = f"https://{self.adguard.host}:{self.adguard.port}"
|
||||
else:
|
||||
|
||||
@@ -127,6 +127,7 @@ _EXPERIMENTAL_CONDITION_PLATFORMS = {
|
||||
"assist_satellite",
|
||||
"fan",
|
||||
"light",
|
||||
"siren",
|
||||
}
|
||||
|
||||
_EXPERIMENTAL_TRIGGER_PLATFORMS = {
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["acme", "hass_nabucasa", "snitun"],
|
||||
"requirements": ["hass-nabucasa==1.10.0"],
|
||||
"requirements": ["hass-nabucasa==1.11.0"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ LOGGER = logging.getLogger(__package__)
|
||||
|
||||
DOMAIN = "deconz"
|
||||
|
||||
HASSIO_CONFIGURATION_URL = "homeassistant://hassio/ingress/core_deconz"
|
||||
HASSIO_CONFIGURATION_URL = "homeassistant://app/core_deconz"
|
||||
|
||||
CONF_BRIDGE_ID = "bridgeid"
|
||||
CONF_GROUP_ID_BASE = "group_id_base"
|
||||
|
||||
@@ -1034,7 +1034,7 @@ def _async_setup_device_registry(
|
||||
and dashboard.data
|
||||
and dashboard.data.get(device_info.name)
|
||||
):
|
||||
configuration_url = f"homeassistant://hassio/ingress/{dashboard.addon_slug}"
|
||||
configuration_url = f"homeassistant://app/{dashboard.addon_slug}"
|
||||
|
||||
manufacturer = "espressif"
|
||||
if device_info.manufacturer:
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pyfirefly==0.1.11"]
|
||||
"requirements": ["pyfirefly==0.1.12"]
|
||||
}
|
||||
|
||||
36
homeassistant/components/homevolt/__init__.py
Normal file
36
homeassistant/components/homevolt/__init__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""The Homevolt integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homevolt import Homevolt
|
||||
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .coordinator import HomevoltConfigEntry, HomevoltDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: HomevoltConfigEntry) -> bool:
|
||||
"""Set up Homevolt from a config entry."""
|
||||
host: str = entry.data[CONF_HOST]
|
||||
password: str | None = entry.data.get(CONF_PASSWORD)
|
||||
|
||||
websession = async_get_clientsession(hass)
|
||||
client = Homevolt(host, password, websession=websession)
|
||||
|
||||
coordinator = HomevoltDataUpdateCoordinator(hass, entry, client)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: HomevoltConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
70
homeassistant/components/homevolt/config_flow.py
Normal file
70
homeassistant/components/homevolt/config_flow.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Config flow for the Homevolt integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homevolt import Homevolt, HomevoltAuthenticationError, HomevoltConnectionError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST): str,
|
||||
vol.Optional(CONF_PASSWORD): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class HomevoltConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Homevolt."""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
host = user_input[CONF_HOST]
|
||||
password = user_input.get(CONF_PASSWORD)
|
||||
websession = async_get_clientsession(self.hass)
|
||||
client = Homevolt(host, password, websession=websession)
|
||||
try:
|
||||
await client.update_info()
|
||||
device = client.get_device()
|
||||
device_id = device.device_id
|
||||
except HomevoltAuthenticationError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except HomevoltConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception:
|
||||
_LOGGER.exception(
|
||||
"Error occurred while connecting to the Homevolt battery"
|
||||
)
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
await self.async_set_unique_id(device_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(
|
||||
title="Homevolt Local",
|
||||
data={
|
||||
CONF_HOST: host,
|
||||
CONF_PASSWORD: password,
|
||||
},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
)
|
||||
9
homeassistant/components/homevolt/const.py
Normal file
9
homeassistant/components/homevolt/const.py
Normal file
@@ -0,0 +1,9 @@
|
||||
"""Constants for the Homevolt integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
DOMAIN = "homevolt"
|
||||
MANUFACTURER = "Homevolt"
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
56
homeassistant/components/homevolt/coordinator.py
Normal file
56
homeassistant/components/homevolt/coordinator.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""Data update coordinator for Homevolt integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homevolt import (
|
||||
Device,
|
||||
Homevolt,
|
||||
HomevoltAuthenticationError,
|
||||
HomevoltConnectionError,
|
||||
HomevoltError,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN, SCAN_INTERVAL
|
||||
|
||||
type HomevoltConfigEntry = ConfigEntry[HomevoltDataUpdateCoordinator]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HomevoltDataUpdateCoordinator(DataUpdateCoordinator[Device]):
|
||||
"""Class to manage fetching Homevolt data."""
|
||||
|
||||
config_entry: HomevoltConfigEntry
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: HomevoltConfigEntry,
|
||||
client: Homevolt,
|
||||
) -> None:
|
||||
"""Initialize the Homevolt coordinator."""
|
||||
self.client = client
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=SCAN_INTERVAL,
|
||||
config_entry=entry,
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> Device:
|
||||
"""Fetch data from the Homevolt API."""
|
||||
try:
|
||||
await self.client.update_info()
|
||||
return self.client.get_device()
|
||||
except HomevoltAuthenticationError as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
except (HomevoltConnectionError, HomevoltError) as err:
|
||||
raise UpdateFailed(f"Error communicating with device: {err}") from err
|
||||
12
homeassistant/components/homevolt/manifest.json
Normal file
12
homeassistant/components/homevolt/manifest.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"domain": "homevolt",
|
||||
"name": "Homevolt",
|
||||
"codeowners": ["@danielhiversen"],
|
||||
"config_flow": true,
|
||||
"dependencies": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/homevolt",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["homevolt==0.2.4"]
|
||||
}
|
||||
70
homeassistant/components/homevolt/quality_scale.yaml
Normal file
70
homeassistant/components/homevolt/quality_scale.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
rules:
|
||||
# Bronze
|
||||
action-setup:
|
||||
status: exempt
|
||||
comment: Integration does not register custom actions.
|
||||
appropriate-polling: done
|
||||
brands: done
|
||||
common-modules: done
|
||||
config-flow-test-coverage: done
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: Integration does not register custom actions.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: Local_polling without events
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
runtime-data: done
|
||||
test-before-configure: done
|
||||
test-before-setup: done
|
||||
unique-config-entry: done
|
||||
|
||||
# Silver
|
||||
action-exceptions:
|
||||
status: exempt
|
||||
comment: Integration does not register custom actions.
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
comment: Integration does not have an options flow.
|
||||
docs-installation-parameters: todo
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
log-when-unavailable: todo
|
||||
parallel-updates: done
|
||||
reauthentication-flow: todo
|
||||
test-coverage: todo
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: todo
|
||||
discovery-update-info: todo
|
||||
discovery: todo
|
||||
docs-data-update: todo
|
||||
docs-examples: todo
|
||||
docs-known-limitations: todo
|
||||
docs-supported-devices: todo
|
||||
docs-supported-functions: todo
|
||||
docs-troubleshooting: todo
|
||||
docs-use-cases: todo
|
||||
dynamic-devices: todo
|
||||
entity-category: todo
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: todo
|
||||
entity-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: todo
|
||||
reconfiguration-flow: todo
|
||||
repair-issues: todo
|
||||
stale-devices: todo
|
||||
|
||||
# Platinum
|
||||
async-dependency: done
|
||||
inject-websession: done
|
||||
strict-typing: todo
|
||||
162
homeassistant/components/homevolt/sensor.py
Normal file
162
homeassistant/components/homevolt/sensor.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""Support for Homevolt sensors."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homevolt.models import SensorType
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
PERCENTAGE,
|
||||
SIGNAL_STRENGTH_DECIBELS,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfEnergy,
|
||||
UnitOfFrequency,
|
||||
UnitOfPower,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
from .coordinator import HomevoltConfigEntry, HomevoltDataUpdateCoordinator
|
||||
|
||||
PARALLEL_UPDATES = 0 # Coordinator-based updates
|
||||
|
||||
SENSORS: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key=SensorType.COUNT,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SensorType.ENERGY_TOTAL,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SensorType.ENERGY_INCREASING,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SensorType.FREQUENCY,
|
||||
device_class=SensorDeviceClass.FREQUENCY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfFrequency.HERTZ,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SensorType.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SensorType.POWER,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SensorType.SCHEDULE_TYPE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SensorType.SIGNAL_STRENGTH,
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SensorType.TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SensorType.TEXT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SensorType.VOLTAGE,
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SensorType.CURRENT,
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: HomevoltConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Homevolt sensor."""
|
||||
coordinator = entry.runtime_data
|
||||
entities = []
|
||||
sensors_by_key = {sensor.key: sensor for sensor in SENSORS}
|
||||
for sensor_key, sensor in coordinator.data.sensors.items():
|
||||
if (description := sensors_by_key.get(sensor.type)) is None:
|
||||
continue
|
||||
entities.append(
|
||||
HomevoltSensor(
|
||||
description,
|
||||
coordinator,
|
||||
sensor_key,
|
||||
)
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class HomevoltSensor(CoordinatorEntity[HomevoltDataUpdateCoordinator], SensorEntity):
|
||||
"""Representation of a Homevolt sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
description: SensorEntityDescription,
|
||||
coordinator: HomevoltDataUpdateCoordinator,
|
||||
sensor_key: str,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
device_id = coordinator.data.device_id
|
||||
self._attr_unique_id = f"{device_id}_{sensor_key}"
|
||||
sensor_data = coordinator.data.sensors[sensor_key]
|
||||
self._attr_translation_key = sensor_data.slug
|
||||
self._sensor_key = sensor_key
|
||||
device_metadata = coordinator.data.device_metadata.get(
|
||||
sensor_data.device_identifier
|
||||
)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{device_id}_{sensor_data.device_identifier}")},
|
||||
configuration_url=coordinator.client.base_url,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=device_metadata.model if device_metadata else None,
|
||||
name=device_metadata.name if device_metadata else None,
|
||||
)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
return super().available and self._sensor_key in self.coordinator.data.sensors
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the native value of the sensor."""
|
||||
return self.coordinator.data.sensors[self._sensor_key].value
|
||||
198
homeassistant/components/homevolt/strings.json
Normal file
198
homeassistant/components/homevolt/strings.json
Normal file
@@ -0,0 +1,198 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "The IP address or hostname of your Homevolt battery on your local network.",
|
||||
"password": "The local password configured for your Homevolt battery, if required."
|
||||
},
|
||||
"description": "Connect Home Assistant to your Homevolt battery over the local network.",
|
||||
"title": "Homevolt Local"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"available_charging_energy": {
|
||||
"name": "Available charging energy"
|
||||
},
|
||||
"available_charging_power": {
|
||||
"name": "Available charging power"
|
||||
},
|
||||
"available_discharge_energy": {
|
||||
"name": "Available discharge energy"
|
||||
},
|
||||
"available_discharge_power": {
|
||||
"name": "Available discharge power"
|
||||
},
|
||||
"average_rssi_grid": {
|
||||
"name": "Grid average RSSI"
|
||||
},
|
||||
"average_rssi_load": {
|
||||
"name": "Load average RSSI"
|
||||
},
|
||||
"battery_state_of_charge": {
|
||||
"name": "Battery state of charge"
|
||||
},
|
||||
"charge_cycles": {
|
||||
"name": "Charge cycles"
|
||||
},
|
||||
"energy_exported_grid": {
|
||||
"name": "Grid exported energy"
|
||||
},
|
||||
"energy_exported_load": {
|
||||
"name": "Load exported energy"
|
||||
},
|
||||
"energy_imported_grid": {
|
||||
"name": "Grid imported energy"
|
||||
},
|
||||
"energy_imported_load": {
|
||||
"name": "Load imported energy"
|
||||
},
|
||||
"exported_energy": {
|
||||
"name": "Exported energy"
|
||||
},
|
||||
"frequency": {
|
||||
"name": "Frequency"
|
||||
},
|
||||
"imported_energy": {
|
||||
"name": "Imported energy"
|
||||
},
|
||||
"l1_current": {
|
||||
"name": "L1 current"
|
||||
},
|
||||
"l1_current_grid": {
|
||||
"name": "Grid L1 current"
|
||||
},
|
||||
"l1_current_load": {
|
||||
"name": "Load L1 current"
|
||||
},
|
||||
"l1_l2_voltage": {
|
||||
"name": "L1-L2 voltage"
|
||||
},
|
||||
"l1_power_grid": {
|
||||
"name": "Grid L1 power"
|
||||
},
|
||||
"l1_power_load": {
|
||||
"name": "Load L1 power"
|
||||
},
|
||||
"l1_voltage": {
|
||||
"name": "L1 voltage"
|
||||
},
|
||||
"l1_voltage_grid": {
|
||||
"name": "Grid L1 voltage"
|
||||
},
|
||||
"l1_voltage_load": {
|
||||
"name": "Load L1 voltage"
|
||||
},
|
||||
"l2_current": {
|
||||
"name": "L2 current"
|
||||
},
|
||||
"l2_current_grid": {
|
||||
"name": "Grid L2 current"
|
||||
},
|
||||
"l2_current_load": {
|
||||
"name": "Load L2 current"
|
||||
},
|
||||
"l2_l3_voltage": {
|
||||
"name": "L2-L3 voltage"
|
||||
},
|
||||
"l2_power_grid": {
|
||||
"name": "Grid L2 power"
|
||||
},
|
||||
"l2_power_load": {
|
||||
"name": "Load L2 power"
|
||||
},
|
||||
"l2_voltage": {
|
||||
"name": "L2 voltage"
|
||||
},
|
||||
"l2_voltage_grid": {
|
||||
"name": "Grid L2 voltage"
|
||||
},
|
||||
"l2_voltage_load": {
|
||||
"name": "Load L2 voltage"
|
||||
},
|
||||
"l3_current": {
|
||||
"name": "L3 current"
|
||||
},
|
||||
"l3_current_grid": {
|
||||
"name": "Grid L3 current"
|
||||
},
|
||||
"l3_current_load": {
|
||||
"name": "Load L3 current"
|
||||
},
|
||||
"l3_l1_voltage": {
|
||||
"name": "L3-L1 voltage"
|
||||
},
|
||||
"l3_power_grid": {
|
||||
"name": "Grid L3 power"
|
||||
},
|
||||
"l3_power_load": {
|
||||
"name": "Load L3 power"
|
||||
},
|
||||
"l3_voltage": {
|
||||
"name": "L3 voltage"
|
||||
},
|
||||
"l3_voltage_grid": {
|
||||
"name": "Grid L3 voltage"
|
||||
},
|
||||
"l3_voltage_load": {
|
||||
"name": "Load L3 voltage"
|
||||
},
|
||||
"power": {
|
||||
"name": "Power"
|
||||
},
|
||||
"power_grid": {
|
||||
"name": "Grid power"
|
||||
},
|
||||
"power_load": {
|
||||
"name": "Load power"
|
||||
},
|
||||
"rssi_grid": {
|
||||
"name": "Grid RSSI"
|
||||
},
|
||||
"rssi_load": {
|
||||
"name": "Load RSSI"
|
||||
},
|
||||
"schedule_id": {
|
||||
"name": "Schedule ID"
|
||||
},
|
||||
"schedule_max_discharge": {
|
||||
"name": "Schedule max discharge"
|
||||
},
|
||||
"schedule_max_power": {
|
||||
"name": "Schedule max power"
|
||||
},
|
||||
"schedule_power_setpoint": {
|
||||
"name": "Schedule power setpoint"
|
||||
},
|
||||
"schedule_type": {
|
||||
"name": "Schedule type"
|
||||
},
|
||||
"state_of_charge": {
|
||||
"name": "State of charge"
|
||||
},
|
||||
"system_temperature": {
|
||||
"name": "System temperature"
|
||||
},
|
||||
"tmax": {
|
||||
"name": "Maximum temperature"
|
||||
},
|
||||
"tmin": {
|
||||
"name": "Minimum temperature"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
"title": "Lawn mower",
|
||||
"triggers": {
|
||||
"docked": {
|
||||
"description": "Triggers after one or more lawn mowers return to dock.",
|
||||
"description": "Triggers after one or more lawn mowers have returned to dock.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::lawn_mower::common::trigger_behavior_description%]",
|
||||
|
||||
@@ -50,7 +50,7 @@ class MealieConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Check connection to the Mealie API."""
|
||||
assert self.host is not None
|
||||
|
||||
if "/hassio/ingress/" in self.host:
|
||||
if "/app/" in self.host:
|
||||
return {"base": "ingress_url"}, None
|
||||
|
||||
client = MealieClient(
|
||||
|
||||
@@ -73,15 +73,6 @@ SHARED_OPTIONS = [
|
||||
CONF_STATE_TOPIC,
|
||||
]
|
||||
|
||||
MQTT_ORIGIN_INFO_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_SW_VERSION): cv.string,
|
||||
vol.Optional(CONF_SUPPORT_URL): cv.configuration_url,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
_MQTT_AVAILABILITY_SINGLE_SCHEMA = vol.Schema(
|
||||
{
|
||||
|
||||
@@ -125,7 +125,7 @@ class NumberDeviceClass(StrEnum):
|
||||
CO = "carbon_monoxide"
|
||||
"""Carbon Monoxide gas concentration.
|
||||
|
||||
Unit of measurement: `ppm` (parts per million), `mg/m³`, `μg/m³`
|
||||
Unit of measurement: `ppb` (parts per billion), `ppm` (parts per million), `mg/m³`, `μg/m³`
|
||||
"""
|
||||
|
||||
CO2 = "carbon_dioxide"
|
||||
@@ -373,7 +373,7 @@ class NumberDeviceClass(StrEnum):
|
||||
SULPHUR_DIOXIDE = "sulphur_dioxide"
|
||||
"""Amount of SO2.
|
||||
|
||||
Unit of measurement: `μg/m³`
|
||||
Unit of measurement: `ppb` (parts per billion), `μg/m³`
|
||||
"""
|
||||
|
||||
TEMPERATURE = "temperature"
|
||||
@@ -483,6 +483,7 @@ DEVICE_CLASS_UNITS: dict[NumberDeviceClass, set[type[StrEnum] | str | None]] = {
|
||||
NumberDeviceClass.BATTERY: {PERCENTAGE},
|
||||
NumberDeviceClass.BLOOD_GLUCOSE_CONCENTRATION: set(UnitOfBloodGlucoseConcentration),
|
||||
NumberDeviceClass.CO: {
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
@@ -545,7 +546,10 @@ DEVICE_CLASS_UNITS: dict[NumberDeviceClass, set[type[StrEnum] | str | None]] = {
|
||||
},
|
||||
NumberDeviceClass.SOUND_PRESSURE: set(UnitOfSoundPressure),
|
||||
NumberDeviceClass.SPEED: {*UnitOfSpeed, *UnitOfVolumetricFlux},
|
||||
NumberDeviceClass.SULPHUR_DIOXIDE: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
|
||||
NumberDeviceClass.SULPHUR_DIOXIDE: {
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
},
|
||||
NumberDeviceClass.TEMPERATURE: set(UnitOfTemperature),
|
||||
NumberDeviceClass.TEMPERATURE_DELTA: set(UnitOfTemperature),
|
||||
NumberDeviceClass.VOLATILE_ORGANIC_COMPOUNDS: {
|
||||
|
||||
@@ -158,7 +158,7 @@ MODEL_NAMES = [ # https://ollama.com/library
|
||||
"yi",
|
||||
"zephyr",
|
||||
]
|
||||
DEFAULT_MODEL = "qwen3:4b"
|
||||
DEFAULT_MODEL = "qwen3:4b-instruct"
|
||||
|
||||
DEFAULT_CONVERSATION_NAME = "Ollama Conversation"
|
||||
DEFAULT_AI_TASK_NAME = "Ollama AI Task"
|
||||
|
||||
@@ -64,6 +64,7 @@ from homeassistant.util.unit_conversion import (
|
||||
ReactiveEnergyConverter,
|
||||
ReactivePowerConverter,
|
||||
SpeedConverter,
|
||||
SulphurDioxideConcentrationConverter,
|
||||
TemperatureConverter,
|
||||
TemperatureDeltaConverter,
|
||||
UnitlessRatioConverter,
|
||||
@@ -225,6 +226,7 @@ _PRIMARY_UNIT_CONVERTERS: list[type[BaseUnitConverter]] = [
|
||||
_SECONDARY_UNIT_CONVERTERS: list[type[BaseUnitConverter]] = [
|
||||
CarbonMonoxideConcentrationConverter,
|
||||
TemperatureDeltaConverter,
|
||||
SulphurDioxideConcentrationConverter,
|
||||
]
|
||||
|
||||
STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = {
|
||||
|
||||
@@ -38,6 +38,7 @@ from homeassistant.util.unit_conversion import (
|
||||
ReactiveEnergyConverter,
|
||||
ReactivePowerConverter,
|
||||
SpeedConverter,
|
||||
SulphurDioxideConcentrationConverter,
|
||||
TemperatureConverter,
|
||||
TemperatureDeltaConverter,
|
||||
UnitlessRatioConverter,
|
||||
@@ -94,6 +95,9 @@ UNIT_SCHEMA = vol.Schema(
|
||||
vol.Optional("reactive_energy"): vol.In(ReactiveEnergyConverter.VALID_UNITS),
|
||||
vol.Optional("reactive_power"): vol.In(ReactivePowerConverter.VALID_UNITS),
|
||||
vol.Optional("speed"): vol.In(SpeedConverter.VALID_UNITS),
|
||||
vol.Optional("sulphur_dioxide"): vol.In(
|
||||
SulphurDioxideConcentrationConverter.VALID_UNITS
|
||||
),
|
||||
vol.Optional("temperature"): vol.In(TemperatureConverter.VALID_UNITS),
|
||||
vol.Optional("temperature_delta"): vol.In(
|
||||
TemperatureDeltaConverter.VALID_UNITS
|
||||
|
||||
@@ -44,6 +44,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: LeilSaunaConfigEntry) ->
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: LeilSaunaConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
entry.runtime_data.client.close()
|
||||
await entry.runtime_data.client.async_close()
|
||||
|
||||
return unload_ok
|
||||
|
||||
@@ -51,7 +51,7 @@ async def validate_input(data: dict[str, Any]) -> None:
|
||||
# Try to read data to verify communication
|
||||
await client.async_get_data()
|
||||
finally:
|
||||
client.close()
|
||||
await client.async_close()
|
||||
|
||||
|
||||
class LeilSaunaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pysaunum"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pysaunum==0.2.0"]
|
||||
"requirements": ["pysaunum==0.3.0"]
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ from homeassistant.util.unit_conversion import (
|
||||
ReactiveEnergyConverter,
|
||||
ReactivePowerConverter,
|
||||
SpeedConverter,
|
||||
SulphurDioxideConcentrationConverter,
|
||||
TemperatureConverter,
|
||||
TemperatureDeltaConverter,
|
||||
UnitlessRatioConverter,
|
||||
@@ -158,7 +159,7 @@ class SensorDeviceClass(StrEnum):
|
||||
CO = "carbon_monoxide"
|
||||
"""Carbon Monoxide gas concentration.
|
||||
|
||||
Unit of measurement: `ppm` (parts per million), `mg/m³`, `μg/m³`
|
||||
Unit of measurement: `ppb` (parts per billion), `ppm` (parts per million), `mg/m³`, `μg/m³`
|
||||
"""
|
||||
|
||||
CO2 = "carbon_dioxide"
|
||||
@@ -409,7 +410,7 @@ class SensorDeviceClass(StrEnum):
|
||||
SULPHUR_DIOXIDE = "sulphur_dioxide"
|
||||
"""Amount of SO2.
|
||||
|
||||
Unit of measurement: `μg/m³`
|
||||
Unit of measurement: `ppb` (parts per billion), `μg/m³`
|
||||
"""
|
||||
|
||||
TEMPERATURE = "temperature"
|
||||
@@ -569,6 +570,7 @@ UNIT_CONVERTERS: dict[SensorDeviceClass | str | None, type[BaseUnitConverter]] =
|
||||
SensorDeviceClass.PRESSURE: PressureConverter,
|
||||
SensorDeviceClass.REACTIVE_ENERGY: ReactiveEnergyConverter,
|
||||
SensorDeviceClass.REACTIVE_POWER: ReactivePowerConverter,
|
||||
SensorDeviceClass.SULPHUR_DIOXIDE: SulphurDioxideConcentrationConverter,
|
||||
SensorDeviceClass.SPEED: SpeedConverter,
|
||||
SensorDeviceClass.TEMPERATURE: TemperatureConverter,
|
||||
SensorDeviceClass.TEMPERATURE_DELTA: TemperatureDeltaConverter,
|
||||
@@ -595,6 +597,7 @@ DEVICE_CLASS_UNITS: dict[SensorDeviceClass, set[type[StrEnum] | str | None]] = {
|
||||
SensorDeviceClass.BATTERY: {PERCENTAGE},
|
||||
SensorDeviceClass.BLOOD_GLUCOSE_CONCENTRATION: set(UnitOfBloodGlucoseConcentration),
|
||||
SensorDeviceClass.CO: {
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
@@ -657,7 +660,10 @@ DEVICE_CLASS_UNITS: dict[SensorDeviceClass, set[type[StrEnum] | str | None]] = {
|
||||
},
|
||||
SensorDeviceClass.SOUND_PRESSURE: set(UnitOfSoundPressure),
|
||||
SensorDeviceClass.SPEED: {*UnitOfSpeed, *UnitOfVolumetricFlux},
|
||||
SensorDeviceClass.SULPHUR_DIOXIDE: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
|
||||
SensorDeviceClass.SULPHUR_DIOXIDE: {
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
},
|
||||
SensorDeviceClass.TEMPERATURE: set(UnitOfTemperature),
|
||||
SensorDeviceClass.TEMPERATURE_DELTA: set(UnitOfTemperature),
|
||||
SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS: {
|
||||
|
||||
17
homeassistant/components/siren/condition.py
Normal file
17
homeassistant/components/siren/condition.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Provides conditions for sirens."""
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.condition import Condition, make_entity_state_condition
|
||||
|
||||
from . import DOMAIN
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF),
|
||||
"is_on": make_entity_state_condition(DOMAIN, STATE_ON),
|
||||
}
|
||||
|
||||
|
||||
async def async_get_conditions(hass: HomeAssistant) -> dict[str, type[Condition]]:
|
||||
"""Return the siren conditions."""
|
||||
return CONDITIONS
|
||||
17
homeassistant/components/siren/conditions.yaml
Normal file
17
homeassistant/components/siren/conditions.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
.condition_common: &condition_common
|
||||
target:
|
||||
entity:
|
||||
domain: siren
|
||||
fields:
|
||||
behavior:
|
||||
required: true
|
||||
default: any
|
||||
selector:
|
||||
select:
|
||||
translation_key: condition_behavior
|
||||
options:
|
||||
- all
|
||||
- any
|
||||
|
||||
is_off: *condition_common
|
||||
is_on: *condition_common
|
||||
@@ -1,4 +1,12 @@
|
||||
{
|
||||
"conditions": {
|
||||
"is_off": {
|
||||
"condition": "mdi:bullhorn-outline"
|
||||
},
|
||||
"is_on": {
|
||||
"condition": "mdi:bullhorn"
|
||||
}
|
||||
},
|
||||
"entity_component": {
|
||||
"_": {
|
||||
"default": "mdi:bullhorn"
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
{
|
||||
"common": {
|
||||
"condition_behavior_description": "How the state should match on the targeted sirens.",
|
||||
"condition_behavior_name": "Behavior",
|
||||
"trigger_behavior_description": "The behavior of the targeted sirens to trigger on.",
|
||||
"trigger_behavior_name": "Behavior"
|
||||
},
|
||||
"conditions": {
|
||||
"is_off": {
|
||||
"description": "Tests if one or more sirens are off.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::siren::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::siren::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
"name": "If a siren is off"
|
||||
},
|
||||
"is_on": {
|
||||
"description": "Tests if one or more sirens are on.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"description": "[%key:component::siren::common::condition_behavior_description%]",
|
||||
"name": "[%key:component::siren::common::condition_behavior_name%]"
|
||||
}
|
||||
},
|
||||
"name": "If a siren is on"
|
||||
}
|
||||
},
|
||||
"entity_component": {
|
||||
"_": {
|
||||
"name": "[%key:component::siren::title%]",
|
||||
@@ -18,6 +42,12 @@
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"condition_behavior": {
|
||||
"options": {
|
||||
"all": "All",
|
||||
"any": "Any"
|
||||
}
|
||||
},
|
||||
"trigger_behavior": {
|
||||
"options": {
|
||||
"any": "Any",
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
"create_entry": {
|
||||
"default": "Successfully authenticated with Spotify."
|
||||
},
|
||||
"initiate_flow": {
|
||||
"user": "[%key:common::config_flow::initiate_flow::account%]"
|
||||
},
|
||||
"step": {
|
||||
"pick_implementation": {
|
||||
"data": {
|
||||
|
||||
@@ -185,6 +185,9 @@ async def make_device_data(
|
||||
"Smart Lock Lite",
|
||||
"Smart Lock Pro",
|
||||
"Smart Lock Ultra",
|
||||
"Smart Lock Vision",
|
||||
"Smart Lock Vision Pro",
|
||||
"Smart Lock Pro Wifi",
|
||||
]:
|
||||
coordinator = await coordinator_for_device(
|
||||
hass, entry, api, device, coordinators_by_id
|
||||
|
||||
@@ -92,6 +92,18 @@ BINARY_SENSOR_DESCRIPTIONS_BY_DEVICE_TYPES = {
|
||||
CALIBRATION_DESCRIPTION,
|
||||
DOOR_OPEN_DESCRIPTION,
|
||||
),
|
||||
"Smart Lock Vision": (
|
||||
CALIBRATION_DESCRIPTION,
|
||||
DOOR_OPEN_DESCRIPTION,
|
||||
),
|
||||
"Smart Lock Vision Pro": (
|
||||
CALIBRATION_DESCRIPTION,
|
||||
DOOR_OPEN_DESCRIPTION,
|
||||
),
|
||||
"Smart Lock Pro Wifi": (
|
||||
CALIBRATION_DESCRIPTION,
|
||||
DOOR_OPEN_DESCRIPTION,
|
||||
),
|
||||
"Curtain": (CALIBRATION_DESCRIPTION,),
|
||||
"Curtain3": (CALIBRATION_DESCRIPTION,),
|
||||
"Roller Shade": (CALIBRATION_DESCRIPTION,),
|
||||
|
||||
@@ -46,7 +46,7 @@ class SwitchBotCloudLock(SwitchBotCloudEntity, LockEntity):
|
||||
"""Set attributes from coordinator data."""
|
||||
if coord_data := self.coordinator.data:
|
||||
self._attr_is_locked = coord_data["lockState"] == "locked"
|
||||
if self.__model in LockV2Commands.get_supported_devices():
|
||||
if self.__model != "Smart Lock Lite":
|
||||
self._attr_supported_features = LockEntityFeature.OPEN
|
||||
|
||||
async def async_lock(self, **kwargs: Any) -> None:
|
||||
|
||||
@@ -225,6 +225,9 @@ SENSOR_DESCRIPTIONS_BY_DEVICE_TYPES = {
|
||||
"Smart Lock Lite": (BATTERY_DESCRIPTION,),
|
||||
"Smart Lock Pro": (BATTERY_DESCRIPTION,),
|
||||
"Smart Lock Ultra": (BATTERY_DESCRIPTION,),
|
||||
"Smart Lock Vision": (BATTERY_DESCRIPTION,),
|
||||
"Smart Lock Vision Pro": (BATTERY_DESCRIPTION,),
|
||||
"Smart Lock Pro Wifi": (BATTERY_DESCRIPTION,),
|
||||
"Relay Switch 2PM": (
|
||||
RELAY_SWITCH_2PM_POWER_DESCRIPTION,
|
||||
RELAY_SWITCH_2PM_VOLTAGE_DESCRIPTION,
|
||||
|
||||
@@ -165,7 +165,7 @@ class DeviceListener(SharingDeviceListener):
|
||||
self,
|
||||
device: CustomerDevice,
|
||||
updated_status_properties: list[str] | None = None,
|
||||
dp_timestamps: dict | None = None,
|
||||
dp_timestamps: dict[str, int] | None = None,
|
||||
) -> None:
|
||||
"""Update device status with optional DP timestamps."""
|
||||
LOGGER.debug(
|
||||
|
||||
@@ -471,9 +471,11 @@ class TuyaBinarySensorEntity(TuyaEntity, BinarySensorEntity):
|
||||
async def _handle_state_update(
|
||||
self,
|
||||
updated_status_properties: list[str] | None,
|
||||
dp_timestamps: dict | None = None,
|
||||
dp_timestamps: dict[str, int] | None,
|
||||
) -> None:
|
||||
"""Handle state update, only if this entity's dpcode was actually updated."""
|
||||
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
|
||||
if self._dpcode_wrapper.skip_update(
|
||||
self.device, updated_status_properties, dp_timestamps
|
||||
):
|
||||
return
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -57,7 +57,7 @@ class TuyaEntity(Entity):
|
||||
async def _handle_state_update(
|
||||
self,
|
||||
updated_status_properties: list[str] | None,
|
||||
dp_timestamps: dict | None = None,
|
||||
dp_timestamps: dict[str, int] | None,
|
||||
) -> None:
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
@@ -218,10 +218,10 @@ class TuyaEventEntity(TuyaEntity, EventEntity):
|
||||
async def _handle_state_update(
|
||||
self,
|
||||
updated_status_properties: list[str] | None,
|
||||
dp_timestamps: dict | None = None,
|
||||
dp_timestamps: dict[str, int] | None,
|
||||
) -> None:
|
||||
if self._dpcode_wrapper.skip_update(
|
||||
self.device, updated_status_properties
|
||||
self.device, updated_status_properties, dp_timestamps
|
||||
) or not (event_data := self._dpcode_wrapper.read_device_status(self.device)):
|
||||
return
|
||||
|
||||
|
||||
@@ -31,7 +31,10 @@ class DeviceWrapper[T]:
|
||||
options: list[str]
|
||||
|
||||
def skip_update(
|
||||
self, device: CustomerDevice, updated_status_properties: list[str] | None
|
||||
self,
|
||||
device: CustomerDevice,
|
||||
updated_status_properties: list[str] | None,
|
||||
dp_timestamps: dict[str, int] | None,
|
||||
) -> bool:
|
||||
"""Determine if the wrapper should skip an update.
|
||||
|
||||
@@ -62,7 +65,10 @@ class DPCodeWrapper(DeviceWrapper):
|
||||
self.dpcode = dpcode
|
||||
|
||||
def skip_update(
|
||||
self, device: CustomerDevice, updated_status_properties: list[str] | None
|
||||
self,
|
||||
device: CustomerDevice,
|
||||
updated_status_properties: list[str] | None,
|
||||
dp_timestamps: dict[str, int] | None,
|
||||
) -> bool:
|
||||
"""Determine if the wrapper should skip an update.
|
||||
|
||||
|
||||
@@ -554,10 +554,12 @@ class TuyaNumberEntity(TuyaEntity, NumberEntity):
|
||||
async def _handle_state_update(
|
||||
self,
|
||||
updated_status_properties: list[str] | None,
|
||||
dp_timestamps: dict | None = None,
|
||||
dp_timestamps: dict[str, int] | None,
|
||||
) -> None:
|
||||
"""Handle state update, only if this entity's dpcode was actually updated."""
|
||||
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
|
||||
if self._dpcode_wrapper.skip_update(
|
||||
self.device, updated_status_properties, dp_timestamps
|
||||
):
|
||||
return
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
@@ -410,10 +410,12 @@ class TuyaSelectEntity(TuyaEntity, SelectEntity):
|
||||
async def _handle_state_update(
|
||||
self,
|
||||
updated_status_properties: list[str] | None,
|
||||
dp_timestamps: dict | None = None,
|
||||
dp_timestamps: dict[str, int] | None,
|
||||
) -> None:
|
||||
"""Handle state update, only if this entity's dpcode was actually updated."""
|
||||
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
|
||||
if self._dpcode_wrapper.skip_update(
|
||||
self.device, updated_status_properties, dp_timestamps
|
||||
):
|
||||
return
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
@@ -1853,9 +1853,11 @@ class TuyaSensorEntity(TuyaEntity, SensorEntity):
|
||||
async def _handle_state_update(
|
||||
self,
|
||||
updated_status_properties: list[str] | None,
|
||||
dp_timestamps: dict | None = None,
|
||||
dp_timestamps: dict[str, int] | None,
|
||||
) -> None:
|
||||
"""Handle state update, only if this entity's dpcode was actually updated."""
|
||||
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
|
||||
if self._dpcode_wrapper.skip_update(
|
||||
self.device, updated_status_properties, dp_timestamps
|
||||
):
|
||||
return
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -110,10 +110,12 @@ class TuyaSirenEntity(TuyaEntity, SirenEntity):
|
||||
async def _handle_state_update(
|
||||
self,
|
||||
updated_status_properties: list[str] | None,
|
||||
dp_timestamps: dict | None = None,
|
||||
dp_timestamps: dict[str, int] | None,
|
||||
) -> None:
|
||||
"""Handle state update, only if this entity's dpcode was actually updated."""
|
||||
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
|
||||
if self._dpcode_wrapper.skip_update(
|
||||
self.device, updated_status_properties, dp_timestamps
|
||||
):
|
||||
return
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
@@ -1043,10 +1043,12 @@ class TuyaSwitchEntity(TuyaEntity, SwitchEntity):
|
||||
async def _handle_state_update(
|
||||
self,
|
||||
updated_status_properties: list[str] | None,
|
||||
dp_timestamps: dict | None = None,
|
||||
dp_timestamps: dict[str, int] | None,
|
||||
) -> None:
|
||||
"""Handle state update, only if this entity's dpcode was actually updated."""
|
||||
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
|
||||
if self._dpcode_wrapper.skip_update(
|
||||
self.device, updated_status_properties, dp_timestamps
|
||||
):
|
||||
return
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
@@ -140,10 +140,12 @@ class TuyaValveEntity(TuyaEntity, ValveEntity):
|
||||
async def _handle_state_update(
|
||||
self,
|
||||
updated_status_properties: list[str] | None,
|
||||
dp_timestamps: dict | None = None,
|
||||
dp_timestamps: dict[str, int] | None,
|
||||
) -> None:
|
||||
"""Handle state update, only if this entity's dpcode was actually updated."""
|
||||
if self._dpcode_wrapper.skip_update(self.device, updated_status_properties):
|
||||
if self._dpcode_wrapper.skip_update(
|
||||
self.device, updated_status_properties, dp_timestamps
|
||||
):
|
||||
return
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["waterfurnace"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["waterfurnace==1.2.0"]
|
||||
"requirements": ["waterfurnace==1.4.0"]
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ async def async_setup_entry(
|
||||
class WyomingTtsProvider(tts.TextToSpeechEntity):
|
||||
"""Wyoming text-to-speech provider."""
|
||||
|
||||
_attr_default_options = {}
|
||||
_attr_supported_options = [tts.ATTR_AUDIO_OUTPUT, tts.ATTR_VOICE, ATTR_SPEAKER]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config_entry: ConfigEntry,
|
||||
@@ -78,38 +81,13 @@ class WyomingTtsProvider(tts.TextToSpeechEntity):
|
||||
self._voices[language], key=lambda v: v.name
|
||||
)
|
||||
|
||||
self._supported_languages: list[str] = list(voice_languages)
|
||||
self._attr_supported_languages = list(voice_languages)
|
||||
if self._attr_supported_languages:
|
||||
self._attr_default_language = self._attr_supported_languages[0]
|
||||
|
||||
self._attr_name = self._tts_service.name
|
||||
self._attr_unique_id = f"{config_entry.entry_id}-tts"
|
||||
|
||||
@property
|
||||
def default_language(self):
|
||||
"""Return default language."""
|
||||
if not self._supported_languages:
|
||||
return None
|
||||
|
||||
return self._supported_languages[0]
|
||||
|
||||
@property
|
||||
def supported_languages(self):
|
||||
"""Return list of supported languages."""
|
||||
return self._supported_languages
|
||||
|
||||
@property
|
||||
def supported_options(self):
|
||||
"""Return list of supported options like voice, emotion."""
|
||||
return [
|
||||
tts.ATTR_AUDIO_OUTPUT,
|
||||
tts.ATTR_VOICE,
|
||||
ATTR_SPEAKER,
|
||||
]
|
||||
|
||||
@property
|
||||
def default_options(self):
|
||||
"""Return a dict include default options."""
|
||||
return {}
|
||||
|
||||
@callback
|
||||
def async_get_supported_voices(self, language: str) -> list[tts.Voice] | None:
|
||||
"""Return a list of supported voices for a language."""
|
||||
|
||||
1
homeassistant/generated/config_flows.py
generated
1
homeassistant/generated/config_flows.py
generated
@@ -294,6 +294,7 @@ FLOWS = {
|
||||
"homekit",
|
||||
"homekit_controller",
|
||||
"homematicip_cloud",
|
||||
"homevolt",
|
||||
"homewizard",
|
||||
"homeworks",
|
||||
"honeywell",
|
||||
|
||||
@@ -2836,6 +2836,12 @@
|
||||
"zwave"
|
||||
]
|
||||
},
|
||||
"homevolt": {
|
||||
"name": "Homevolt",
|
||||
"integration_type": "device",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling"
|
||||
},
|
||||
"homewizard": {
|
||||
"name": "HomeWizard",
|
||||
"integration_type": "device",
|
||||
|
||||
@@ -36,7 +36,7 @@ fnv-hash-fast==1.6.0
|
||||
go2rtc-client==0.4.0
|
||||
ha-ffmpeg==3.2.2
|
||||
habluetooth==5.8.0
|
||||
hass-nabucasa==1.10.0
|
||||
hass-nabucasa==1.11.0
|
||||
hassil==3.5.0
|
||||
home-assistant-bluetooth==1.13.1
|
||||
home-assistant-frontend==20260107.2
|
||||
|
||||
@@ -85,6 +85,9 @@
|
||||
"timeout_connect": "Timeout establishing connection",
|
||||
"unknown": "Unexpected error"
|
||||
},
|
||||
"initiate_flow": {
|
||||
"account": "Add account"
|
||||
},
|
||||
"title": {
|
||||
"oauth2_pick_implementation": "Pick authentication method",
|
||||
"reauth": "Authentication expired for {name}",
|
||||
|
||||
@@ -103,6 +103,7 @@ _AMBIENT_IDEAL_GAS_MOLAR_VOLUME = ( # m3⋅mol⁻¹
|
||||
)
|
||||
# Molar masses in g⋅mol⁻¹
|
||||
_CARBON_MONOXIDE_MOLAR_MASS = 28.01
|
||||
_SULPHUR_DIOXIDE_MOLAR_MASS = 64.066
|
||||
|
||||
|
||||
class BaseUnitConverter:
|
||||
@@ -193,6 +194,7 @@ class CarbonMonoxideConcentrationConverter(BaseUnitConverter):
|
||||
|
||||
UNIT_CLASS = "carbon_monoxide"
|
||||
_UNIT_CONVERSION: dict[str | None, float] = {
|
||||
CONCENTRATION_PARTS_PER_BILLION: 1e9,
|
||||
CONCENTRATION_PARTS_PER_MILLION: 1e6,
|
||||
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER: (
|
||||
_CARBON_MONOXIDE_MOLAR_MASS / _AMBIENT_IDEAL_GAS_MOLAR_VOLUME * 1e3
|
||||
@@ -202,12 +204,29 @@ class CarbonMonoxideConcentrationConverter(BaseUnitConverter):
|
||||
),
|
||||
}
|
||||
VALID_UNITS = {
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
}
|
||||
|
||||
|
||||
class SulphurDioxideConcentrationConverter(BaseUnitConverter):
|
||||
"""Convert sulphur dioxide ratio to mass per volume."""
|
||||
|
||||
UNIT_CLASS = "sulphur_dioxide"
|
||||
_UNIT_CONVERSION: dict[str | None, float] = {
|
||||
CONCENTRATION_PARTS_PER_BILLION: 1e9,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER: (
|
||||
_SULPHUR_DIOXIDE_MOLAR_MASS / _AMBIENT_IDEAL_GAS_MOLAR_VOLUME * 1e6
|
||||
),
|
||||
}
|
||||
VALID_UNITS = {
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
}
|
||||
|
||||
|
||||
class DataRateConverter(BaseUnitConverter):
|
||||
"""Utility to convert data rate values."""
|
||||
|
||||
|
||||
@@ -168,7 +168,6 @@ _TEST_FIXTURES: dict[str, list[str] | str] = {
|
||||
"service_calls": "list[ServiceCall]",
|
||||
"snapshot": "SnapshotAssertion",
|
||||
"socket_enabled": "None",
|
||||
"stub_blueprint_populate": "None",
|
||||
"tmp_path": "Path",
|
||||
"tmpdir": "py.path.local",
|
||||
"tts_mutagen_mock": "MagicMock",
|
||||
|
||||
@@ -48,7 +48,7 @@ dependencies = [
|
||||
"fnv-hash-fast==1.6.0",
|
||||
# hass-nabucasa is imported by helpers which don't depend on the cloud
|
||||
# integration
|
||||
"hass-nabucasa==1.10.0",
|
||||
"hass-nabucasa==1.11.0",
|
||||
# When bumping httpx, please check the version pins of
|
||||
# httpcore, anyio, and h11 in gen_requirements_all
|
||||
"httpx==0.28.1",
|
||||
|
||||
2
requirements.txt
generated
2
requirements.txt
generated
@@ -24,7 +24,7 @@ cronsim==2.7
|
||||
cryptography==46.0.2
|
||||
fnv-hash-fast==1.6.0
|
||||
ha-ffmpeg==3.2.2
|
||||
hass-nabucasa==1.10.0
|
||||
hass-nabucasa==1.11.0
|
||||
hassil==3.5.0
|
||||
home-assistant-bluetooth==1.13.1
|
||||
home-assistant-intents==2026.1.6
|
||||
|
||||
11
requirements_all.txt
generated
11
requirements_all.txt
generated
@@ -1171,7 +1171,7 @@ habluetooth==5.8.0
|
||||
hanna-cloud==0.0.7
|
||||
|
||||
# homeassistant.components.cloud
|
||||
hass-nabucasa==1.10.0
|
||||
hass-nabucasa==1.11.0
|
||||
|
||||
# homeassistant.components.splunk
|
||||
hass-splunk==0.1.1
|
||||
@@ -1226,6 +1226,9 @@ homelink-integration-api==0.0.1
|
||||
# homeassistant.components.homematicip_cloud
|
||||
homematicip==2.4.0
|
||||
|
||||
# homeassistant.components.homevolt
|
||||
homevolt==0.2.4
|
||||
|
||||
# homeassistant.components.horizon
|
||||
horimote==0.4.1
|
||||
|
||||
@@ -2045,7 +2048,7 @@ pyfibaro==0.8.3
|
||||
pyfido==2.1.2
|
||||
|
||||
# homeassistant.components.firefly_iii
|
||||
pyfirefly==0.1.11
|
||||
pyfirefly==0.1.12
|
||||
|
||||
# homeassistant.components.fireservicerota
|
||||
pyfireservicerota==0.0.46
|
||||
@@ -2377,7 +2380,7 @@ pysabnzbd==1.1.1
|
||||
pysaj==0.0.16
|
||||
|
||||
# homeassistant.components.saunum
|
||||
pysaunum==0.2.0
|
||||
pysaunum==0.3.0
|
||||
|
||||
# homeassistant.components.schlage
|
||||
pyschlage==2025.9.0
|
||||
@@ -3170,7 +3173,7 @@ wallbox==0.9.0
|
||||
watchdog==6.0.0
|
||||
|
||||
# homeassistant.components.waterfurnace
|
||||
waterfurnace==1.2.0
|
||||
waterfurnace==1.4.0
|
||||
|
||||
# homeassistant.components.watergate
|
||||
watergate-local-api==2025.1.0
|
||||
|
||||
9
requirements_test_all.txt
generated
9
requirements_test_all.txt
generated
@@ -1041,7 +1041,7 @@ habluetooth==5.8.0
|
||||
hanna-cloud==0.0.7
|
||||
|
||||
# homeassistant.components.cloud
|
||||
hass-nabucasa==1.10.0
|
||||
hass-nabucasa==1.11.0
|
||||
|
||||
# homeassistant.components.assist_satellite
|
||||
# homeassistant.components.conversation
|
||||
@@ -1084,6 +1084,9 @@ homelink-integration-api==0.0.1
|
||||
# homeassistant.components.homematicip_cloud
|
||||
homematicip==2.4.0
|
||||
|
||||
# homeassistant.components.homevolt
|
||||
homevolt==0.2.4
|
||||
|
||||
# homeassistant.components.remember_the_milk
|
||||
httplib2==0.20.4
|
||||
|
||||
@@ -1737,7 +1740,7 @@ pyfibaro==0.8.3
|
||||
pyfido==2.1.2
|
||||
|
||||
# homeassistant.components.firefly_iii
|
||||
pyfirefly==0.1.11
|
||||
pyfirefly==0.1.12
|
||||
|
||||
# homeassistant.components.fireservicerota
|
||||
pyfireservicerota==0.0.46
|
||||
@@ -2012,7 +2015,7 @@ pyrympro==0.0.9
|
||||
pysabnzbd==1.1.1
|
||||
|
||||
# homeassistant.components.saunum
|
||||
pysaunum==0.2.0
|
||||
pysaunum==0.3.0
|
||||
|
||||
# homeassistant.components.schlage
|
||||
pyschlage==2025.9.0
|
||||
|
||||
@@ -214,6 +214,10 @@ def gen_data_entry_schema(
|
||||
vol.Required("user"): translation_value_validator,
|
||||
str: translation_value_validator,
|
||||
}
|
||||
else:
|
||||
schema[vol.Optional("initiate_flow")] = {
|
||||
vol.Required("user"): translation_value_validator,
|
||||
}
|
||||
if flow_title == REQUIRED:
|
||||
schema[vol.Required("title")] = translation_value_validator
|
||||
elif flow_title == REMOVED:
|
||||
|
||||
@@ -100,6 +100,13 @@ async def target_entities(
|
||||
suggested_object_id=f"device_{domain}",
|
||||
device_id=device.id,
|
||||
)
|
||||
entity_reg.async_get_or_create(
|
||||
domain=domain,
|
||||
platform="test",
|
||||
unique_id=f"{domain}_device2",
|
||||
suggested_object_id=f"device2_{domain}",
|
||||
device_id=device.id,
|
||||
)
|
||||
entity_reg.async_get_or_create(
|
||||
domain=domain,
|
||||
platform="test",
|
||||
@@ -130,9 +137,11 @@ async def target_entities(
|
||||
return {
|
||||
"included": [
|
||||
f"{domain}.standalone_{domain}",
|
||||
f"{domain}.standalone2_{domain}",
|
||||
f"{domain}.label_{domain}",
|
||||
f"{domain}.area_{domain}",
|
||||
f"{domain}.device_{domain}",
|
||||
f"{domain}.device2_{domain}",
|
||||
],
|
||||
"excluded": [
|
||||
f"{domain}.standalone_{domain}_excluded",
|
||||
@@ -150,17 +159,22 @@ def parametrize_target_entities(domain: str) -> list[tuple[dict, str, int]]:
|
||||
"""
|
||||
return [
|
||||
(
|
||||
{CONF_ENTITY_ID: f"{domain}.standalone_{domain}"},
|
||||
{
|
||||
CONF_ENTITY_ID: [
|
||||
f"{domain}.standalone_{domain}",
|
||||
f"{domain}.standalone2_{domain}",
|
||||
]
|
||||
},
|
||||
f"{domain}.standalone_{domain}",
|
||||
1,
|
||||
2,
|
||||
),
|
||||
({ATTR_LABEL_ID: "test_label"}, f"{domain}.label_{domain}", 2),
|
||||
({ATTR_AREA_ID: "test_area"}, f"{domain}.area_{domain}", 2),
|
||||
({ATTR_FLOOR_ID: "test_floor"}, f"{domain}.area_{domain}", 2),
|
||||
({ATTR_LABEL_ID: "test_label"}, f"{domain}.device_{domain}", 2),
|
||||
({ATTR_AREA_ID: "test_area"}, f"{domain}.device_{domain}", 2),
|
||||
({ATTR_FLOOR_ID: "test_floor"}, f"{domain}.device_{domain}", 2),
|
||||
({ATTR_DEVICE_ID: "test_device"}, f"{domain}.device_{domain}", 1),
|
||||
({ATTR_LABEL_ID: "test_label"}, f"{domain}.label_{domain}", 3),
|
||||
({ATTR_AREA_ID: "test_area"}, f"{domain}.area_{domain}", 3),
|
||||
({ATTR_FLOOR_ID: "test_floor"}, f"{domain}.area_{domain}", 3),
|
||||
({ATTR_LABEL_ID: "test_label"}, f"{domain}.device_{domain}", 3),
|
||||
({ATTR_AREA_ID: "test_area"}, f"{domain}.device_{domain}", 3),
|
||||
({ATTR_FLOOR_ID: "test_floor"}, f"{domain}.device_{domain}", 3),
|
||||
({ATTR_DEVICE_ID: "test_device"}, f"{domain}.device_{domain}", 2),
|
||||
]
|
||||
|
||||
|
||||
@@ -184,18 +198,19 @@ class ConditionStateDescription(TypedDict):
|
||||
|
||||
included: _StateDescription # State for entities meant to be targeted
|
||||
excluded: _StateDescription # State for entities not meant to be targeted
|
||||
state_valid: bool # False if the state of the included entities is missing (None), unavailable or unknown
|
||||
|
||||
condition_true: bool # If the condition is expected to evaluate to true
|
||||
condition_true_first_entity: bool # If the condition is expected to evaluate to true for the first targeted entity
|
||||
|
||||
|
||||
def parametrize_condition_states(
|
||||
def _parametrize_condition_states(
|
||||
*,
|
||||
condition: str,
|
||||
condition_options: dict[str, Any] | None = None,
|
||||
target_states: list[str | None | tuple[str | None, dict]],
|
||||
other_states: list[str | None | tuple[str | None, dict]],
|
||||
additional_attributes: dict | None = None,
|
||||
additional_attributes: dict | None,
|
||||
condition_true_if_invalid: bool,
|
||||
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
||||
"""Parametrize states and expected condition evaluations.
|
||||
|
||||
@@ -212,7 +227,7 @@ def parametrize_condition_states(
|
||||
def state_with_attributes(
|
||||
state: str | None | tuple[str | None, dict],
|
||||
condition_true: bool,
|
||||
state_valid: bool,
|
||||
condition_true_first_entity: bool,
|
||||
) -> ConditionStateDescription:
|
||||
"""Return ConditionStateDescription dict."""
|
||||
if isinstance(state, str) or state is None:
|
||||
@@ -226,7 +241,7 @@ def parametrize_condition_states(
|
||||
"attributes": {},
|
||||
},
|
||||
"condition_true": condition_true,
|
||||
"state_valid": state_valid,
|
||||
"condition_true_first_entity": condition_true_first_entity,
|
||||
}
|
||||
return {
|
||||
"included": {
|
||||
@@ -238,7 +253,7 @@ def parametrize_condition_states(
|
||||
"attributes": state[1],
|
||||
},
|
||||
"condition_true": condition_true,
|
||||
"state_valid": state_valid,
|
||||
"condition_true_first_entity": condition_true_first_entity,
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -247,11 +262,19 @@ def parametrize_condition_states(
|
||||
condition_options,
|
||||
list(
|
||||
itertools.chain(
|
||||
(state_with_attributes(None, False, False),),
|
||||
(state_with_attributes(STATE_UNAVAILABLE, False, False),),
|
||||
(state_with_attributes(STATE_UNKNOWN, False, False),),
|
||||
(state_with_attributes(None, condition_true_if_invalid, True),),
|
||||
(
|
||||
state_with_attributes(other_state, False, True)
|
||||
state_with_attributes(
|
||||
STATE_UNAVAILABLE, condition_true_if_invalid, True
|
||||
),
|
||||
),
|
||||
(
|
||||
state_with_attributes(
|
||||
STATE_UNKNOWN, condition_true_if_invalid, True
|
||||
),
|
||||
),
|
||||
(
|
||||
state_with_attributes(other_state, False, False)
|
||||
for other_state in other_states
|
||||
),
|
||||
),
|
||||
@@ -263,8 +286,8 @@ def parametrize_condition_states(
|
||||
condition,
|
||||
condition_options,
|
||||
[
|
||||
state_with_attributes(other_states[0], False, True),
|
||||
state_with_attributes(target_state, True, True),
|
||||
state_with_attributes(other_states[0], False, False),
|
||||
state_with_attributes(target_state, True, False),
|
||||
],
|
||||
)
|
||||
for target_state in target_states
|
||||
@@ -272,6 +295,60 @@ def parametrize_condition_states(
|
||||
]
|
||||
|
||||
|
||||
def parametrize_condition_states_any(
|
||||
*,
|
||||
condition: str,
|
||||
condition_options: dict[str, Any] | None = None,
|
||||
target_states: list[str | None | tuple[str | None, dict]],
|
||||
other_states: list[str | None | tuple[str | None, dict]],
|
||||
additional_attributes: dict | None = None,
|
||||
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
||||
"""Parametrize states and expected condition evaluations.
|
||||
|
||||
The target_states and other_states iterables are either iterables of
|
||||
states or iterables of (state, attributes) tuples.
|
||||
|
||||
Returns a list of tuples with (condition, condition options, list of states),
|
||||
where states is a list of ConditionStateDescription dicts.
|
||||
"""
|
||||
|
||||
return _parametrize_condition_states(
|
||||
condition=condition,
|
||||
condition_options=condition_options,
|
||||
target_states=target_states,
|
||||
other_states=other_states,
|
||||
additional_attributes=additional_attributes,
|
||||
condition_true_if_invalid=False,
|
||||
)
|
||||
|
||||
|
||||
def parametrize_condition_states_all(
|
||||
*,
|
||||
condition: str,
|
||||
condition_options: dict[str, Any] | None = None,
|
||||
target_states: list[str | None | tuple[str | None, dict]],
|
||||
other_states: list[str | None | tuple[str | None, dict]],
|
||||
additional_attributes: dict | None = None,
|
||||
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
||||
"""Parametrize states and expected condition evaluations.
|
||||
|
||||
The target_states and other_states iterables are either iterables of
|
||||
states or iterables of (state, attributes) tuples.
|
||||
|
||||
Returns a list of tuples with (condition, condition options, list of states),
|
||||
where states is a list of ConditionStateDescription dicts.
|
||||
"""
|
||||
|
||||
return _parametrize_condition_states(
|
||||
condition=condition,
|
||||
condition_options=condition_options,
|
||||
target_states=target_states,
|
||||
other_states=other_states,
|
||||
additional_attributes=additional_attributes,
|
||||
condition_true_if_invalid=True,
|
||||
)
|
||||
|
||||
|
||||
def parametrize_trigger_states(
|
||||
*,
|
||||
trigger: str,
|
||||
|
||||
@@ -16,18 +16,14 @@ from tests.components import (
|
||||
assert_condition_gated_by_labs_flag,
|
||||
create_target_condition,
|
||||
other_states,
|
||||
parametrize_condition_states,
|
||||
parametrize_condition_states_all,
|
||||
parametrize_condition_states_any,
|
||||
parametrize_target_entities,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_alarm_control_panels(hass: HomeAssistant) -> list[str]:
|
||||
"""Create multiple alarm_control_panel entities associated with different targets."""
|
||||
@@ -61,7 +57,7 @@ async def test_alarm_control_panel_conditions_gated_by_labs_flag(
|
||||
@pytest.mark.parametrize(
|
||||
("condition", "condition_options", "states"),
|
||||
[
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="alarm_control_panel.is_armed",
|
||||
target_states=[
|
||||
AlarmControlPanelState.ARMED_AWAY,
|
||||
@@ -78,7 +74,7 @@ async def test_alarm_control_panel_conditions_gated_by_labs_flag(
|
||||
AlarmControlPanelState.TRIGGERED,
|
||||
],
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="alarm_control_panel.is_armed_away",
|
||||
target_states=[AlarmControlPanelState.ARMED_AWAY],
|
||||
other_states=other_states(AlarmControlPanelState.ARMED_AWAY),
|
||||
@@ -86,7 +82,7 @@ async def test_alarm_control_panel_conditions_gated_by_labs_flag(
|
||||
ATTR_SUPPORTED_FEATURES: AlarmControlPanelEntityFeature.ARM_AWAY
|
||||
},
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="alarm_control_panel.is_armed_home",
|
||||
target_states=[AlarmControlPanelState.ARMED_HOME],
|
||||
other_states=other_states(AlarmControlPanelState.ARMED_HOME),
|
||||
@@ -94,7 +90,7 @@ async def test_alarm_control_panel_conditions_gated_by_labs_flag(
|
||||
ATTR_SUPPORTED_FEATURES: AlarmControlPanelEntityFeature.ARM_HOME
|
||||
},
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="alarm_control_panel.is_armed_night",
|
||||
target_states=[AlarmControlPanelState.ARMED_NIGHT],
|
||||
other_states=other_states(AlarmControlPanelState.ARMED_NIGHT),
|
||||
@@ -102,7 +98,7 @@ async def test_alarm_control_panel_conditions_gated_by_labs_flag(
|
||||
ATTR_SUPPORTED_FEATURES: AlarmControlPanelEntityFeature.ARM_NIGHT
|
||||
},
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="alarm_control_panel.is_armed_vacation",
|
||||
target_states=[AlarmControlPanelState.ARMED_VACATION],
|
||||
other_states=other_states(AlarmControlPanelState.ARMED_VACATION),
|
||||
@@ -110,12 +106,12 @@ async def test_alarm_control_panel_conditions_gated_by_labs_flag(
|
||||
ATTR_SUPPORTED_FEATURES: AlarmControlPanelEntityFeature.ARM_VACATION
|
||||
},
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="alarm_control_panel.is_disarmed",
|
||||
target_states=[AlarmControlPanelState.DISARMED],
|
||||
other_states=other_states(AlarmControlPanelState.DISARMED),
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="alarm_control_panel.is_triggered",
|
||||
target_states=[AlarmControlPanelState.TRIGGERED],
|
||||
other_states=other_states(AlarmControlPanelState.TRIGGERED),
|
||||
@@ -168,7 +164,7 @@ async def test_alarm_control_panel_state_condition_behavior_any(
|
||||
@pytest.mark.parametrize(
|
||||
("condition", "condition_options", "states"),
|
||||
[
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="alarm_control_panel.is_armed",
|
||||
target_states=[
|
||||
AlarmControlPanelState.ARMED_AWAY,
|
||||
@@ -185,7 +181,7 @@ async def test_alarm_control_panel_state_condition_behavior_any(
|
||||
AlarmControlPanelState.TRIGGERED,
|
||||
],
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="alarm_control_panel.is_armed_away",
|
||||
target_states=[AlarmControlPanelState.ARMED_AWAY],
|
||||
other_states=other_states(AlarmControlPanelState.ARMED_AWAY),
|
||||
@@ -193,7 +189,7 @@ async def test_alarm_control_panel_state_condition_behavior_any(
|
||||
ATTR_SUPPORTED_FEATURES: AlarmControlPanelEntityFeature.ARM_AWAY
|
||||
},
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="alarm_control_panel.is_armed_home",
|
||||
target_states=[AlarmControlPanelState.ARMED_HOME],
|
||||
other_states=other_states(AlarmControlPanelState.ARMED_HOME),
|
||||
@@ -201,7 +197,7 @@ async def test_alarm_control_panel_state_condition_behavior_any(
|
||||
ATTR_SUPPORTED_FEATURES: AlarmControlPanelEntityFeature.ARM_HOME
|
||||
},
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="alarm_control_panel.is_armed_night",
|
||||
target_states=[AlarmControlPanelState.ARMED_NIGHT],
|
||||
other_states=other_states(AlarmControlPanelState.ARMED_NIGHT),
|
||||
@@ -209,7 +205,7 @@ async def test_alarm_control_panel_state_condition_behavior_any(
|
||||
ATTR_SUPPORTED_FEATURES: AlarmControlPanelEntityFeature.ARM_NIGHT
|
||||
},
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="alarm_control_panel.is_armed_vacation",
|
||||
target_states=[AlarmControlPanelState.ARMED_VACATION],
|
||||
other_states=other_states(AlarmControlPanelState.ARMED_VACATION),
|
||||
@@ -217,12 +213,12 @@ async def test_alarm_control_panel_state_condition_behavior_any(
|
||||
ATTR_SUPPORTED_FEATURES: AlarmControlPanelEntityFeature.ARM_VACATION
|
||||
},
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="alarm_control_panel.is_disarmed",
|
||||
target_states=[AlarmControlPanelState.DISARMED],
|
||||
other_states=other_states(AlarmControlPanelState.DISARMED),
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="alarm_control_panel.is_triggered",
|
||||
target_states=[AlarmControlPanelState.TRIGGERED],
|
||||
other_states=other_states(AlarmControlPanelState.TRIGGERED),
|
||||
@@ -259,17 +255,10 @@ async def test_alarm_control_panel_state_condition_behavior_all(
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
# The condition passes if all entities are either in a target state or invalid
|
||||
assert condition(hass) == (
|
||||
(not state["state_valid"])
|
||||
or (state["condition_true"] and entities_in_target == 1)
|
||||
)
|
||||
assert condition(hass) == state["condition_true_first_entity"]
|
||||
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# The condition passes if all entities are either in a target state or invalid
|
||||
assert condition(hass) == (
|
||||
(not state["state_valid"]) or state["condition_true"]
|
||||
)
|
||||
assert condition(hass) == state["condition_true"]
|
||||
|
||||
@@ -25,11 +25,6 @@ from tests.common import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("set_state", "features_reg", "features_state", "expected_action_types"),
|
||||
[
|
||||
|
||||
@@ -18,11 +18,6 @@ from homeassistant.setup import async_setup_component
|
||||
from tests.common import MockConfigEntry, async_get_device_automations
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("set_state", "features_reg", "features_state", "expected_condition_types"),
|
||||
[
|
||||
|
||||
@@ -26,11 +26,6 @@ from tests.common import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("set_state", "features_reg", "features_state", "expected_trigger_types"),
|
||||
[
|
||||
|
||||
@@ -22,11 +22,6 @@ from tests.components import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_alarm_control_panels(hass: HomeAssistant) -> list[str]:
|
||||
"""Create multiple alarm control panel entities associated with different targets."""
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""The tests for Arcam FMJ Receiver control device triggers."""
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import automation
|
||||
from homeassistant.components.arcam_fmj.const import DOMAIN
|
||||
from homeassistant.components.device_automation import DeviceAutomationType
|
||||
@@ -12,11 +10,6 @@ from homeassistant.setup import async_setup_component
|
||||
from tests.common import MockConfigEntry, async_get_device_automations
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
async def test_get_triggers(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
||||
@@ -12,18 +12,14 @@ from tests.components import (
|
||||
assert_condition_gated_by_labs_flag,
|
||||
create_target_condition,
|
||||
other_states,
|
||||
parametrize_condition_states,
|
||||
parametrize_condition_states_all,
|
||||
parametrize_condition_states_any,
|
||||
parametrize_target_entities,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_assist_satellites(hass: HomeAssistant) -> list[str]:
|
||||
"""Create multiple assist satellite entities associated with different targets."""
|
||||
@@ -54,22 +50,22 @@ async def test_assist_satellite_conditions_gated_by_labs_flag(
|
||||
@pytest.mark.parametrize(
|
||||
("condition", "condition_options", "states"),
|
||||
[
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="assist_satellite.is_idle",
|
||||
target_states=[AssistSatelliteState.IDLE],
|
||||
other_states=other_states(AssistSatelliteState.IDLE),
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="assist_satellite.is_listening",
|
||||
target_states=[AssistSatelliteState.LISTENING],
|
||||
other_states=other_states(AssistSatelliteState.LISTENING),
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="assist_satellite.is_processing",
|
||||
target_states=[AssistSatelliteState.PROCESSING],
|
||||
other_states=other_states(AssistSatelliteState.PROCESSING),
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="assist_satellite.is_responding",
|
||||
target_states=[AssistSatelliteState.RESPONDING],
|
||||
other_states=other_states(AssistSatelliteState.RESPONDING),
|
||||
@@ -122,22 +118,22 @@ async def test_assist_satellite_state_condition_behavior_any(
|
||||
@pytest.mark.parametrize(
|
||||
("condition", "condition_options", "states"),
|
||||
[
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="assist_satellite.is_idle",
|
||||
target_states=[AssistSatelliteState.IDLE],
|
||||
other_states=other_states(AssistSatelliteState.IDLE),
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="assist_satellite.is_listening",
|
||||
target_states=[AssistSatelliteState.LISTENING],
|
||||
other_states=other_states(AssistSatelliteState.LISTENING),
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="assist_satellite.is_processing",
|
||||
target_states=[AssistSatelliteState.PROCESSING],
|
||||
other_states=other_states(AssistSatelliteState.PROCESSING),
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="assist_satellite.is_responding",
|
||||
target_states=[AssistSatelliteState.RESPONDING],
|
||||
other_states=other_states(AssistSatelliteState.RESPONDING),
|
||||
@@ -174,17 +170,10 @@ async def test_assist_satellite_state_condition_behavior_all(
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
# The condition passes if all entities are either in a target state or invalid
|
||||
assert condition(hass) == (
|
||||
(not state["state_valid"])
|
||||
or (state["condition_true"] and entities_in_target == 1)
|
||||
)
|
||||
assert condition(hass) == state["condition_true_first_entity"]
|
||||
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# The condition passes if all entities are either in a target state or invalid
|
||||
assert condition(hass) == (
|
||||
(not state["state_valid"]) or state["condition_true"]
|
||||
)
|
||||
assert condition(hass) == state["condition_true"]
|
||||
|
||||
@@ -19,11 +19,6 @@ from tests.components import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_assist_satellites(hass: HomeAssistant) -> list[str]:
|
||||
"""Create multiple assist satellite entities associated with different targets."""
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
"""Conftest for automation tests."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
@@ -26,11 +26,6 @@ from tests.common import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
async def test_get_conditions(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
||||
@@ -26,11 +26,6 @@ from tests.common import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
async def test_get_triggers(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
||||
@@ -23,11 +23,6 @@ from tests.components import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_binary_sensors(hass: HomeAssistant) -> tuple[list[str], list[str]]:
|
||||
"""Create multiple binary sensor entities associated with different targets."""
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
"""Blueprints test helpers."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
def stub_blueprint_populate_fixture_helper() -> Generator[None]:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
with patch(
|
||||
"homeassistant.components.blueprint.models.DomainBlueprints.async_populate"
|
||||
):
|
||||
yield
|
||||
@@ -1,8 +0,0 @@
|
||||
"""Blueprints conftest."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
@@ -19,11 +19,6 @@ from tests.components import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_buttons(hass: HomeAssistant) -> list[str]:
|
||||
"""Create multiple button entities associated with different targets."""
|
||||
|
||||
@@ -24,11 +24,6 @@ from tests.common import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("set_state", "features_reg", "features_state", "expected_action_types"),
|
||||
[
|
||||
|
||||
@@ -20,11 +20,6 @@ from homeassistant.setup import async_setup_component
|
||||
from tests.common import MockConfigEntry, async_get_device_automations
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("set_state", "features_reg", "features_state", "expected_condition_types"),
|
||||
[
|
||||
|
||||
@@ -26,11 +26,6 @@ from homeassistant.setup import async_setup_component
|
||||
from tests.common import MockConfigEntry, async_get_device_automations
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
async def test_get_triggers(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
||||
@@ -38,11 +38,6 @@ from tests.components import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_climates(hass: HomeAssistant) -> list[str]:
|
||||
"""Create multiple climate entities associated with different targets."""
|
||||
|
||||
@@ -18,16 +18,10 @@ from homeassistant.util import yaml as yaml_util
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def setup_automation(
|
||||
hass: HomeAssistant,
|
||||
automation_config: dict[str, Any],
|
||||
stub_blueprint_populate: None,
|
||||
) -> None:
|
||||
"""Set up automation integration."""
|
||||
assert await async_setup_component(
|
||||
|
||||
@@ -17,11 +17,6 @@ from tests.common import MockConfigEntry, MockModule, mock_integration
|
||||
from tests.typing import MockHAClientWebSocket, WebSocketGenerator
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
async def client_fixture(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
|
||||
@@ -18,11 +18,6 @@ from homeassistant.util import yaml as yaml_util
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def setup_script(hass: HomeAssistant, script_config: dict[str, Any]) -> None:
|
||||
"""Set up script integration."""
|
||||
|
||||
@@ -108,17 +108,6 @@ def entity_registry_enabled_by_default() -> Generator[None]:
|
||||
yield
|
||||
|
||||
|
||||
# Blueprint test fixtures
|
||||
@pytest.fixture(name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_fixture() -> Generator[None]:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
from .blueprint.common import ( # noqa: PLC0415
|
||||
stub_blueprint_populate_fixture_helper,
|
||||
)
|
||||
|
||||
yield from stub_blueprint_populate_fixture_helper()
|
||||
|
||||
|
||||
# TTS test fixtures
|
||||
@pytest.fixture(name="mock_tts_get_cache_files")
|
||||
def mock_tts_get_cache_files_fixture() -> Generator[MagicMock]:
|
||||
|
||||
@@ -23,11 +23,6 @@ from tests.common import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("set_state", "features_reg", "features_state", "expected_action_types"),
|
||||
[
|
||||
|
||||
@@ -22,11 +22,6 @@ from tests.common import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("set_state", "features_reg", "features_state", "expected_condition_types"),
|
||||
[
|
||||
|
||||
@@ -26,11 +26,6 @@ from tests.common import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("set_state", "features_reg", "features_state", "expected_trigger_types"),
|
||||
[
|
||||
|
||||
@@ -39,11 +39,6 @@ from .conftest import WebsocketDataType
|
||||
from tests.common import MockConfigEntry, async_get_device_automations
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sensor_payload",
|
||||
[
|
||||
|
||||
@@ -10,11 +10,6 @@ from homeassistant.helpers import recorder as recorder_helper
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_ssdp():
|
||||
"""Mock ssdp."""
|
||||
|
||||
@@ -10,11 +10,6 @@ from homeassistant.setup import async_setup_component
|
||||
from tests.components.light.conftest import mock_light_profiles # noqa: F401
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def setup_homeassistant(hass: HomeAssistant):
|
||||
"""Set up the homeassistant integration."""
|
||||
|
||||
@@ -34,11 +34,6 @@ class MockDeviceEntry(dr.DeviceEntry):
|
||||
id: str = attr.ib(default="very_unique")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_integration(hass: HomeAssistant) -> None:
|
||||
"""Set up a mock integration with device automation support."""
|
||||
|
||||
@@ -14,11 +14,6 @@ from homeassistant.util import dt as dt_util
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
async def test_if_fires_on_state_change(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
||||
@@ -15,11 +15,6 @@ from homeassistant.setup import async_setup_component
|
||||
from tests.common import MockConfigEntry, async_get_device_automations
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
async def test_get_conditions(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
||||
@@ -19,12 +19,6 @@ from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry, async_get_device_automations
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
AWAY_LATITUDE = 32.881011
|
||||
AWAY_LONGITUDE = -117.234758
|
||||
|
||||
|
||||
@@ -24,11 +24,6 @@ from tests.components import (
|
||||
STATE_WORK_ZONE = "work"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_device_trackers(hass: HomeAssistant) -> list[str]:
|
||||
"""Create multiple device_trackers entities associated with different targets."""
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
"""Conftest for emulated_hue tests."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
@@ -11,18 +11,14 @@ from tests.components import (
|
||||
ConditionStateDescription,
|
||||
assert_condition_gated_by_labs_flag,
|
||||
create_target_condition,
|
||||
parametrize_condition_states,
|
||||
parametrize_condition_states_all,
|
||||
parametrize_condition_states_any,
|
||||
parametrize_target_entities,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_fans(hass: HomeAssistant) -> list[str]:
|
||||
"""Create multiple fan entities associated with different targets."""
|
||||
@@ -57,12 +53,12 @@ async def test_fan_conditions_gated_by_labs_flag(
|
||||
@pytest.mark.parametrize(
|
||||
("condition", "condition_options", "states"),
|
||||
[
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="fan.is_on",
|
||||
target_states=[STATE_ON],
|
||||
other_states=[STATE_OFF],
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_any(
|
||||
condition="fan.is_off",
|
||||
target_states=[STATE_OFF],
|
||||
other_states=[STATE_ON],
|
||||
@@ -123,12 +119,12 @@ async def test_fan_state_condition_behavior_any(
|
||||
@pytest.mark.parametrize(
|
||||
("condition", "condition_options", "states"),
|
||||
[
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="fan.is_on",
|
||||
target_states=[STATE_ON],
|
||||
other_states=[STATE_OFF],
|
||||
),
|
||||
*parametrize_condition_states(
|
||||
*parametrize_condition_states_all(
|
||||
condition="fan.is_off",
|
||||
target_states=[STATE_OFF],
|
||||
other_states=[STATE_ON],
|
||||
@@ -169,17 +165,10 @@ async def test_fan_state_condition_behavior_all(
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
# The condition passes if all entities are either in a target state or invalid
|
||||
assert condition(hass) == (
|
||||
(not state["state_valid"])
|
||||
or (state["condition_true"] and entities_in_target == 1)
|
||||
)
|
||||
assert condition(hass) == state["condition_true_first_entity"]
|
||||
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# The condition passes if all entities are either in a target state or invalid
|
||||
assert condition(hass) == (
|
||||
(not state["state_valid"]) or state["condition_true"]
|
||||
)
|
||||
assert condition(hass) == state["condition_true"]
|
||||
|
||||
@@ -19,11 +19,6 @@ from tests.common import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
async def test_get_actions(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
||||
@@ -15,11 +15,6 @@ from homeassistant.setup import async_setup_component
|
||||
from tests.common import MockConfigEntry, async_get_device_automations
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
async def test_get_conditions(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
||||
@@ -23,11 +23,6 @@ from tests.common import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
async def test_get_triggers(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
||||
@@ -17,11 +17,6 @@ from tests.components import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||
"""Stub copying the blueprints to the config folder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_fans(hass: HomeAssistant) -> list[str]:
|
||||
"""Create multiple fan entities associated with different targets."""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user