mirror of
https://github.com/home-assistant/core.git
synced 2025-10-13 05:39:37 +00:00
Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
82758f7671 | ||
![]() |
7739cdc626 | ||
![]() |
4ca1ae61aa | ||
![]() |
3d130a9bdf | ||
![]() |
2b38f33d50 | ||
![]() |
19dedb038e | ||
![]() |
59781422f7 | ||
![]() |
083277d1ff | ||
![]() |
9b9c55b37b | ||
![]() |
c9d67d596b | ||
![]() |
7948b35265 | ||
![]() |
be843970fd | ||
![]() |
53b65b2fb4 | ||
![]() |
ac7be97245 | ||
![]() |
09e539bf0e | ||
![]() |
6ef1b3bad3 | ||
![]() |
38e46f7a53 | ||
![]() |
ef60d16659 | ||
![]() |
bf4f8b48a3 | ||
![]() |
3c1496d2bb | ||
![]() |
d457787639 | ||
![]() |
de4bfd6f05 | ||
![]() |
34c5748132 | ||
![]() |
5bfd9620db | ||
![]() |
6f8766e4bd | ||
![]() |
d3b519846b | ||
![]() |
36d952800b | ||
![]() |
b832561e53 | ||
![]() |
c59d295bf2 | ||
![]() |
6e28e3aed1 | ||
![]() |
6d8944d379 | ||
![]() |
762fd6d241 | ||
![]() |
4c6500e7a4 |
@@ -71,7 +71,14 @@ class AemetConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
}
|
||||
)
|
||||
|
||||
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=schema,
|
||||
errors=errors,
|
||||
description_placeholders={
|
||||
"api_key_url": "https://opendata.aemet.es/centrodedescargas/altaUsuario"
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
|
@@ -14,7 +14,7 @@
|
||||
"longitude": "[%key:common::config_flow::data::longitude%]",
|
||||
"name": "Name of the integration"
|
||||
},
|
||||
"description": "To generate API key go to https://opendata.aemet.es/centrodedescargas/altaUsuario"
|
||||
"description": "To generate API key go to {api_key_url}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -18,6 +18,10 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import CONF_USE_NEAREST, DOMAIN, NO_AIRLY_SENSORS
|
||||
|
||||
DESCRIPTION_PLACEHOLDERS = {
|
||||
"developer_registration_url": "https://developer.airly.eu/register",
|
||||
}
|
||||
|
||||
|
||||
class AirlyFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow for Airly."""
|
||||
@@ -85,6 +89,7 @@ class AirlyFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
description_placeholders=DESCRIPTION_PLACEHOLDERS,
|
||||
)
|
||||
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "To generate API key go to https://developer.airly.eu/register",
|
||||
"description": "To generate API key go to {developer_registration_url}",
|
||||
"data": {
|
||||
"name": "[%key:common::config_flow::data::name%]",
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||
|
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioamazondevices"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aioamazondevices==6.4.0"]
|
||||
"requirements": ["aioamazondevices==6.4.3"]
|
||||
}
|
||||
|
@@ -36,11 +36,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: AugustConfigEntry) -> bo
|
||||
raise ConfigEntryAuthFailed("Migration to OAuth required")
|
||||
|
||||
session = async_create_august_clientsession(hass)
|
||||
implementation = (
|
||||
await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||
hass, entry
|
||||
try:
|
||||
implementation = (
|
||||
await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||
hass, entry
|
||||
)
|
||||
)
|
||||
)
|
||||
except ValueError as err:
|
||||
raise ConfigEntryNotReady("OAuth implementation not available") from err
|
||||
oauth_session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)
|
||||
august_gateway = AugustGateway(Path(hass.config.config_dir), session, oauth_session)
|
||||
try:
|
||||
|
@@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/control4",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyControl4"],
|
||||
"requirements": ["pyControl4==1.2.0"],
|
||||
"requirements": ["pyControl4==1.5.0"],
|
||||
"ssdp": [
|
||||
{
|
||||
"st": "c4:director"
|
||||
|
@@ -47,11 +47,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ECConfigEntry) ->
|
||||
radar_coordinator = ECDataUpdateCoordinator(
|
||||
hass, config_entry, radar_data, "radar", DEFAULT_RADAR_UPDATE_INTERVAL
|
||||
)
|
||||
try:
|
||||
await radar_coordinator.async_config_entry_first_refresh()
|
||||
except ConfigEntryNotReady:
|
||||
errors = errors + 1
|
||||
_LOGGER.warning("Unable to retrieve Environment Canada radar")
|
||||
# Skip initial refresh for radar since the camera entity is disabled by default.
|
||||
# The coordinator will fetch data when the entity is enabled.
|
||||
|
||||
aqhi_data = ECAirQuality(coordinates=(lat, lon))
|
||||
aqhi_coordinator = ECDataUpdateCoordinator(
|
||||
@@ -63,7 +60,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ECConfigEntry) ->
|
||||
errors = errors + 1
|
||||
_LOGGER.warning("Unable to retrieve Environment Canada AQHI")
|
||||
|
||||
if errors == 3:
|
||||
# Require at least one coordinator to succeed (weather or AQHI)
|
||||
# Radar is optional since the camera entity is disabled by default
|
||||
if errors >= 2:
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
config_entry.runtime_data = ECRuntimeData(
|
||||
|
@@ -59,6 +59,14 @@ class ECCameraEntity(CoordinatorEntity[ECDataUpdateCoordinator[ECRadar]], Camera
|
||||
|
||||
self.content_type = "image/gif"
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""When entity is added to hass."""
|
||||
await super().async_added_to_hass()
|
||||
# Trigger coordinator refresh when entity is enabled
|
||||
# since radar coordinator skips initial refresh during setup
|
||||
if not self.coordinator.last_update_success:
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
def camera_image(
|
||||
self, width: int | None = None, height: int | None = None
|
||||
) -> bytes | None:
|
||||
|
@@ -6,11 +6,18 @@ import xml.etree.ElementTree as ET
|
||||
|
||||
import aiohttp
|
||||
from env_canada import ECWeather, ec_exc
|
||||
from env_canada.ec_weather import get_ec_sites_list
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_LANGUAGE, CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.selector import (
|
||||
SelectOptionDict,
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
)
|
||||
|
||||
from .const import CONF_STATION, CONF_TITLE, DOMAIN
|
||||
|
||||
@@ -25,14 +32,16 @@ async def validate_input(data):
|
||||
lang = data.get(CONF_LANGUAGE).lower()
|
||||
|
||||
if station:
|
||||
# When station is provided, use it and get the coordinates from ECWeather
|
||||
weather_data = ECWeather(station_id=station, language=lang)
|
||||
else:
|
||||
weather_data = ECWeather(coordinates=(lat, lon), language=lang)
|
||||
await weather_data.update()
|
||||
|
||||
if lat is None or lon is None:
|
||||
await weather_data.update()
|
||||
# Always use the station's coordinates, not the user-provided ones
|
||||
lat = weather_data.lat
|
||||
lon = weather_data.lon
|
||||
else:
|
||||
# When no station is provided, use coordinates to find nearest station
|
||||
weather_data = ECWeather(coordinates=(lat, lon), language=lang)
|
||||
await weather_data.update()
|
||||
|
||||
return {
|
||||
CONF_TITLE: weather_data.metadata.location,
|
||||
@@ -46,6 +55,13 @@ class EnvironmentCanadaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Environment Canada weather."""
|
||||
|
||||
VERSION = 1
|
||||
_station_codes: list[dict[str, str]] | None = None
|
||||
|
||||
async def _get_station_codes(self) -> list[dict[str, str]]:
|
||||
"""Get station codes, cached after first call."""
|
||||
if self._station_codes is None:
|
||||
self._station_codes = await get_ec_sites_list()
|
||||
return self._station_codes
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -80,9 +96,21 @@ class EnvironmentCanadaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(title=info[CONF_TITLE], data=user_input)
|
||||
|
||||
station_codes = await self._get_station_codes()
|
||||
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_STATION): str,
|
||||
vol.Optional(CONF_STATION): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=[
|
||||
SelectOptionDict(
|
||||
value=station["value"], label=station["label"]
|
||||
)
|
||||
for station in station_codes
|
||||
],
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
vol.Optional(
|
||||
CONF_LATITUDE, default=self.hass.config.latitude
|
||||
): cv.latitude,
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["env_canada"],
|
||||
"requirements": ["env-canada==0.11.3"]
|
||||
"requirements": ["env-canada==0.12.1"]
|
||||
}
|
||||
|
@@ -3,11 +3,11 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Environment Canada: weather location and language",
|
||||
"description": "Either a station ID or latitude/longitude must be specified. The default latitude/longitude used are the values configured in your Home Assistant installation. The closest weather station to the coordinates will be used if specifying coordinates. If a station code is used it must follow the format: PP/code, where PP is the two-letter province and code is the station ID. The list of station IDs can be found here: https://dd.weather.gc.ca/citypage_weather/docs/site_list_towns_en.csv. Weather information can be retrieved in either English or French.",
|
||||
"description": "Select a weather station from the dropdown, or specify coordinates to use the closest station. The default coordinates are from your Home Assistant installation. Weather information can be retrieved in English or French.",
|
||||
"data": {
|
||||
"latitude": "[%key:common::config_flow::data::latitude%]",
|
||||
"longitude": "[%key:common::config_flow::data::longitude%]",
|
||||
"station": "Weather station ID",
|
||||
"station": "Weather station",
|
||||
"language": "Weather information language"
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
|
||||
},
|
||||
"error": {
|
||||
"bad_station_id": "Station ID is invalid, missing, or not found in the station ID database",
|
||||
"bad_station_id": "Station code is invalid, missing, or not found in the station code database",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"error_response": "Response from Environment Canada in error",
|
||||
"too_many_attempts": "Connections to Environment Canada are rate limited; Try again in 60 seconds",
|
||||
|
@@ -17,7 +17,7 @@
|
||||
"mqtt": ["esphome/discover/#"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": [
|
||||
"aioesphomeapi==41.13.0",
|
||||
"aioesphomeapi==41.14.0",
|
||||
"esphome-dashboard-api==1.3.0",
|
||||
"bleak-esphome==3.4.0"
|
||||
],
|
||||
|
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyheos"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyheos==1.0.5"],
|
||||
"requirements": ["pyheos==1.0.6"],
|
||||
"ssdp": [
|
||||
{
|
||||
"st": "urn:schemas-denon-com:device:ACT-Denon:1"
|
||||
|
@@ -158,7 +158,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
has_fn=lambda data: data.measurement.energy_import_kwh is not None,
|
||||
value_fn=lambda data: data.measurement.energy_import_kwh,
|
||||
value_fn=lambda data: data.measurement.energy_import_kwh or None,
|
||||
),
|
||||
HomeWizardSensorEntityDescription(
|
||||
key="total_power_import_t1_kwh",
|
||||
@@ -172,7 +172,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
|
||||
data.measurement.energy_import_t1_kwh is not None
|
||||
and data.measurement.energy_export_t2_kwh is not None
|
||||
),
|
||||
value_fn=lambda data: data.measurement.energy_import_t1_kwh,
|
||||
value_fn=lambda data: data.measurement.energy_import_t1_kwh or None,
|
||||
),
|
||||
HomeWizardSensorEntityDescription(
|
||||
key="total_power_import_t2_kwh",
|
||||
@@ -182,7 +182,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
has_fn=lambda data: data.measurement.energy_import_t2_kwh is not None,
|
||||
value_fn=lambda data: data.measurement.energy_import_t2_kwh,
|
||||
value_fn=lambda data: data.measurement.energy_import_t2_kwh or None,
|
||||
),
|
||||
HomeWizardSensorEntityDescription(
|
||||
key="total_power_import_t3_kwh",
|
||||
@@ -192,7 +192,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
has_fn=lambda data: data.measurement.energy_import_t3_kwh is not None,
|
||||
value_fn=lambda data: data.measurement.energy_import_t3_kwh,
|
||||
value_fn=lambda data: data.measurement.energy_import_t3_kwh or None,
|
||||
),
|
||||
HomeWizardSensorEntityDescription(
|
||||
key="total_power_import_t4_kwh",
|
||||
@@ -202,7 +202,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
has_fn=lambda data: data.measurement.energy_import_t4_kwh is not None,
|
||||
value_fn=lambda data: data.measurement.energy_import_t4_kwh,
|
||||
value_fn=lambda data: data.measurement.energy_import_t4_kwh or None,
|
||||
),
|
||||
HomeWizardSensorEntityDescription(
|
||||
key="total_power_export_kwh",
|
||||
@@ -212,7 +212,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
has_fn=lambda data: data.measurement.energy_export_kwh is not None,
|
||||
enabled_fn=lambda data: data.measurement.energy_export_kwh != 0,
|
||||
value_fn=lambda data: data.measurement.energy_export_kwh,
|
||||
value_fn=lambda data: data.measurement.energy_export_kwh or None,
|
||||
),
|
||||
HomeWizardSensorEntityDescription(
|
||||
key="total_power_export_t1_kwh",
|
||||
@@ -227,7 +227,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
|
||||
and data.measurement.energy_export_t2_kwh is not None
|
||||
),
|
||||
enabled_fn=lambda data: data.measurement.energy_export_t1_kwh != 0,
|
||||
value_fn=lambda data: data.measurement.energy_export_t1_kwh,
|
||||
value_fn=lambda data: data.measurement.energy_export_t1_kwh or None,
|
||||
),
|
||||
HomeWizardSensorEntityDescription(
|
||||
key="total_power_export_t2_kwh",
|
||||
@@ -238,7 +238,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
has_fn=lambda data: data.measurement.energy_export_t2_kwh is not None,
|
||||
enabled_fn=lambda data: data.measurement.energy_export_t2_kwh != 0,
|
||||
value_fn=lambda data: data.measurement.energy_export_t2_kwh,
|
||||
value_fn=lambda data: data.measurement.energy_export_t2_kwh or None,
|
||||
),
|
||||
HomeWizardSensorEntityDescription(
|
||||
key="total_power_export_t3_kwh",
|
||||
@@ -249,7 +249,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
has_fn=lambda data: data.measurement.energy_export_t3_kwh is not None,
|
||||
enabled_fn=lambda data: data.measurement.energy_export_t3_kwh != 0,
|
||||
value_fn=lambda data: data.measurement.energy_export_t3_kwh,
|
||||
value_fn=lambda data: data.measurement.energy_export_t3_kwh or None,
|
||||
),
|
||||
HomeWizardSensorEntityDescription(
|
||||
key="total_power_export_t4_kwh",
|
||||
@@ -260,7 +260,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
has_fn=lambda data: data.measurement.energy_export_t4_kwh is not None,
|
||||
enabled_fn=lambda data: data.measurement.energy_export_t4_kwh != 0,
|
||||
value_fn=lambda data: data.measurement.energy_export_t4_kwh,
|
||||
value_fn=lambda data: data.measurement.energy_export_t4_kwh or None,
|
||||
),
|
||||
HomeWizardSensorEntityDescription(
|
||||
key="active_power_w",
|
||||
|
@@ -89,12 +89,12 @@ class AutomowerBaseEntity(CoordinatorEntity[AutomowerDataUpdateCoordinator]):
|
||||
"""Initialize AutomowerEntity."""
|
||||
super().__init__(coordinator)
|
||||
self.mower_id = mower_id
|
||||
parts = self.mower_attributes.system.model.split(maxsplit=2)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, mower_id)},
|
||||
manufacturer="Husqvarna",
|
||||
model=self.mower_attributes.system.model.removeprefix(
|
||||
"HUSQVARNA "
|
||||
).removeprefix("Husqvarna "),
|
||||
manufacturer=parts[0],
|
||||
model=parts[1],
|
||||
model_id=parts[2],
|
||||
name=self.mower_attributes.system.name,
|
||||
serial_number=self.mower_attributes.system.serial_number,
|
||||
suggested_area="Garden",
|
||||
|
@@ -134,4 +134,8 @@ class MastodonConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
data=user_input,
|
||||
)
|
||||
|
||||
return self.show_user_form(user_input, errors)
|
||||
return self.show_user_form(
|
||||
user_input,
|
||||
errors,
|
||||
description_placeholders={"example_url": "https://mastodon.social"},
|
||||
)
|
||||
|
@@ -9,7 +9,7 @@
|
||||
"access_token": "[%key:common::config_flow::data::access_token%]"
|
||||
},
|
||||
"data_description": {
|
||||
"base_url": "The URL of your Mastodon instance e.g. https://mastodon.social.",
|
||||
"base_url": "The URL of your Mastodon instance e.g. {example_url}.",
|
||||
"client_id": "The client key for the application created within your Mastodon account.",
|
||||
"client_secret": "The client secret for the application created within your Mastodon account.",
|
||||
"access_token": "The access token for the application created within your Mastodon account."
|
||||
|
@@ -150,7 +150,6 @@ from .const import (
|
||||
CONF_ACTION_TOPIC,
|
||||
CONF_AVAILABILITY_TEMPLATE,
|
||||
CONF_AVAILABILITY_TOPIC,
|
||||
CONF_AVAILABLE_TONES,
|
||||
CONF_BIRTH_MESSAGE,
|
||||
CONF_BLUE_TEMPLATE,
|
||||
CONF_BRIGHTNESS_COMMAND_TEMPLATE,
|
||||
@@ -308,8 +307,6 @@ from .const import (
|
||||
CONF_STATE_VALUE_TEMPLATE,
|
||||
CONF_STEP,
|
||||
CONF_SUGGESTED_DISPLAY_PRECISION,
|
||||
CONF_SUPPORT_DURATION,
|
||||
CONF_SUPPORT_VOLUME_SET,
|
||||
CONF_SUPPORTED_COLOR_MODES,
|
||||
CONF_SUPPORTED_FEATURES,
|
||||
CONF_SWING_HORIZONTAL_MODE_COMMAND_TEMPLATE,
|
||||
@@ -463,7 +460,6 @@ SUBENTRY_PLATFORMS = [
|
||||
Platform.NUMBER,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SIREN,
|
||||
Platform.SWITCH,
|
||||
]
|
||||
|
||||
@@ -474,26 +470,6 @@ _CODE_VALIDATION_MODE = {
|
||||
EXCLUDE_FROM_CONFIG_IF_NONE = {CONF_ENTITY_CATEGORY}
|
||||
PWD_NOT_CHANGED = "__**password_not_changed**__"
|
||||
|
||||
DEVELOPER_DOCUMENTATION_URL = "https://developers.home-assistant.io/"
|
||||
USER_DOCUMENTATION_URL = "https://www.home-assistant.io/"
|
||||
|
||||
INTEGRATION_URL = f"{USER_DOCUMENTATION_URL}integrations/{DOMAIN}/"
|
||||
TEMPLATING_URL = f"{USER_DOCUMENTATION_URL}docs/configuration/templating/"
|
||||
AVAILABLE_STATE_CLASSES_URL = (
|
||||
f"{DEVELOPER_DOCUMENTATION_URL}docs/core/entity/sensor/#available-state-classes"
|
||||
)
|
||||
NAMING_ENTITIES_URL = f"{INTEGRATION_URL}#naming-of-mqtt-entities"
|
||||
REGISTRY_PROPERTIES_URL = (
|
||||
f"{DEVELOPER_DOCUMENTATION_URL}docs/core/entity/#registry-properties"
|
||||
)
|
||||
|
||||
TRANSLATION_DESCRIPTION_PLACEHOLDERS = {
|
||||
"templating_url": TEMPLATING_URL,
|
||||
"available_state_classes_url": AVAILABLE_STATE_CLASSES_URL,
|
||||
"naming_entities_url": NAMING_ENTITIES_URL,
|
||||
"registry_properties_url": REGISTRY_PROPERTIES_URL,
|
||||
}
|
||||
|
||||
# Common selectors
|
||||
BOOLEAN_SELECTOR = BooleanSelector()
|
||||
TEMPLATE_SELECTOR = TemplateSelector(TemplateSelectorConfig())
|
||||
@@ -1167,7 +1143,6 @@ ENTITY_CONFIG_VALIDATOR: dict[
|
||||
Platform.NOTIFY.value: None,
|
||||
Platform.NUMBER.value: validate_number_platform_config,
|
||||
Platform.SELECT: None,
|
||||
Platform.SIREN: None,
|
||||
Platform.SENSOR.value: validate_sensor_platform_config,
|
||||
Platform.SWITCH.value: None,
|
||||
}
|
||||
@@ -1424,7 +1399,6 @@ PLATFORM_ENTITY_FIELDS: dict[str, dict[str, PlatformField]] = {
|
||||
default=None,
|
||||
),
|
||||
},
|
||||
Platform.SIREN: {},
|
||||
Platform.SWITCH.value: {
|
||||
CONF_DEVICE_CLASS: PlatformField(
|
||||
selector=SWITCH_DEVICE_CLASS_SELECTOR, required=False
|
||||
@@ -3187,71 +3161,6 @@ PLATFORM_MQTT_FIELDS: dict[str, dict[str, PlatformField]] = {
|
||||
section="advanced_settings",
|
||||
),
|
||||
},
|
||||
Platform.SIREN: {
|
||||
CONF_COMMAND_TOPIC: PlatformField(
|
||||
selector=TEXT_SELECTOR,
|
||||
required=True,
|
||||
validator=valid_publish_topic,
|
||||
error="invalid_publish_topic",
|
||||
),
|
||||
CONF_COMMAND_TEMPLATE: PlatformField(
|
||||
selector=TEMPLATE_SELECTOR,
|
||||
required=False,
|
||||
validator=validate(cv.template),
|
||||
error="invalid_template",
|
||||
),
|
||||
CONF_STATE_TOPIC: PlatformField(
|
||||
selector=TEXT_SELECTOR,
|
||||
required=False,
|
||||
validator=valid_subscribe_topic,
|
||||
error="invalid_subscribe_topic",
|
||||
),
|
||||
CONF_VALUE_TEMPLATE: PlatformField(
|
||||
selector=TEMPLATE_SELECTOR,
|
||||
required=False,
|
||||
validator=validate(cv.template),
|
||||
error="invalid_template",
|
||||
),
|
||||
CONF_PAYLOAD_OFF: PlatformField(
|
||||
selector=TEXT_SELECTOR,
|
||||
required=False,
|
||||
default=DEFAULT_PAYLOAD_OFF,
|
||||
),
|
||||
CONF_PAYLOAD_ON: PlatformField(
|
||||
selector=TEXT_SELECTOR,
|
||||
required=False,
|
||||
default=DEFAULT_PAYLOAD_ON,
|
||||
),
|
||||
CONF_STATE_OFF: PlatformField(
|
||||
selector=TEXT_SELECTOR,
|
||||
required=False,
|
||||
),
|
||||
CONF_STATE_ON: PlatformField(
|
||||
selector=TEXT_SELECTOR,
|
||||
required=False,
|
||||
),
|
||||
CONF_AVAILABLE_TONES: PlatformField(
|
||||
selector=OPTIONS_SELECTOR,
|
||||
required=False,
|
||||
),
|
||||
CONF_SUPPORT_DURATION: PlatformField(
|
||||
selector=BOOLEAN_SELECTOR,
|
||||
required=False,
|
||||
),
|
||||
CONF_SUPPORT_VOLUME_SET: PlatformField(
|
||||
selector=BOOLEAN_SELECTOR,
|
||||
required=False,
|
||||
),
|
||||
CONF_RETAIN: PlatformField(selector=BOOLEAN_SELECTOR, required=False),
|
||||
CONF_OPTIMISTIC: PlatformField(selector=BOOLEAN_SELECTOR, required=False),
|
||||
CONF_COMMAND_OFF_TEMPLATE: PlatformField(
|
||||
selector=TEMPLATE_SELECTOR,
|
||||
required=False,
|
||||
validator=validate(cv.template),
|
||||
error="invalid_template",
|
||||
section="siren_advanced_settings",
|
||||
),
|
||||
},
|
||||
Platform.SWITCH.value: {
|
||||
CONF_COMMAND_TOPIC: PlatformField(
|
||||
selector=TEXT_SELECTOR,
|
||||
@@ -4339,8 +4248,7 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
||||
return self.async_show_form(
|
||||
step_id="entity_platform_config",
|
||||
data_schema=data_schema,
|
||||
description_placeholders=TRANSLATION_DESCRIPTION_PLACEHOLDERS
|
||||
| {
|
||||
description_placeholders={
|
||||
"mqtt_device": device_name,
|
||||
CONF_PLATFORM: platform,
|
||||
"entity": full_entity_name,
|
||||
@@ -4393,8 +4301,7 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
||||
return self.async_show_form(
|
||||
step_id="mqtt_platform_config",
|
||||
data_schema=data_schema,
|
||||
description_placeholders=TRANSLATION_DESCRIPTION_PLACEHOLDERS
|
||||
| {
|
||||
description_placeholders={
|
||||
"mqtt_device": device_name,
|
||||
CONF_PLATFORM: platform,
|
||||
"entity": full_entity_name,
|
||||
@@ -4610,7 +4517,9 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
||||
step_id="export_yaml",
|
||||
last_step=False,
|
||||
data_schema=data_schema,
|
||||
description_placeholders={"url": INTEGRATION_URL},
|
||||
description_placeholders={
|
||||
"url": "https://www.home-assistant.io/integrations/mqtt/"
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_export_discovery(
|
||||
@@ -4662,7 +4571,9 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
||||
step_id="export_discovery",
|
||||
last_step=False,
|
||||
data_schema=data_schema,
|
||||
description_placeholders={"url": INTEGRATION_URL},
|
||||
description_placeholders={
|
||||
"url": "https://www.home-assistant.io/integrations/mqtt/"
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
@@ -30,7 +30,6 @@ CONF_AVAILABILITY = "availability"
|
||||
CONF_AVAILABILITY_MODE = "availability_mode"
|
||||
CONF_AVAILABILITY_TEMPLATE = "availability_template"
|
||||
CONF_AVAILABILITY_TOPIC = "availability_topic"
|
||||
CONF_AVAILABLE_TONES = "available_tones"
|
||||
CONF_BROKER = "broker"
|
||||
CONF_BIRTH_MESSAGE = "birth_message"
|
||||
CONF_CODE_ARM_REQUIRED = "code_arm_required"
|
||||
@@ -201,8 +200,6 @@ CONF_STATE_UNLOCKED = "state_unlocked"
|
||||
CONF_STATE_UNLOCKING = "state_unlocking"
|
||||
CONF_STEP = "step"
|
||||
CONF_SUGGESTED_DISPLAY_PRECISION = "suggested_display_precision"
|
||||
CONF_SUPPORT_DURATION = "support_duration"
|
||||
CONF_SUPPORT_VOLUME_SET = "support_volume_set"
|
||||
CONF_SUPPORTED_COLOR_MODES = "supported_color_modes"
|
||||
CONF_SWING_HORIZONTAL_MODE_COMMAND_TEMPLATE = "swing_horizontal_mode_command_template"
|
||||
CONF_SWING_HORIZONTAL_MODE_COMMAND_TOPIC = "swing_horizontal_mode_command_topic"
|
||||
@@ -305,7 +302,6 @@ DEFAULT_PAYLOAD_RESET = "None"
|
||||
DEFAULT_PAYLOAD_STOP = "STOP"
|
||||
DEFAULT_PAYLOAD_TRIGGER = "TRIGGER"
|
||||
DEFAULT_PAYLOAD_UNLOCK = "UNLOCK"
|
||||
DEFAULT_PORT = 1883
|
||||
DEFAULT_RETAIN = False
|
||||
DEFAULT_TILT_CLOSED_POSITION = 0
|
||||
DEFAULT_TILT_MAX = 100
|
||||
@@ -316,7 +312,6 @@ DEFAULT_WS_HEADERS: dict[str, str] = {}
|
||||
DEFAULT_WS_PATH = "/"
|
||||
DEFAULT_POSITION_CLOSED = 0
|
||||
DEFAULT_POSITION_OPEN = 100
|
||||
DEFAULT_RETAIN = False
|
||||
DEFAULT_SPEED_RANGE_MAX = 100
|
||||
DEFAULT_SPEED_RANGE_MIN = 1
|
||||
DEFAULT_STATE_LOCKED = "LOCKED"
|
||||
|
@@ -39,18 +39,10 @@ from homeassistant.util.json import JSON_DECODE_EXCEPTIONS, json_loads_object
|
||||
from . import subscription
|
||||
from .config import MQTT_RW_SCHEMA
|
||||
from .const import (
|
||||
CONF_AVAILABLE_TONES,
|
||||
CONF_COMMAND_OFF_TEMPLATE,
|
||||
CONF_COMMAND_TEMPLATE,
|
||||
CONF_COMMAND_TOPIC,
|
||||
CONF_STATE_OFF,
|
||||
CONF_STATE_ON,
|
||||
CONF_STATE_TOPIC,
|
||||
CONF_STATE_VALUE_TEMPLATE,
|
||||
CONF_SUPPORT_DURATION,
|
||||
CONF_SUPPORT_VOLUME_SET,
|
||||
DEFAULT_PAYLOAD_OFF,
|
||||
DEFAULT_PAYLOAD_ON,
|
||||
PAYLOAD_EMPTY_JSON,
|
||||
PAYLOAD_NONE,
|
||||
)
|
||||
@@ -66,9 +58,18 @@ from .schemas import MQTT_ENTITY_COMMON_SCHEMA
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
DEFAULT_NAME = "MQTT Siren"
|
||||
DEFAULT_PAYLOAD_ON = "ON"
|
||||
DEFAULT_PAYLOAD_OFF = "OFF"
|
||||
|
||||
ENTITY_ID_FORMAT = siren.DOMAIN + ".{}"
|
||||
|
||||
CONF_AVAILABLE_TONES = "available_tones"
|
||||
CONF_COMMAND_OFF_TEMPLATE = "command_off_template"
|
||||
CONF_STATE_ON = "state_on"
|
||||
CONF_STATE_OFF = "state_off"
|
||||
CONF_SUPPORT_DURATION = "support_duration"
|
||||
CONF_SUPPORT_VOLUME_SET = "support_volume_set"
|
||||
|
||||
STATE = "state"
|
||||
|
||||
PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend(
|
||||
|
@@ -153,7 +153,7 @@
|
||||
},
|
||||
"data_description": {
|
||||
"availability_topic": "Topic to receive the availability payload on",
|
||||
"availability_template": "A [template]({templating_url}#using-templates-with-the-mqtt-integration) to render the availability payload received on the availability topic",
|
||||
"availability_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-templates-with-the-mqtt-integration) to render the availability payload received on the availability topic",
|
||||
"payload_available": "The payload that indicates the device is available (defaults to 'online')",
|
||||
"payload_not_available": "The payload that indicates the device is not available (defaults to 'offline')"
|
||||
}
|
||||
@@ -221,7 +221,7 @@
|
||||
},
|
||||
"data_description": {
|
||||
"platform": "The type of the entity to configure.",
|
||||
"name": "The name of the entity. Leave empty to set it to `None` to [mark it as main feature of the MQTT device]({naming_entities_url).",
|
||||
"name": "The name of the entity. Leave empty to set it to `None` to [mark it as main feature of the MQTT device](https://www.home-assistant.io/integrations/mqtt/#naming-of-mqtt-entities).",
|
||||
"entity_picture": "An URL to a picture to be assigned."
|
||||
}
|
||||
},
|
||||
@@ -288,7 +288,7 @@
|
||||
"climate_feature_target_temperature": "The climate supports setting the target temperature.",
|
||||
"climate_feature_target_humidity": "The climate supports setting the target humidity.",
|
||||
"device_class": "The device class of the {platform} entity. [Learn more.]({url}#device_class)",
|
||||
"entity_category": "Allows marking an entity as device configuration or diagnostics. An entity with a category will not be exposed to cloud, Alexa, or Google Assistant components, nor included in indirect action calls to devices or areas. Sensor entities cannot be assigned a device configuration class. [Learn more.]({registry_properties_url)",
|
||||
"entity_category": "Allows marking an entity as device configuration or diagnostics. An entity with a category will not be exposed to cloud, Alexa, or Google Assistant components, nor included in indirect action calls to devices or areas. Sensor entities cannot be assigned a device configuration class. [Learn more.](https://developers.home-assistant.io/docs/core/entity/#registry-properties)",
|
||||
"fan_feature_speed": "The fan supports multiple speeds.",
|
||||
"fan_feature_preset_modes": "The fan supports preset modes.",
|
||||
"fan_feature_oscillation": "The fan supports oscillation.",
|
||||
@@ -296,7 +296,7 @@
|
||||
"image_processing_mode": "Select how the image data is received.",
|
||||
"options": "Options for allowed sensor state values. The sensor’s Device class must be set to Enumeration. The 'Options' setting cannot be used together with State class or Unit of measurement.",
|
||||
"schema": "The schema to use. [Learn more.]({url}#comparison-of-light-mqtt-schemas)",
|
||||
"state_class": "The [State class]({available_state_classes_url}) of the sensor. [Learn more.]({url}#state_class)",
|
||||
"state_class": "The [State class](https://developers.home-assistant.io/docs/core/entity/sensor/#available-state-classes) of the sensor. [Learn more.]({url}#state_class)",
|
||||
"suggested_display_precision": "The number of decimals which should be used in the {platform} entity state after rounding. [Learn more.]({url}#suggested_display_precision)",
|
||||
"supported_features": "The features that the entity supports.",
|
||||
"temperature_unit": "This determines the native unit of measurement the MQTT climate device works with.",
|
||||
@@ -318,7 +318,6 @@
|
||||
"title": "Configure MQTT device \"{mqtt_device}\"",
|
||||
"description": "Please configure MQTT specific details for {platform} entity \"{entity}\":",
|
||||
"data": {
|
||||
"available_tones": "Available Tones",
|
||||
"blue_template": "Blue template",
|
||||
"brightness_template": "Brightness template",
|
||||
"code": "Alarm code",
|
||||
@@ -361,41 +360,38 @@
|
||||
"state_topic": "State topic",
|
||||
"state_value_template": "State value template",
|
||||
"step": "Step",
|
||||
"support_duration": "Duration support",
|
||||
"support_volume_set": "Set volume support",
|
||||
"supported_color_modes": "Supported color modes",
|
||||
"url_template": "URL template",
|
||||
"url_topic": "URL topic",
|
||||
"value_template": "Value template"
|
||||
},
|
||||
"data_description": {
|
||||
"available_tones": "The siren supports tones. The `tone` variable will become available for use in the \"Command template\" setting. [Learn more.]({url}#available_tones)",
|
||||
"blue_template": "[Template]({templating_url}#using-value-templates-with-mqtt) to extract blue color from the state payload value. Expected result of the template is an integer from 0-255 range.",
|
||||
"brightness_template": "[Template]({templating_url}#using-value-templates-with-mqtt) to extract brightness from the state payload value. Expected result of the template is an integer from 0-255 range.",
|
||||
"blue_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract blue color from the state payload value. Expected result of the template is an integer from 0-255 range.",
|
||||
"brightness_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract brightness from the state payload value. Expected result of the template is an integer from 0-255 range.",
|
||||
"code": "Specifies a code to enable or disable the alarm in the frontend. Note that this blocks sending MQTT message commands to the remote device if the code validation fails. [Learn more.]({url}#code)",
|
||||
"code_format": "A regular expression to validate a supplied code when it is set during the action to open, lock or unlock the MQTT lock. [Learn more.]({url}#code_format)",
|
||||
"code_arm_required": "If set, the code is required to arm the alarm. If not set, the code is not validated.",
|
||||
"code_disarm_required": "If set, the code is required to disarm the alarm. If not set, the code is not validated.",
|
||||
"code_trigger_required": "If set, the code is required to manually trigger the alarm. If not set, the code is not validated.",
|
||||
"color_temp_template": "[Template]({templating_url}#using-value-templates-with-mqtt) to extract color temperature in Kelvin from the state payload value. Expected result of the template is an integer.",
|
||||
"command_off_template": "The [template]({templating_url}#using-command-templates-with-mqtt) for \"off\" state changes. Available variables are: `state` and `transition`.",
|
||||
"command_on_template": "The [template]({templating_url}#using-command-templates-with-mqtt) for \"on\" state changes. Available variables: `state`, `brightness`, `color_temp`, `red`, `green`, `blue`, `hue`, `sat`, `flash`, `transition` and `effect`. Values `red`, `green`, `blue` and `brightness` are provided as integers from range 0-255. Value of `hue` is provided as float from range 0-360. Value of `sat` is provided as float from range 0-100. Value of `color_temp` is provided as integer representing Kelvin units.",
|
||||
"command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to render the payload to be published at the command topic. [Learn more.]({url}#command_template)",
|
||||
"color_temp_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract color temperature in Kelvin from the state payload value. Expected result of the template is an integer.",
|
||||
"command_off_template": "The [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) for \"off\" state changes. Available variables are: `state` and `transition`.",
|
||||
"command_on_template": "The [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) for \"on\" state changes. Available variables: `state`, `brightness`, `color_temp`, `red`, `green`, `blue`, `hue`, `sat`, `flash`, `transition` and `effect`. Values `red`, `green`, `blue` and `brightness` are provided as integers from range 0-255. Value of `hue` is provided as float from range 0-360. Value of `sat` is provided as float from range 0-100. Value of `color_temp` is provided as integer representing Kelvin units.",
|
||||
"command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to render the payload to be published at the command topic. [Learn more.]({url}#command_template)",
|
||||
"command_topic": "The publishing topic that will be used to control the {platform} entity. [Learn more.]({url}#command_topic)",
|
||||
"content_type": "The content type or the image data that is received at the image topic.",
|
||||
"force_update": "Sends update events even if the value hasn’t changed. Useful if you want to have meaningful value graphs in history. [Learn more.]({url}#force_update)",
|
||||
"green_template": "[Template]({templating_url}#using-value-templates-with-mqtt) to extract green color from the state payload value. Expected result of the template is an integer from 0-255 range.",
|
||||
"green_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract green color from the state payload value. Expected result of the template is an integer from 0-255 range.",
|
||||
"image_encoding": "Select the encoding of the received image data",
|
||||
"image_topic": "The MQTT topic subscribed to receive messages containing the image data. [Learn more.]({url}#image_topic)",
|
||||
"last_reset_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the last reset. When Last reset template is set, the State class option must be Total. [Learn more.]({url}#last_reset_value_template)",
|
||||
"last_reset_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the last reset. When Last reset template is set, the State class option must be Total. [Learn more.]({url}#last_reset_value_template)",
|
||||
"max": "Maximum value. [Learn more.]({url}#max)",
|
||||
"min": "Minimum value. [Learn more.]({url}#min)",
|
||||
"mode": "Control how the number should be displayed in the UI. [Learn more.]({url}#mode)",
|
||||
"modes": "A list of supported operation modes. [Learn more.]({url}#modes)",
|
||||
"mode_command_topic": "The MQTT topic to publish commands to change the climate operation mode. [Learn more.]({url}#mode_command_topic)",
|
||||
"mode_command_template": "[Template]({templating_url}#using-command-templates-with-mqtt) to define the operation mode to be sent to the operation mode command topic. [Learn more.]({url}#mode_command_template)",
|
||||
"mode_command_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to define the operation mode to be sent to the operation mode command topic. [Learn more.]({url}#mode_command_template)",
|
||||
"mode_state_topic": "The MQTT topic subscribed to receive operation mode state messages. [Learn more.]({url}#mode_state_topic)",
|
||||
"mode_state_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the operation mode state. [Learn more.]({url}#mode_state_template)",
|
||||
"mode_state_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the operation mode state. [Learn more.]({url}#mode_state_template)",
|
||||
"on_command_type": "Defines when the payload \"on\" is sent. Using \"Last\" (the default) will send any style (brightness, color, etc) topics first and then a payload \"on\" to the command topic. Using \"First\" will send the payload \"on\" and then any style topics. Using \"Brightness\" will only send brightness commands instead of the payload \"on\" to turn the light on.",
|
||||
"optimistic": "Flag that defines if the {platform} entity works in optimistic mode. [Learn more.]({url}#optimistic)",
|
||||
"options": "List of options that can be selected.",
|
||||
@@ -404,19 +400,17 @@
|
||||
"payload_press": "The payload to send when the button is triggered.",
|
||||
"payload_reset": "The payload received at the state topic that resets the entity to an unknown state.",
|
||||
"qos": "The QoS value a {platform} entity should use.",
|
||||
"red_template": "[Template]({templating_url}#using-value-templates-with-mqtt) to extract red color from the state payload value. Expected result of the template is an integer from 0-255 range.",
|
||||
"red_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract red color from the state payload value. Expected result of the template is an integer from 0-255 range.",
|
||||
"retain": "Select if values published by the {platform} entity should be retained at the MQTT broker.",
|
||||
"state_off": "The incoming payload that represents the \"off\" state. Use only when the value that represents \"off\" state in the state topic is different from value that should be sent to the command topic to turn the device off.",
|
||||
"state_on": "The incoming payload that represents the \"on\" state. Use only when the value that represents \"on\" state in the state topic is different from value that should be sent to the command topic to turn the device on.",
|
||||
"state_template": "[Template]({templating_url}#using-value-templates-with-mqtt) to extract state from the state payload value.",
|
||||
"state_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract state from the state payload value.",
|
||||
"state_topic": "The MQTT topic subscribed to receive {platform} state values. [Learn more.]({url}#state_topic)",
|
||||
"step": "Step value. Smallest value 0.001.",
|
||||
"support_duration": "The siren supports setting a duration in second. The `duration` variable will become available for use in the \"Command template\" setting. [Learn more.]({url}#support_duration)",
|
||||
"support_volume_set": "The siren supports setting a volume. The `tone` variable will become available for use in the \"Command template\" setting. [Learn more.]({url}#support_volume_set)",
|
||||
"supported_color_modes": "A list of color modes supported by the light. Possible color modes are On/Off, Brightness, Color temperature, HS, XY, RGB, RGBW, RGBWW, White. Note that if On/Off or Brightness are used, that must be the only value in the list. [Learn more.]({url}#supported_color_modes)",
|
||||
"url_template": "[Template]({templating_url}#using-value-templates-with-mqtt) to extract an URL from the received URL topic payload value. [Learn more.]({url}#url_template)",
|
||||
"url_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract an URL from the received URL topic payload value. [Learn more.]({url}#url_template)",
|
||||
"url_topic": "The MQTT topic subscribed to receive messages containing the image URL. [Learn more.]({url}#url_topic)",
|
||||
"value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the {platform} entity value. [Learn more.]({url}#value_template)"
|
||||
"value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the {platform} entity value. [Learn more.]({url}#value_template)"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_settings": {
|
||||
@@ -470,7 +464,7 @@
|
||||
"action_topic": "Action topic"
|
||||
},
|
||||
"data_description": {
|
||||
"action_template": "A [template]({templating_url}#using-value-templates-with-mqtt) to render the value received on the action topic with.",
|
||||
"action_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to render the value received on the action topic with.",
|
||||
"action_topic": "The MQTT topic to subscribe for changes of the current action. If this is set, the climate graph uses the value received as data source. A \"None\" payload resets the current action state. An empty payload is ignored. Valid action values are: \"off\", \"heating\", \"cooling\", \"drying\", \"idle\" and \"fan\". [Learn more.]({url}#action_topic)"
|
||||
}
|
||||
},
|
||||
@@ -486,9 +480,9 @@
|
||||
"data_description": {
|
||||
"fan_modes": "List of fan modes this climate is capable of running at. Common fan modes that offer translations are `off`, `on`, `auto`, `low`, `medium`, `high`, `middle`, `focus` and `diffuse`.",
|
||||
"fan_mode_command_topic": "The MQTT topic to publish commands to change the climate fan mode. [Learn more.]({url}#fan_mode_command_topic)",
|
||||
"fan_mode_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the fan mode command topic.",
|
||||
"fan_mode_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the fan mode command topic.",
|
||||
"fan_mode_state_topic": "The MQTT topic subscribed to receive the climate fan mode. [Learn more.]({url}#fan_mode_state_topic)",
|
||||
"fan_mode_state_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the climate fan mode value."
|
||||
"fan_mode_state_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the climate fan mode value."
|
||||
}
|
||||
},
|
||||
"climate_power_settings": {
|
||||
@@ -502,7 +496,7 @@
|
||||
"data_description": {
|
||||
"payload_off": "[%key:component::mqtt::config_subentries::device::step::mqtt_platform_config::data_description::payload_off%]",
|
||||
"payload_on": "[%key:component::mqtt::config_subentries::device::step::mqtt_platform_config::data_description::payload_on%]",
|
||||
"power_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the power command topic. The `value` parameter is the payload set for payload \"on\" or payload \"off\".",
|
||||
"power_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the power command topic. The `value` parameter is the payload set for payload \"on\" or payload \"off\".",
|
||||
"power_command_topic": "The MQTT topic to publish commands to change the climate power state. Sends the payload configured with payload \"on\" or payload \"off\". [Learn more.]({url}#power_command_topic)"
|
||||
}
|
||||
},
|
||||
@@ -535,9 +529,9 @@
|
||||
"data_description": {
|
||||
"swing_horizontal_modes": "List of horizontal swing modes this climate is capable of running at. Common horizontal swing modes that offer translations are `off` and `on`.",
|
||||
"swing_horizontal_mode_command_topic": "The MQTT topic to publish commands to change the climate horizontal swing mode. [Learn more.]({url}#swing_horizontal_mode_command_topic)",
|
||||
"swing_horizontal_mode_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the horizontal swing mode command topic.",
|
||||
"swing_horizontal_mode_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the horizontal swing mode command topic.",
|
||||
"swing_horizontal_mode_state_topic": "The MQTT topic subscribed to receive the climate horizontal swing mode. [Learn more.]({url}#swing_horizontal_mode_state_topic)",
|
||||
"swing_horizontal_mode_state_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the climate horizontal swing mode value."
|
||||
"swing_horizontal_mode_state_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the climate horizontal swing mode value."
|
||||
}
|
||||
},
|
||||
"climate_swing_mode_settings": {
|
||||
@@ -552,9 +546,9 @@
|
||||
"data_description": {
|
||||
"swing_modes": "List of swing modes this climate is capable of running at. Common swing modes that offer translations are `off`, `on`, `vertical`, `horizontal` and `both`.",
|
||||
"swing_mode_command_topic": "The MQTT topic to publish commands to change the climate swing mode. [Learn more.]({url}#swing_mode_command_topic)",
|
||||
"swing_mode_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the swing mode command topic.",
|
||||
"swing_mode_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the swing mode command topic.",
|
||||
"swing_mode_state_topic": "The MQTT topic subscribed to receive the climate swing mode. [Learn more.]({url}#swing_mode_state_topic)",
|
||||
"swing_mode_state_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the climate swing mode value."
|
||||
"swing_mode_state_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the climate swing mode value."
|
||||
}
|
||||
},
|
||||
"cover_payload_settings": {
|
||||
@@ -595,9 +589,9 @@
|
||||
"data_description": {
|
||||
"position_closed": "Number which represents \"closed\" position.",
|
||||
"position_open": "Number which represents \"open\" position.",
|
||||
"position_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the payload for the position topic. Within the template the following variables are also available: `entity_id`, `position_open`, `position_closed`, `tilt_min` and `tilt_max`. [Learn more.]({url}#position_template)",
|
||||
"position_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the payload for the position topic. Within the template the following variables are also available: `entity_id`, `position_open`, `position_closed`, `tilt_min` and `tilt_max`. [Learn more.]({url}#position_template)",
|
||||
"position_topic": "The MQTT topic subscribed to receive cover position state messages. [Learn more.]({url}#position_topic)",
|
||||
"set_position_template": "[Template]({templating_url}#using-command-templates-with-mqtt) to define the position to be sent to the set position topic. Within the template the following variables are available: `value` (the scaled target position), `entity_id`, `position` (the target position percentage), `position_open`, `position_closed`, `tilt_min` and `tilt_max`. [Learn more.]({url}#set_position_template)",
|
||||
"set_position_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to define the position to be sent to the set position topic. Within the template the following variables are available: `value` (the scaled target position), `entity_id`, `position` (the target position percentage), `position_open`, `position_closed`, `tilt_min` and `tilt_max`. [Learn more.]({url}#set_position_template)",
|
||||
"set_position_topic": "The MQTT topic to publish position commands to. You need to use the set position topic as well if you want to use the position topic. Use template if position topic wants different values than within range \"position closed\" - \"position_open\". If template is not defined and position \"closed\" != 100 and position \"open\" != 0 then proper position value is calculated from percentage position. [Learn more.]({url}#set_position_topic)"
|
||||
}
|
||||
},
|
||||
@@ -616,12 +610,12 @@
|
||||
},
|
||||
"data_description": {
|
||||
"tilt_closed_value": "The value that will be sent to the \"tilt command topic\" when the cover tilt is closed.",
|
||||
"tilt_command_template": "[Template]({templating_url}#using-command-templates-with-mqtt) to define the position to be sent to the tilt command topic. Within the template the following variables are available: `entity_id`, `tilt_position` (the target tilt position percentage), `position_open`, `position_closed`, `tilt_min` and `tilt_max`. [Learn more.]({url}#tilt_command_template)",
|
||||
"tilt_command_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to define the position to be sent to the tilt command topic. Within the template the following variables are available: `entity_id`, `tilt_position` (the target tilt position percentage), `position_open`, `position_closed`, `tilt_min` and `tilt_max`. [Learn more.]({url}#tilt_command_template)",
|
||||
"tilt_command_topic": "The MQTT topic to publish commands to control the cover tilt. [Learn more.]({url}#tilt_command_topic)",
|
||||
"tilt_max": "The maximum tilt value.",
|
||||
"tilt_min": "The minimum tilt value.",
|
||||
"tilt_opened_value": "The value that will be sent to the \"tilt command topic\" when the cover tilt is opened.",
|
||||
"tilt_status_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the payload for the tilt status topic. Within the template the following variables are available: `entity_id`, `position_open`, `position_closed`, `tilt_min` and `tilt_max`. [Learn more.]({url}#tilt_status_template)",
|
||||
"tilt_status_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the payload for the tilt status topic. Within the template the following variables are available: `entity_id`, `position_open`, `position_closed`, `tilt_min` and `tilt_max`. [Learn more.]({url}#tilt_status_template)",
|
||||
"tilt_status_topic": "The MQTT topic subscribed to receive tilt status update values. [Learn more.]({url}#tilt_status_topic)",
|
||||
"tilt_optimistic": "Flag that defines if tilt works in optimistic mode. If tilt status topic is not defined, tilt works in optimistic mode by default. [Learn more.]({url}#tilt_optimistic)"
|
||||
}
|
||||
@@ -633,7 +627,7 @@
|
||||
"current_humidity_topic": "Current humidity topic"
|
||||
},
|
||||
"data_description": {
|
||||
"current_humidity_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the current humidity value. [Learn more.]({url}#current_humidity_template)",
|
||||
"current_humidity_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the current humidity value. [Learn more.]({url}#current_humidity_template)",
|
||||
"current_humidity_topic": "The MQTT topic subscribed to receive current humidity update values. [Learn more.]({url}#current_humidity_topic)"
|
||||
}
|
||||
},
|
||||
@@ -644,7 +638,7 @@
|
||||
"current_temperature_topic": "Current temperature topic"
|
||||
},
|
||||
"data_description": {
|
||||
"current_temperature_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the current temperature value. [Learn more.]({url}#current_temperature_template)",
|
||||
"current_temperature_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the current temperature value. [Learn more.]({url}#current_temperature_template)",
|
||||
"current_temperature_topic": "The MQTT topic subscribed to receive current temperature update values. [Learn more.]({url}#current_temperature_topic)"
|
||||
}
|
||||
},
|
||||
@@ -660,11 +654,11 @@
|
||||
},
|
||||
"data_description": {
|
||||
"brightness": "Flag that defines if light supports brightness when the RGB, RGBW, or RGBWW color mode is supported.",
|
||||
"brightness_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the brightness command topic.",
|
||||
"brightness_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the brightness command topic.",
|
||||
"brightness_command_topic": "The publishing topic that will be used to control the brightness. [Learn more.]({url}#brightness_command_topic)",
|
||||
"brightness_scale": "Defines the maximum brightness value (i.e., 100%) of the maximum brightness.",
|
||||
"brightness_state_topic": "The MQTT topic subscribed to receive brightness state values. [Learn more.]({url}#brightness_state_topic)",
|
||||
"brightness_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the brightness value."
|
||||
"brightness_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the brightness value."
|
||||
}
|
||||
},
|
||||
"lock_payload_settings": {
|
||||
@@ -702,9 +696,9 @@
|
||||
},
|
||||
"data_description": {
|
||||
"direction_command_topic": "The MQTT topic to publish commands to change the fan direction. The payload will be either `forward` or `reverse` and can be customized using the direction command template. [Learn more.]({url}#direction_command_topic)",
|
||||
"direction_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the direction command topic. The template variable `value` will be either `forward` or `reverse`.",
|
||||
"direction_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the direction command topic. The template variable `value` will be either `forward` or `reverse`.",
|
||||
"direction_state_topic": "The MQTT topic subscribed to receive fan direction state. Accepted state payloads are `forward` or `reverse`. [Learn more.]({url}#direction_state_topic)",
|
||||
"direction_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract fan direction state value. The template should return either `forward` or `reverse`. When the template returns an empty string, the direction will be ignored."
|
||||
"direction_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract fan direction state value. The template should return either `forward` or `reverse`. When the template returns an empty string, the direction will be ignored."
|
||||
}
|
||||
},
|
||||
"fan_oscillation_settings": {
|
||||
@@ -719,9 +713,9 @@
|
||||
},
|
||||
"data_description": {
|
||||
"oscillation_command_topic": "The MQTT topic to publish commands to change the fan oscillation state. [Learn more.]({url}#oscillation_command_topic)",
|
||||
"oscillation_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the oscillation command topic.",
|
||||
"oscillation_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the oscillation command topic.",
|
||||
"oscillation_state_topic": "The MQTT topic subscribed to receive fan oscillation state. [Learn more.]({url}#oscillation_state_topic)",
|
||||
"oscillation_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract fan oscillation state value.",
|
||||
"oscillation_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract fan oscillation state value.",
|
||||
"payload_oscillation_off": "The payload that represents the oscillation \"off\" state.",
|
||||
"payload_oscillation_on": "The payload that represents the oscillation \"on\" state."
|
||||
}
|
||||
@@ -740,9 +734,9 @@
|
||||
"payload_reset_preset_mode": "A special payload that resets the fan preset mode state attribute to unknown when received at the preset mode state topic.",
|
||||
"preset_modes": "List of preset modes this fan is capable of running at. Common examples include auto, smart, whoosh, eco and breeze.",
|
||||
"preset_mode_command_topic": "The MQTT topic to publish commands to change the fan preset mode. [Learn more.]({url}#preset_mode_command_topic)",
|
||||
"preset_mode_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the preset mode command topic.",
|
||||
"preset_mode_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the preset mode command topic.",
|
||||
"preset_mode_state_topic": "The MQTT topic subscribed to receive fan preset mode. [Learn more.]({url}#preset_mode_state_topic)",
|
||||
"preset_mode_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract fan preset mode value."
|
||||
"preset_mode_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract fan preset mode value."
|
||||
}
|
||||
},
|
||||
"fan_speed_settings": {
|
||||
@@ -759,9 +753,9 @@
|
||||
"data_description": {
|
||||
"payload_reset_percentage": "A special payload that resets the fan speed percentage state attribute to unknown when received at the percentage state topic.",
|
||||
"percentage_command_topic": "The MQTT topic to publish commands to change the fan speed state based on a percentage. [Learn more.]({url}#percentage_command_topic)",
|
||||
"percentage_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the percentage command topic.",
|
||||
"percentage_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the percentage command topic.",
|
||||
"percentage_state_topic": "The MQTT topic subscribed to receive fan speed based on percentage. [Learn more.]({url}#percentage_state_topic)",
|
||||
"percentage_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the speed percentage value.",
|
||||
"percentage_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the speed percentage value.",
|
||||
"speed_range_min": "The minimum of numeric output range (off not included, so speed_range_min - 1 represents 0 %). The percentage step is 100 / the number of speeds within the \"speed range\".",
|
||||
"speed_range_max": "The maximum of numeric output range (representing 100 %). The percentage step is 100 / number of speeds within the \"speed range\"."
|
||||
}
|
||||
@@ -774,7 +768,7 @@
|
||||
},
|
||||
"data_description": {
|
||||
"color_mode_state_topic": "The MQTT topic subscribed to receive color mode updates. If this is not configured, the color mode will be automatically set according to the last received valid color or color temperature.",
|
||||
"color_mode_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the color mode value."
|
||||
"color_mode_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the color mode value."
|
||||
}
|
||||
},
|
||||
"light_color_temp_settings": {
|
||||
@@ -786,10 +780,10 @@
|
||||
"color_temp_value_template": "Color temperature value template"
|
||||
},
|
||||
"data_description": {
|
||||
"color_temp_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the color temperature command topic.",
|
||||
"color_temp_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the color temperature command topic.",
|
||||
"color_temp_command_topic": "The publishing topic that will be used to control the color temperature. [Learn more.]({url}#color_temp_command_topic)",
|
||||
"color_temp_state_topic": "The MQTT topic subscribed to receive color temperature state updates. [Learn more.]({url}#color_temp_state_topic)",
|
||||
"color_temp_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the color temperature value."
|
||||
"color_temp_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the color temperature value."
|
||||
}
|
||||
},
|
||||
"light_effect_settings": {
|
||||
@@ -805,7 +799,7 @@
|
||||
},
|
||||
"data_description": {
|
||||
"effect": "Flag that defines if the light supports effects.",
|
||||
"effect_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the effect command topic.",
|
||||
"effect_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the effect command topic.",
|
||||
"effect_command_topic": "The publishing topic that will be used to control the light's effect state. [Learn more.]({url}#effect_command_topic)",
|
||||
"effect_list": "The list of effects the light supports.",
|
||||
"effect_state_topic": "The MQTT topic subscribed to receive effect state updates. [Learn more.]({url}#effect_state_topic)"
|
||||
@@ -820,10 +814,10 @@
|
||||
"hs_value_template": "HS value template"
|
||||
},
|
||||
"data_description": {
|
||||
"hs_command_template": "Defines a [template]({templating_url}#using-command-templates-with-mqtt) to compose message which will be sent to HS command topic. Available variables: `hue` and `sat`.",
|
||||
"hs_command_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose message which will be sent to HS command topic. Available variables: `hue` and `sat`.",
|
||||
"hs_command_topic": "The MQTT topic to publish commands to change the light’s color state in HS format (Hue Saturation). Range for Hue: 0° .. 360°, Range of Saturation: 0..100. Note: Brightness is sent separately in the brightness command topic. [Learn more.]({url}#hs_command_topic)",
|
||||
"hs_state_topic": "The MQTT topic subscribed to receive color state updates in HS format. The expected payload is the hue and saturation values separated by commas, for example, `359.5,100.0`. Note: Brightness is received separately in the brightness state topic. [Learn more.]({url}#hs_state_topic)",
|
||||
"hs_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the HS value."
|
||||
"hs_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the HS value."
|
||||
}
|
||||
},
|
||||
"light_rgb_settings": {
|
||||
@@ -835,10 +829,10 @@
|
||||
"rgb_value_template": "RGB value template"
|
||||
},
|
||||
"data_description": {
|
||||
"rgb_command_template": "Defines a [template]({templating_url}#using-command-templates-with-mqtt) to compose message which will be sent to RGB command topic. Available variables: `red`, `green` and `blue`.",
|
||||
"rgb_command_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose message which will be sent to RGB command topic. Available variables: `red`, `green` and `blue`.",
|
||||
"rgb_command_topic": "The MQTT topic to publish commands to change the light’s RGB state. [Learn more.]({url}#rgb_command_topic)",
|
||||
"rgb_state_topic": "The MQTT topic subscribed to receive RGB state updates. The expected payload is the RGB values separated by commas, for example, `255,0,127`. [Learn more.]({url}#rgb_state_topic)",
|
||||
"rgb_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the RGB value."
|
||||
"rgb_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the RGB value."
|
||||
}
|
||||
},
|
||||
"light_rgbw_settings": {
|
||||
@@ -850,10 +844,10 @@
|
||||
"rgbw_value_template": "RGBW value template"
|
||||
},
|
||||
"data_description": {
|
||||
"rgbw_command_template": "Defines a [template]({templating_url}#using-command-templates-with-mqtt) to compose message which will be sent to RGBW command topic. Available variables: `red`, `green`, `blue` and `white`.",
|
||||
"rgbw_command_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose message which will be sent to RGBW command topic. Available variables: `red`, `green`, `blue` and `white`.",
|
||||
"rgbw_command_topic": "The MQTT topic to publish commands to change the light’s RGBW state. [Learn more.]({url}#rgbw_command_topic)",
|
||||
"rgbw_state_topic": "The MQTT topic subscribed to receive RGBW state updates. The expected payload is the RGBW values separated by commas, for example, `255,0,127,64`. [Learn more.]({url}#rgbw_state_topic)",
|
||||
"rgbw_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the RGBW value."
|
||||
"rgbw_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the RGBW value."
|
||||
}
|
||||
},
|
||||
"light_rgbww_settings": {
|
||||
@@ -865,10 +859,10 @@
|
||||
"rgbww_value_template": "RGBWW value template"
|
||||
},
|
||||
"data_description": {
|
||||
"rgbww_command_template": "Defines a [template]({templating_url}#using-command-templates-with-mqtt) to compose message which will be sent to RGBWW command topic. Available variables: `red`, `green`, `blue`, `cold_white` and `warm_white`.",
|
||||
"rgbww_command_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose message which will be sent to RGBWW command topic. Available variables: `red`, `green`, `blue`, `cold_white` and `warm_white`.",
|
||||
"rgbww_command_topic": "The MQTT topic to publish commands to change the light’s RGBWW state. [Learn more.]({url}#rgbww_command_topic)",
|
||||
"rgbww_state_topic": "The MQTT topic subscribed to receive RGBWW state updates. The expected payload is the RGBWW values separated by commas, for example, `255,0,127,64,32`. [Learn more.]({url}#rgbww_state_topic)",
|
||||
"rgbww_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the RGBWW value."
|
||||
"rgbww_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the RGBWW value."
|
||||
}
|
||||
},
|
||||
"light_white_settings": {
|
||||
@@ -891,10 +885,10 @@
|
||||
"xy_value_template": "XY value template"
|
||||
},
|
||||
"data_description": {
|
||||
"xy_command_template": "Defines a [template]({templating_url}#using-command-templates-with-mqtt) to compose message which will be sent to XY command topic. Available variables: `x` and `y`.",
|
||||
"xy_command_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose message which will be sent to XY command topic. Available variables: `x` and `y`.",
|
||||
"xy_command_topic": "The MQTT topic to publish commands to change the light’s XY state. [Learn more.]({url}#xy_command_topic)",
|
||||
"xy_state_topic": "The MQTT topic subscribed to receive XY state updates. The expected payload is the X and Y color values separated by commas, for example, `0.675,0.322`. [Learn more.]({url}#xy_state_topic)",
|
||||
"xy_value_template": "Defines a [template]({templating_url}#using-value-templates-with-mqtt) to extract the XY value."
|
||||
"xy_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the XY value."
|
||||
}
|
||||
},
|
||||
"target_humidity_settings": {
|
||||
@@ -910,21 +904,12 @@
|
||||
"data_description": {
|
||||
"max_humidity": "The maximum target humidity that can be set.",
|
||||
"min_humidity": "The minimum target humidity that can be set.",
|
||||
"target_humidity_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the target humidity command topic.",
|
||||
"target_humidity_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the target humidity command topic.",
|
||||
"target_humidity_command_topic": "The MQTT topic to publish commands to change the climate target humidity. [Learn more.]({url}#humidity_command_topic)",
|
||||
"target_humidity_state_template": "A [template]({templating_url}#using-value-templates-with-mqtt) to render the value received on the target humidity state topic with.",
|
||||
"target_humidity_state_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to render the value received on the target humidity state topic with.",
|
||||
"target_humidity_state_topic": "The MQTT topic to subscribe for changes of the target humidity. [Learn more.]({url}#humidity_state_topic)"
|
||||
}
|
||||
},
|
||||
"siren_advanced_settings": {
|
||||
"name": "Advanced siren settings",
|
||||
"data": {
|
||||
"command_off_template": "Command \"off\" template"
|
||||
},
|
||||
"data_description": {
|
||||
"command_off_template": "The [template]({templating_url}#using-command-templates-with-mqtt) for \"off\" state changes. By default the \"[Command template]({url}#command_template)\" will be used. [Learn more.]({url}#command_off_template)"
|
||||
}
|
||||
},
|
||||
"target_temperature_settings": {
|
||||
"name": "Target temperature settings",
|
||||
"data": {
|
||||
@@ -952,17 +937,17 @@
|
||||
"min_temp": "The minimum target temperature that can be set.",
|
||||
"precision": "The precision in degrees the thermostat is working at.",
|
||||
"temp_step": "The target temperature step in degrees Celsius or Fahrenheit.",
|
||||
"temperature_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the temperature command topic.",
|
||||
"temperature_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the temperature command topic.",
|
||||
"temperature_command_topic": "The MQTT topic to publish commands to change the climate target temperature. [Learn more.]({url}#temperature_command_topic)",
|
||||
"temperature_high_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the upper temperature command topic.",
|
||||
"temperature_high_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the upper temperature command topic.",
|
||||
"temperature_high_command_topic": "The MQTT topic to publish commands to change the climate upper target temperature. [Learn more.]({url}#temperature_high_command_topic)",
|
||||
"temperature_low_command_template": "A [template]({templating_url}#using-command-templates-with-mqtt) to compose the payload to be published at the lower temperature command topic.",
|
||||
"temperature_low_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the lower temperature command topic.",
|
||||
"temperature_low_command_topic": "The MQTT topic to publish commands to change the climate lower target temperature. [Learn more.]({url}#temperature_low_command_topic)",
|
||||
"temperature_state_template": "A [template]({templating_url}#using-value-templates-with-mqtt) to render the value received on the temperature state topic with.",
|
||||
"temperature_state_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to render the value received on the temperature state topic with.",
|
||||
"temperature_state_topic": "The MQTT topic to subscribe for changes of the target temperature. [Learn more.]({url}#temperature_state_topic)",
|
||||
"temperature_high_state_template": "A [template]({templating_url}#using-value-templates-with-mqtt) to render the value received on the upper temperature state topic with.",
|
||||
"temperature_high_state_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to render the value received on the upper temperature state topic with.",
|
||||
"temperature_high_state_topic": "The MQTT topic to subscribe for changes of the upper target temperature. [Learn more.]({url}#temperature_high_state_topic)",
|
||||
"temperature_low_state_template": "A [template]({templating_url}#using-value-templates-with-mqtt) to render the value received on the lower temperature state topic with.",
|
||||
"temperature_low_state_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to render the value received on the lower temperature state topic with.",
|
||||
"temperature_low_state_topic": "The MQTT topic to subscribe for changes of the lower target temperature. [Learn more.]({url}#temperature_low_state_topic)"
|
||||
}
|
||||
}
|
||||
@@ -1353,7 +1338,6 @@
|
||||
"number": "[%key:component::number::title%]",
|
||||
"select": "[%key:component::select::title%]",
|
||||
"sensor": "[%key:component::sensor::title%]",
|
||||
"siren": "[%key:component::siren::title%]",
|
||||
"switch": "[%key:component::switch::title%]"
|
||||
}
|
||||
},
|
||||
|
@@ -73,6 +73,13 @@ STEP_MODBUS_DATA_SCHEMA = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
STEP_MODBUS_PLACEHOLDERS = {
|
||||
"tcp": "tcp://[HOST]:[PORT]",
|
||||
"serial": "serial://[LOCAL DEVICE]",
|
||||
"rfc2217": "rfc2217://[HOST]:[PORT]",
|
||||
}
|
||||
|
||||
|
||||
class FieldError(Exception):
|
||||
"""Field with invalid data."""
|
||||
|
||||
@@ -183,7 +190,9 @@ class NibeHeatPumpConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle the modbus step."""
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="modbus", data_schema=STEP_MODBUS_DATA_SCHEMA
|
||||
step_id="modbus",
|
||||
data_schema=STEP_MODBUS_DATA_SCHEMA,
|
||||
description_placeholders=STEP_MODBUS_PLACEHOLDERS,
|
||||
)
|
||||
|
||||
errors = {}
|
||||
@@ -200,7 +209,10 @@ class NibeHeatPumpConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return self.async_create_entry(title=title, data=data)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="modbus", data_schema=STEP_MODBUS_DATA_SCHEMA, errors=errors
|
||||
step_id="modbus",
|
||||
data_schema=STEP_MODBUS_DATA_SCHEMA,
|
||||
errors=errors,
|
||||
description_placeholders=STEP_MODBUS_PLACEHOLDERS,
|
||||
)
|
||||
|
||||
async def async_step_nibegw(
|
||||
|
@@ -15,7 +15,7 @@
|
||||
"modbus_unit": "Modbus unit identifier"
|
||||
},
|
||||
"data_description": {
|
||||
"modbus_url": "Modbus URL that describes the connection to your heat pump or MODBUS40 unit. It should be in the form:\n - `tcp://[HOST]:[PORT]` for Modbus TCP connection\n - `serial://[LOCAL DEVICE]` for a local Modbus RTU connection\n - `rfc2217://[HOST]:[PORT]` for a remote Telnet-based Modbus RTU connection.",
|
||||
"modbus_url": "Modbus URL that describes the connection to your heat pump or MODBUS40 unit. It should be in the form:\n - `{tcp}` for Modbus TCP connection\n - `{serial}` for a local Modbus RTU connection\n - `{rfc2217}` for a remote Telnet-based Modbus RTU connection.",
|
||||
"modbus_unit": "Unit identification for your heat pump. Can usually be left at 0."
|
||||
}
|
||||
},
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/niko_home_control",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["nikohomecontrol"],
|
||||
"requirements": ["nhc==0.6.1"]
|
||||
"requirements": ["nhc==0.7.0"]
|
||||
}
|
||||
|
@@ -16,7 +16,13 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
from .const import CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL, UNSUPPORTED_IMAGE_MODELS
|
||||
from .const import (
|
||||
CONF_CHAT_MODEL,
|
||||
CONF_IMAGE_MODEL,
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
RECOMMENDED_IMAGE_MODEL,
|
||||
UNSUPPORTED_IMAGE_MODELS,
|
||||
)
|
||||
from .entity import OpenAIBaseLLMEntity
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -142,7 +148,7 @@ class OpenAITaskEntity(
|
||||
mime_type=mime_type,
|
||||
width=int(width) if width else None,
|
||||
height=int(height) if height else None,
|
||||
model="gpt-image-1",
|
||||
model=self.subentry.data.get(CONF_IMAGE_MODEL, RECOMMENDED_IMAGE_MODEL),
|
||||
revised_prompt=image_call.revised_prompt
|
||||
if hasattr(image_call, "revised_prompt")
|
||||
else None,
|
||||
|
@@ -43,6 +43,7 @@ from homeassistant.helpers.typing import VolDictType
|
||||
from .const import (
|
||||
CONF_CHAT_MODEL,
|
||||
CONF_CODE_INTERPRETER,
|
||||
CONF_IMAGE_MODEL,
|
||||
CONF_MAX_TOKENS,
|
||||
CONF_PROMPT,
|
||||
CONF_REASONING_EFFORT,
|
||||
@@ -64,6 +65,7 @@ from .const import (
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
RECOMMENDED_CODE_INTERPRETER,
|
||||
RECOMMENDED_CONVERSATION_OPTIONS,
|
||||
RECOMMENDED_IMAGE_MODEL,
|
||||
RECOMMENDED_MAX_TOKENS,
|
||||
RECOMMENDED_REASONING_EFFORT,
|
||||
RECOMMENDED_TEMPERATURE,
|
||||
@@ -72,6 +74,7 @@ from .const import (
|
||||
RECOMMENDED_WEB_SEARCH,
|
||||
RECOMMENDED_WEB_SEARCH_CONTEXT_SIZE,
|
||||
RECOMMENDED_WEB_SEARCH_USER_LOCATION,
|
||||
UNSUPPORTED_IMAGE_MODELS,
|
||||
UNSUPPORTED_MODELS,
|
||||
UNSUPPORTED_WEB_SEARCH_MODELS,
|
||||
)
|
||||
@@ -411,6 +414,18 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
)
|
||||
}
|
||||
|
||||
if self._subentry_type == "ai_task_data" and not model.startswith(
|
||||
tuple(UNSUPPORTED_IMAGE_MODELS)
|
||||
):
|
||||
step_schema[
|
||||
vol.Optional(CONF_IMAGE_MODEL, default=RECOMMENDED_IMAGE_MODEL)
|
||||
] = SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=["gpt-image-1", "gpt-image-1-mini"],
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
)
|
||||
|
||||
if user_input is not None:
|
||||
if user_input.get(CONF_WEB_SEARCH):
|
||||
if user_input.get(CONF_WEB_SEARCH_USER_LOCATION):
|
||||
|
@@ -13,6 +13,7 @@ DEFAULT_AI_TASK_NAME = "OpenAI AI Task"
|
||||
DEFAULT_NAME = "OpenAI Conversation"
|
||||
|
||||
CONF_CHAT_MODEL = "chat_model"
|
||||
CONF_IMAGE_MODEL = "image_model"
|
||||
CONF_CODE_INTERPRETER = "code_interpreter"
|
||||
CONF_FILENAMES = "filenames"
|
||||
CONF_MAX_TOKENS = "max_tokens"
|
||||
@@ -31,6 +32,7 @@ CONF_WEB_SEARCH_COUNTRY = "country"
|
||||
CONF_WEB_SEARCH_TIMEZONE = "timezone"
|
||||
RECOMMENDED_CODE_INTERPRETER = False
|
||||
RECOMMENDED_CHAT_MODEL = "gpt-4o-mini"
|
||||
RECOMMENDED_IMAGE_MODEL = "gpt-image-1"
|
||||
RECOMMENDED_MAX_TOKENS = 3000
|
||||
RECOMMENDED_REASONING_EFFORT = "low"
|
||||
RECOMMENDED_TEMPERATURE = 1.0
|
||||
|
@@ -67,6 +67,7 @@ from homeassistant.util import slugify
|
||||
from .const import (
|
||||
CONF_CHAT_MODEL,
|
||||
CONF_CODE_INTERPRETER,
|
||||
CONF_IMAGE_MODEL,
|
||||
CONF_MAX_TOKENS,
|
||||
CONF_REASONING_EFFORT,
|
||||
CONF_TEMPERATURE,
|
||||
@@ -82,6 +83,7 @@ from .const import (
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
RECOMMENDED_IMAGE_MODEL,
|
||||
RECOMMENDED_MAX_TOKENS,
|
||||
RECOMMENDED_REASONING_EFFORT,
|
||||
RECOMMENDED_TEMPERATURE,
|
||||
@@ -516,13 +518,15 @@ class OpenAIBaseLLMEntity(Entity):
|
||||
model_args.setdefault("include", []).append("code_interpreter_call.outputs") # type: ignore[union-attr]
|
||||
|
||||
if force_image:
|
||||
tools.append(
|
||||
ImageGeneration(
|
||||
type="image_generation",
|
||||
input_fidelity="high",
|
||||
output_format="png",
|
||||
)
|
||||
image_model = options.get(CONF_IMAGE_MODEL, RECOMMENDED_IMAGE_MODEL)
|
||||
image_tool = ImageGeneration(
|
||||
type="image_generation",
|
||||
model=image_model,
|
||||
output_format="png",
|
||||
)
|
||||
if image_model == "gpt-image-1":
|
||||
image_tool["input_fidelity"] = "high"
|
||||
tools.append(image_tool)
|
||||
model_args["tool_choice"] = ToolChoiceTypesParam(type="image_generation")
|
||||
model_args["store"] = True # Avoid sending image data back and forth
|
||||
|
||||
|
@@ -50,6 +50,7 @@
|
||||
"data": {
|
||||
"code_interpreter": "Enable code interpreter tool",
|
||||
"reasoning_effort": "Reasoning effort",
|
||||
"image_model": "Image generation model",
|
||||
"web_search": "Enable web search",
|
||||
"search_context_size": "Search context size",
|
||||
"user_location": "Include home location"
|
||||
@@ -57,6 +58,7 @@
|
||||
"data_description": {
|
||||
"code_interpreter": "This tool, also known as the python tool to the model, allows it to run code to answer questions",
|
||||
"reasoning_effort": "How many reasoning tokens the model should generate before creating a response to the prompt",
|
||||
"image_model": "The model to use when generating images",
|
||||
"web_search": "Allow the model to search the web for the latest information before generating a response",
|
||||
"search_context_size": "High level guidance for the amount of context window space to use for the search",
|
||||
"user_location": "Refine search results based on geography"
|
||||
@@ -97,12 +99,14 @@
|
||||
"title": "[%key:component::openai_conversation::config_subentries::conversation::step::model::title%]",
|
||||
"data": {
|
||||
"reasoning_effort": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data::reasoning_effort%]",
|
||||
"image_model": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data::image_model%]",
|
||||
"web_search": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data::web_search%]",
|
||||
"search_context_size": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data::search_context_size%]",
|
||||
"user_location": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data::user_location%]"
|
||||
},
|
||||
"data_description": {
|
||||
"reasoning_effort": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data_description::reasoning_effort%]",
|
||||
"image_model": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data_description::image_model%]",
|
||||
"web_search": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data_description::web_search%]",
|
||||
"search_context_size": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data_description::search_context_size%]",
|
||||
"user_location": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data_description::user_location%]"
|
||||
|
@@ -210,7 +210,9 @@ class OverkizConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the local authentication step via config flow."""
|
||||
errors = {}
|
||||
description_placeholders = {}
|
||||
description_placeholders = {
|
||||
"somfy-developer-mode-docs": "https://github.com/Somfy-Developer/Somfy-TaHoma-Developer-Mode#getting-started"
|
||||
}
|
||||
|
||||
if user_input:
|
||||
self._host = user_input[CONF_HOST]
|
||||
|
@@ -13,7 +13,7 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"],
|
||||
"requirements": ["pyoverkiz==1.17.2"],
|
||||
"requirements": ["pyoverkiz==1.19.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_kizbox._tcp.local.",
|
||||
|
@@ -32,7 +32,7 @@
|
||||
}
|
||||
},
|
||||
"local": {
|
||||
"description": "By activating the [Developer Mode of your TaHoma box](https://github.com/Somfy-Developer/Somfy-TaHoma-Developer-Mode#getting-started), you can authorize third-party software (like Home Assistant) to connect to it via your local network.\n\n1. Open the TaHoma By Somfy application on your device.\n2. Navigate to the Help & advanced features -> Advanced features menu in the application.\n3. Activate Developer Mode by tapping 7 times on the version number of your gateway (like 2025.1.4-11).\n4. Generate a token from the Developer Mode menu to authenticate your API calls.\n\n5. Enter the generated token below and update the host to include your Gateway PIN or the IP address of your gateway.",
|
||||
"description": "By activating the [Developer Mode of your TaHoma box]({somfy-developer-mode-docs}), you can authorize third-party software (like Home Assistant) to connect to it via your local network.\n\n1. Open the TaHoma By Somfy application on your device.\n2. Navigate to the Help & advanced features -> Advanced features menu in the application.\n3. Activate Developer Mode by tapping 7 times on the version number of your gateway (like 2025.1.4-11).\n4. Generate a token from the Developer Mode menu to authenticate your API calls.\n\n5. Enter the generated token below and update the host to include your Gateway PIN or the IP address of your gateway.",
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"token": "[%key:common::config_flow::data::api_token%]",
|
||||
|
@@ -8,6 +8,6 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["plugwise"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["plugwise==1.7.8"],
|
||||
"requirements": ["plugwise==1.8.0"],
|
||||
"zeroconf": ["_plugwise._tcp.local."]
|
||||
}
|
||||
|
@@ -15,5 +15,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pyprobeplus==1.0.1"]
|
||||
"requirements": ["pyprobeplus==1.1.0"]
|
||||
}
|
||||
|
@@ -1,19 +1,19 @@
|
||||
"""Helper functions for Prowl."""
|
||||
|
||||
import asyncio
|
||||
from functools import partial
|
||||
|
||||
import prowlpy
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
|
||||
|
||||
async def async_verify_key(hass: HomeAssistant, api_key: str) -> bool:
|
||||
"""Validate API key."""
|
||||
prowl = await hass.async_add_executor_job(partial(prowlpy.Prowl, api_key))
|
||||
prowl = prowlpy.AsyncProwl(api_key, client=get_async_client(hass))
|
||||
try:
|
||||
async with asyncio.timeout(10):
|
||||
await hass.async_add_executor_job(prowl.verify_key)
|
||||
await prowl.verify_key()
|
||||
return True
|
||||
except prowlpy.APIError as ex:
|
||||
if str(ex).startswith("Invalid API key"):
|
||||
|
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["prowl"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["prowlpy==1.0.2"]
|
||||
"requirements": ["prowlpy==1.1.1"]
|
||||
}
|
||||
|
@@ -3,10 +3,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from functools import partial
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import prowlpy
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -24,6 +24,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -37,9 +38,7 @@ async def async_get_service(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> ProwlNotificationService:
|
||||
"""Get the Prowl notification service."""
|
||||
return await hass.async_add_executor_job(
|
||||
partial(ProwlNotificationService, hass, config[CONF_API_KEY])
|
||||
)
|
||||
return ProwlNotificationService(hass, config[CONF_API_KEY], get_async_client(hass))
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -48,7 +47,9 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the notify entities."""
|
||||
prowl = ProwlNotificationEntity(hass, entry.title, entry.data[CONF_API_KEY])
|
||||
prowl = ProwlNotificationEntity(
|
||||
hass, entry.title, entry.data[CONF_API_KEY], get_async_client(hass)
|
||||
)
|
||||
async_add_entities([prowl])
|
||||
|
||||
|
||||
@@ -58,10 +59,12 @@ class ProwlNotificationService(BaseNotificationService):
|
||||
This class is used for legacy configuration via configuration.yaml
|
||||
"""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, api_key: str) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, api_key: str, httpx_client: httpx.AsyncClient
|
||||
) -> None:
|
||||
"""Initialize the service."""
|
||||
self._hass = hass
|
||||
self._prowl = prowlpy.Prowl(api_key)
|
||||
self._prowl = prowlpy.AsyncProwl(api_key, client=httpx_client)
|
||||
|
||||
async def async_send_message(self, message: str, **kwargs: Any) -> None:
|
||||
"""Send the message to the user."""
|
||||
@@ -71,15 +74,12 @@ class ProwlNotificationService(BaseNotificationService):
|
||||
|
||||
try:
|
||||
async with asyncio.timeout(10):
|
||||
await self._hass.async_add_executor_job(
|
||||
partial(
|
||||
self._prowl.send,
|
||||
application="Home-Assistant",
|
||||
event=kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT),
|
||||
description=message,
|
||||
priority=data.get("priority", 0),
|
||||
url=data.get("url"),
|
||||
)
|
||||
await self._prowl.post(
|
||||
application="Home-Assistant",
|
||||
event=kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT),
|
||||
description=message,
|
||||
priority=data.get("priority", 0),
|
||||
url=data.get("url"),
|
||||
)
|
||||
except TimeoutError as ex:
|
||||
_LOGGER.error("Timeout accessing Prowl API")
|
||||
@@ -103,10 +103,16 @@ class ProwlNotificationEntity(NotifyEntity):
|
||||
This class is used for Prowl config entries.
|
||||
"""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, name: str, api_key: str) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
name: str,
|
||||
api_key: str,
|
||||
httpx_client: httpx.AsyncClient,
|
||||
) -> None:
|
||||
"""Initialize the service."""
|
||||
self._hass = hass
|
||||
self._prowl = prowlpy.Prowl(api_key)
|
||||
self._prowl = prowlpy.AsyncProwl(api_key, client=httpx_client)
|
||||
self._attr_name = name
|
||||
self._attr_unique_id = name
|
||||
|
||||
@@ -115,15 +121,12 @@ class ProwlNotificationEntity(NotifyEntity):
|
||||
_LOGGER.debug("Sending Prowl notification from entity %s", self.name)
|
||||
try:
|
||||
async with asyncio.timeout(10):
|
||||
await self._hass.async_add_executor_job(
|
||||
partial(
|
||||
self._prowl.send,
|
||||
application="Home-Assistant",
|
||||
event=title or ATTR_TITLE_DEFAULT,
|
||||
description=message,
|
||||
priority=0,
|
||||
url=None,
|
||||
)
|
||||
await self._prowl.post(
|
||||
application="Home-Assistant",
|
||||
event=title or ATTR_TITLE_DEFAULT,
|
||||
description=message,
|
||||
priority=0,
|
||||
url=None,
|
||||
)
|
||||
except TimeoutError as ex:
|
||||
_LOGGER.error("Timeout accessing Prowl API")
|
||||
|
@@ -14,7 +14,7 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.const import STATE_ON, EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
@@ -337,33 +337,20 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up sensors for device."""
|
||||
"""Set up binary sensor entities."""
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
if config_entry.data[CONF_SLEEP_PERIOD]:
|
||||
async_setup_entry_rpc(
|
||||
hass,
|
||||
config_entry,
|
||||
async_add_entities,
|
||||
RPC_SENSORS,
|
||||
RpcSleepingBinarySensor,
|
||||
)
|
||||
else:
|
||||
coordinator = config_entry.runtime_data.rpc
|
||||
assert coordinator
|
||||
return _async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
async_setup_entry_rpc(
|
||||
hass, config_entry, async_add_entities, RPC_SENSORS, RpcBinarySensor
|
||||
)
|
||||
return _async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
async_remove_orphaned_entities(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
coordinator.mac,
|
||||
BINARY_SENSOR_PLATFORM,
|
||||
coordinator.device.status,
|
||||
)
|
||||
return
|
||||
|
||||
@callback
|
||||
def _async_setup_block_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for BLOCK device."""
|
||||
if config_entry.data[CONF_SLEEP_PERIOD]:
|
||||
async_setup_entry_attribute_entities(
|
||||
hass,
|
||||
@@ -389,6 +376,38 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def _async_setup_rpc_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for RPC device."""
|
||||
if config_entry.data[CONF_SLEEP_PERIOD]:
|
||||
async_setup_entry_rpc(
|
||||
hass,
|
||||
config_entry,
|
||||
async_add_entities,
|
||||
RPC_SENSORS,
|
||||
RpcSleepingBinarySensor,
|
||||
)
|
||||
else:
|
||||
coordinator = config_entry.runtime_data.rpc
|
||||
assert coordinator
|
||||
|
||||
async_setup_entry_rpc(
|
||||
hass, config_entry, async_add_entities, RPC_SENSORS, RpcBinarySensor
|
||||
)
|
||||
|
||||
async_remove_orphaned_entities(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
coordinator.mac,
|
||||
BINARY_SENSOR_PLATFORM,
|
||||
coordinator.device.status,
|
||||
)
|
||||
|
||||
|
||||
class BlockBinarySensor(ShellyBlockAttributeEntity, BinarySensorEntity):
|
||||
"""Represent a block binary sensor entity."""
|
||||
|
||||
|
@@ -173,7 +173,7 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set buttons for device."""
|
||||
"""Set up button entities."""
|
||||
entry_data = config_entry.runtime_data
|
||||
coordinator: ShellyRpcCoordinator | ShellyBlockCoordinator | None
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
|
@@ -269,27 +269,36 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up climate device."""
|
||||
"""Set up climate entities."""
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
return
|
||||
return _async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
return _async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
|
||||
@callback
|
||||
def _async_setup_block_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for BLOCK device."""
|
||||
coordinator = config_entry.runtime_data.block
|
||||
assert coordinator
|
||||
if coordinator.device.initialized:
|
||||
async_setup_climate_entities(async_add_entities, coordinator)
|
||||
_async_setup_block_climate_entities(async_add_entities, coordinator)
|
||||
else:
|
||||
async_restore_climate_entities(
|
||||
_async_restore_block_climate_entities(
|
||||
hass, config_entry, async_add_entities, coordinator
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_climate_entities(
|
||||
def _async_setup_block_climate_entities(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
coordinator: ShellyBlockCoordinator,
|
||||
) -> None:
|
||||
"""Set up online climate devices."""
|
||||
"""Set up online BLOCK climate devices."""
|
||||
|
||||
device_block: Block | None = None
|
||||
sensor_block: Block | None = None
|
||||
@@ -310,13 +319,13 @@ def async_setup_climate_entities(
|
||||
|
||||
|
||||
@callback
|
||||
def async_restore_climate_entities(
|
||||
def _async_restore_block_climate_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
coordinator: ShellyBlockCoordinator,
|
||||
) -> None:
|
||||
"""Restore sleeping climate devices."""
|
||||
"""Restore sleeping BLOCK climate devices."""
|
||||
|
||||
ent_reg = er.async_get(hass)
|
||||
entries = er.async_entries_for_config_entry(ent_reg, config_entry.entry_id)
|
||||
@@ -332,7 +341,7 @@ def async_restore_climate_entities(
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_rpc_entry(
|
||||
def _async_setup_rpc_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
|
@@ -63,20 +63,20 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up covers for device."""
|
||||
"""Set up cover entities."""
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
return async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
return _async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
return async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
return _async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_block_entry(
|
||||
def _async_setup_block_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up cover for device."""
|
||||
"""Set up entities for BLOCK device."""
|
||||
coordinator = config_entry.runtime_data.block
|
||||
assert coordinator
|
||||
|
||||
@@ -86,7 +86,7 @@ def async_setup_block_entry(
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_rpc_entry(
|
||||
def _async_setup_rpc_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
|
@@ -84,71 +84,91 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up sensors for device."""
|
||||
entities: list[ShellyBlockEvent | ShellyRpcEvent] = []
|
||||
|
||||
coordinator: ShellyRpcCoordinator | ShellyBlockCoordinator | None = None
|
||||
|
||||
"""Set up event entities."""
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
coordinator = config_entry.runtime_data.rpc
|
||||
if TYPE_CHECKING:
|
||||
assert coordinator
|
||||
return _async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
key_instances = get_rpc_key_instances(coordinator.device.status, RPC_EVENT.key)
|
||||
return _async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
for key in key_instances:
|
||||
if RPC_EVENT.removal_condition and RPC_EVENT.removal_condition(
|
||||
coordinator.device.config, coordinator.device.status, key
|
||||
):
|
||||
unique_id = f"{coordinator.mac}-{key}"
|
||||
async_remove_shelly_entity(hass, EVENT_DOMAIN, unique_id)
|
||||
else:
|
||||
entities.append(ShellyRpcEvent(coordinator, key, RPC_EVENT))
|
||||
|
||||
script_instances = get_rpc_key_instances(
|
||||
coordinator.device.status, SCRIPT_EVENT.key
|
||||
)
|
||||
script_events = config_entry.runtime_data.rpc_script_events
|
||||
for script in script_instances:
|
||||
script_name = get_rpc_entity_name(coordinator.device, script)
|
||||
if script_name == BLE_SCRIPT_NAME:
|
||||
continue
|
||||
@callback
|
||||
def _async_setup_block_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for BLOCK device."""
|
||||
entities: list[ShellyBlockEvent] = []
|
||||
|
||||
script_id = int(script.split(":")[-1])
|
||||
if script_events and (event_types := script_events[script_id]):
|
||||
entities.append(ShellyRpcScriptEvent(coordinator, script, event_types))
|
||||
coordinator = config_entry.runtime_data.block
|
||||
if TYPE_CHECKING:
|
||||
assert coordinator and coordinator.device.blocks
|
||||
|
||||
# If a script is removed, from the device configuration, we need to remove orphaned entities
|
||||
async_remove_orphaned_entities(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
coordinator.mac,
|
||||
EVENT_DOMAIN,
|
||||
coordinator.device.status,
|
||||
"script",
|
||||
)
|
||||
for block in coordinator.device.blocks:
|
||||
if (
|
||||
"inputEvent" not in block.sensor_ids
|
||||
or "inputEventCnt" not in block.sensor_ids
|
||||
):
|
||||
continue
|
||||
|
||||
else:
|
||||
coordinator = config_entry.runtime_data.block
|
||||
if TYPE_CHECKING:
|
||||
assert coordinator
|
||||
assert coordinator.device.blocks
|
||||
if BLOCK_EVENT.removal_condition and BLOCK_EVENT.removal_condition(
|
||||
coordinator.device.settings, block
|
||||
):
|
||||
channel = int(block.channel or 0) + 1
|
||||
unique_id = f"{coordinator.mac}-{block.description}-{channel}"
|
||||
async_remove_shelly_entity(hass, EVENT_DOMAIN, unique_id)
|
||||
else:
|
||||
entities.append(ShellyBlockEvent(coordinator, block, BLOCK_EVENT))
|
||||
|
||||
for block in coordinator.device.blocks:
|
||||
if (
|
||||
"inputEvent" not in block.sensor_ids
|
||||
or "inputEventCnt" not in block.sensor_ids
|
||||
):
|
||||
continue
|
||||
async_add_entities(entities)
|
||||
|
||||
if BLOCK_EVENT.removal_condition and BLOCK_EVENT.removal_condition(
|
||||
coordinator.device.settings, block
|
||||
):
|
||||
channel = int(block.channel or 0) + 1
|
||||
unique_id = f"{coordinator.mac}-{block.description}-{channel}"
|
||||
async_remove_shelly_entity(hass, EVENT_DOMAIN, unique_id)
|
||||
else:
|
||||
entities.append(ShellyBlockEvent(coordinator, block, BLOCK_EVENT))
|
||||
|
||||
@callback
|
||||
def _async_setup_rpc_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for RPC device."""
|
||||
entities: list[ShellyRpcEvent] = []
|
||||
|
||||
coordinator = config_entry.runtime_data.rpc
|
||||
if TYPE_CHECKING:
|
||||
assert coordinator
|
||||
|
||||
key_instances = get_rpc_key_instances(coordinator.device.status, RPC_EVENT.key)
|
||||
|
||||
for key in key_instances:
|
||||
if RPC_EVENT.removal_condition and RPC_EVENT.removal_condition(
|
||||
coordinator.device.config, coordinator.device.status, key
|
||||
):
|
||||
unique_id = f"{coordinator.mac}-{key}"
|
||||
async_remove_shelly_entity(hass, EVENT_DOMAIN, unique_id)
|
||||
else:
|
||||
entities.append(ShellyRpcEvent(coordinator, key, RPC_EVENT))
|
||||
|
||||
script_instances = get_rpc_key_instances(
|
||||
coordinator.device.status, SCRIPT_EVENT.key
|
||||
)
|
||||
script_events = config_entry.runtime_data.rpc_script_events
|
||||
for script in script_instances:
|
||||
script_name = get_rpc_entity_name(coordinator.device, script)
|
||||
if script_name == BLE_SCRIPT_NAME:
|
||||
continue
|
||||
|
||||
script_id = int(script.split(":")[-1])
|
||||
if script_events and (event_types := script_events[script_id]):
|
||||
entities.append(ShellyRpcScriptEvent(coordinator, script, event_types))
|
||||
|
||||
# If a script is removed, from the device configuration, we need to remove orphaned entities
|
||||
async_remove_orphaned_entities(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
coordinator.mac,
|
||||
EVENT_DOMAIN,
|
||||
coordinator.device.status,
|
||||
"script",
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
@@ -82,20 +82,20 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up lights for device."""
|
||||
"""Set up light entities."""
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
return async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
return _async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
return async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
return _async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_block_entry(
|
||||
def _async_setup_block_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for block device."""
|
||||
"""Set up entities for BLOCK device."""
|
||||
coordinator = config_entry.runtime_data.block
|
||||
assert coordinator
|
||||
|
||||
@@ -538,7 +538,7 @@ LIGHTS: Final = {
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_rpc_entry(
|
||||
def _async_setup_rpc_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
|
@@ -9,7 +9,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aioshelly"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["aioshelly==13.12.0"],
|
||||
"requirements": ["aioshelly==13.13.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_http._tcp.local.",
|
||||
|
@@ -19,7 +19,7 @@ from homeassistant.components.number import (
|
||||
RestoreNumber,
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry
|
||||
@@ -331,30 +331,20 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up numbers for device."""
|
||||
"""Set up number entities."""
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
coordinator = config_entry.runtime_data.rpc
|
||||
assert coordinator
|
||||
return _async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
async_setup_entry_rpc(
|
||||
hass, config_entry, async_add_entities, RPC_NUMBERS, RpcNumber
|
||||
)
|
||||
return _async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
# the user can remove virtual components from the device configuration, so
|
||||
# we need to remove orphaned entities
|
||||
virtual_number_ids = get_virtual_component_ids(
|
||||
coordinator.device.config, NUMBER_PLATFORM
|
||||
)
|
||||
async_remove_orphaned_entities(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
coordinator.mac,
|
||||
NUMBER_PLATFORM,
|
||||
virtual_number_ids,
|
||||
"number",
|
||||
)
|
||||
return
|
||||
|
||||
@callback
|
||||
def _async_setup_block_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for BLOCK device."""
|
||||
if config_entry.data[CONF_SLEEP_PERIOD]:
|
||||
async_setup_entry_attribute_entities(
|
||||
hass,
|
||||
@@ -365,6 +355,35 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def _async_setup_rpc_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for RPC device."""
|
||||
coordinator = config_entry.runtime_data.rpc
|
||||
assert coordinator
|
||||
|
||||
async_setup_entry_rpc(
|
||||
hass, config_entry, async_add_entities, RPC_NUMBERS, RpcNumber
|
||||
)
|
||||
|
||||
# the user can remove virtual components from the device configuration, so
|
||||
# we need to remove orphaned entities
|
||||
virtual_number_ids = get_virtual_component_ids(
|
||||
coordinator.device.config, NUMBER_PLATFORM
|
||||
)
|
||||
async_remove_orphaned_entities(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
coordinator.mac,
|
||||
NUMBER_PLATFORM,
|
||||
virtual_number_ids,
|
||||
"number",
|
||||
)
|
||||
|
||||
|
||||
class BlockSleepingNumber(ShellySleepingBlockAttributeEntity, RestoreNumber):
|
||||
"""Represent a block sleeping number."""
|
||||
|
||||
|
@@ -12,7 +12,7 @@ from homeassistant.components.select import (
|
||||
SelectEntity,
|
||||
SelectEntityDescription,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import ShellyConfigEntry, ShellyRpcCoordinator
|
||||
@@ -54,28 +54,40 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up selectors for device."""
|
||||
"""Set up select entities."""
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
coordinator = config_entry.runtime_data.rpc
|
||||
assert coordinator
|
||||
return _async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
async_setup_entry_rpc(
|
||||
hass, config_entry, async_add_entities, RPC_SELECT_ENTITIES, RpcSelect
|
||||
)
|
||||
return None
|
||||
|
||||
# the user can remove virtual components from the device configuration, so
|
||||
# we need to remove orphaned entities
|
||||
virtual_text_ids = get_virtual_component_ids(
|
||||
coordinator.device.config, SELECT_PLATFORM
|
||||
)
|
||||
async_remove_orphaned_entities(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
coordinator.mac,
|
||||
SELECT_PLATFORM,
|
||||
virtual_text_ids,
|
||||
"enum",
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_setup_rpc_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for RPC device."""
|
||||
coordinator = config_entry.runtime_data.rpc
|
||||
assert coordinator
|
||||
|
||||
async_setup_entry_rpc(
|
||||
hass, config_entry, async_add_entities, RPC_SELECT_ENTITIES, RpcSelect
|
||||
)
|
||||
|
||||
# the user can remove virtual components from the device configuration, so
|
||||
# we need to remove orphaned entities
|
||||
virtual_text_ids = get_virtual_component_ids(
|
||||
coordinator.device.config, SELECT_PLATFORM
|
||||
)
|
||||
async_remove_orphaned_entities(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
coordinator.mac,
|
||||
SELECT_PLATFORM,
|
||||
virtual_text_ids,
|
||||
"enum",
|
||||
)
|
||||
|
||||
|
||||
class RpcSelect(ShellyRpcAttributeEntity, SelectEntity):
|
||||
|
@@ -36,7 +36,7 @@ from homeassistant.const import (
|
||||
UnitOfVolume,
|
||||
UnitOfVolumeFlowRate,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry
|
||||
from homeassistant.helpers.typing import StateType
|
||||
@@ -1710,33 +1710,20 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up sensors for device."""
|
||||
"""Set up sensor entities."""
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
if config_entry.data[CONF_SLEEP_PERIOD]:
|
||||
async_setup_entry_rpc(
|
||||
hass,
|
||||
config_entry,
|
||||
async_add_entities,
|
||||
RPC_SENSORS,
|
||||
RpcSleepingSensor,
|
||||
)
|
||||
else:
|
||||
coordinator = config_entry.runtime_data.rpc
|
||||
assert coordinator
|
||||
return _async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
async_setup_entry_rpc(
|
||||
hass, config_entry, async_add_entities, RPC_SENSORS, RpcSensor
|
||||
)
|
||||
return _async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
async_remove_orphaned_entities(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
coordinator.mac,
|
||||
SENSOR_PLATFORM,
|
||||
coordinator.device.status,
|
||||
)
|
||||
return
|
||||
|
||||
@callback
|
||||
def _async_setup_block_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for BLOCK device."""
|
||||
if config_entry.data[CONF_SLEEP_PERIOD]:
|
||||
async_setup_entry_attribute_entities(
|
||||
hass,
|
||||
@@ -1758,6 +1745,38 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def _async_setup_rpc_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for RPC device."""
|
||||
if config_entry.data[CONF_SLEEP_PERIOD]:
|
||||
async_setup_entry_rpc(
|
||||
hass,
|
||||
config_entry,
|
||||
async_add_entities,
|
||||
RPC_SENSORS,
|
||||
RpcSleepingSensor,
|
||||
)
|
||||
else:
|
||||
coordinator = config_entry.runtime_data.rpc
|
||||
assert coordinator
|
||||
|
||||
async_setup_entry_rpc(
|
||||
hass, config_entry, async_add_entities, RPC_SENSORS, RpcSensor
|
||||
)
|
||||
|
||||
async_remove_orphaned_entities(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
coordinator.mac,
|
||||
SENSOR_PLATFORM,
|
||||
coordinator.device.status,
|
||||
)
|
||||
|
||||
|
||||
class BlockSensor(ShellyBlockAttributeEntity, SensorEntity):
|
||||
"""Represent a block sensor."""
|
||||
|
||||
|
@@ -264,20 +264,20 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up switches for device."""
|
||||
"""Set up switch entities."""
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
return async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
return _async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
return async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
return _async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_block_entry(
|
||||
def _async_setup_block_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for block device."""
|
||||
"""Set up entities for BLOCK device."""
|
||||
coordinator = config_entry.runtime_data.block
|
||||
assert coordinator
|
||||
|
||||
@@ -295,7 +295,7 @@ def async_setup_block_entry(
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_rpc_entry(
|
||||
def _async_setup_rpc_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
|
@@ -12,7 +12,7 @@ from homeassistant.components.text import (
|
||||
TextEntity,
|
||||
TextEntityDescription,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import ShellyConfigEntry
|
||||
@@ -54,28 +54,40 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up sensors for device."""
|
||||
"""Set up text entities."""
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
coordinator = config_entry.runtime_data.rpc
|
||||
assert coordinator
|
||||
return _async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
async_setup_entry_rpc(
|
||||
hass, config_entry, async_add_entities, RPC_TEXT_ENTITIES, RpcText
|
||||
)
|
||||
return None
|
||||
|
||||
# the user can remove virtual components from the device configuration, so
|
||||
# we need to remove orphaned entities
|
||||
virtual_text_ids = get_virtual_component_ids(
|
||||
coordinator.device.config, TEXT_PLATFORM
|
||||
)
|
||||
async_remove_orphaned_entities(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
coordinator.mac,
|
||||
TEXT_PLATFORM,
|
||||
virtual_text_ids,
|
||||
"text",
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_setup_rpc_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for RPC device."""
|
||||
coordinator = config_entry.runtime_data.rpc
|
||||
assert coordinator
|
||||
|
||||
async_setup_entry_rpc(
|
||||
hass, config_entry, async_add_entities, RPC_TEXT_ENTITIES, RpcText
|
||||
)
|
||||
|
||||
# the user can remove virtual components from the device configuration, so
|
||||
# we need to remove orphaned entities
|
||||
virtual_text_ids = get_virtual_component_ids(
|
||||
coordinator.device.config, TEXT_PLATFORM
|
||||
)
|
||||
async_remove_orphaned_entities(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
coordinator.mac,
|
||||
TEXT_PLATFORM,
|
||||
virtual_text_ids,
|
||||
"text",
|
||||
)
|
||||
|
||||
|
||||
class RpcText(ShellyRpcAttributeEntity, TextEntity):
|
||||
|
@@ -115,22 +115,20 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up update entities for Shelly component."""
|
||||
"""Set up update entities."""
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
if config_entry.data[CONF_SLEEP_PERIOD]:
|
||||
async_setup_entry_rpc(
|
||||
hass,
|
||||
config_entry,
|
||||
async_add_entities,
|
||||
RPC_UPDATES,
|
||||
RpcSleepingUpdateEntity,
|
||||
)
|
||||
else:
|
||||
async_setup_entry_rpc(
|
||||
hass, config_entry, async_add_entities, RPC_UPDATES, RpcUpdateEntity
|
||||
)
|
||||
return
|
||||
return _async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
return _async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
|
||||
@callback
|
||||
def _async_setup_block_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for BLOCK device."""
|
||||
if not config_entry.data[CONF_SLEEP_PERIOD]:
|
||||
async_setup_entry_rest(
|
||||
hass,
|
||||
@@ -141,6 +139,27 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def _async_setup_rpc_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for RPC device."""
|
||||
if config_entry.data[CONF_SLEEP_PERIOD]:
|
||||
async_setup_entry_rpc(
|
||||
hass,
|
||||
config_entry,
|
||||
async_add_entities,
|
||||
RPC_UPDATES,
|
||||
RpcSleepingUpdateEntity,
|
||||
)
|
||||
else:
|
||||
async_setup_entry_rpc(
|
||||
hass, config_entry, async_add_entities, RPC_UPDATES, RpcUpdateEntity
|
||||
)
|
||||
|
||||
|
||||
class RestUpdateEntity(ShellyRestAttributeEntity, UpdateEntity):
|
||||
"""Represent a REST update entity."""
|
||||
|
||||
|
@@ -6,7 +6,7 @@ from dataclasses import dataclass
|
||||
from typing import Any, cast
|
||||
|
||||
from aioshelly.block_device import Block
|
||||
from aioshelly.const import BLOCK_GENERATIONS, MODEL_GAS
|
||||
from aioshelly.const import MODEL_GAS, RPC_GENERATIONS
|
||||
|
||||
from homeassistant.components.valve import (
|
||||
ValveDeviceClass,
|
||||
@@ -135,15 +135,34 @@ async def async_setup_entry(
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up valves for device."""
|
||||
if get_device_entry_gen(config_entry) in BLOCK_GENERATIONS:
|
||||
return async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
"""Set up valve entities."""
|
||||
if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
|
||||
return _async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
return async_setup_rpc_entry(hass, config_entry, async_add_entities)
|
||||
return _async_setup_block_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_rpc_entry(
|
||||
def _async_setup_block_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities for BLOCK device."""
|
||||
coordinator = config_entry.runtime_data.block
|
||||
assert coordinator
|
||||
|
||||
async_setup_block_attribute_entities(
|
||||
hass,
|
||||
async_add_entities,
|
||||
coordinator,
|
||||
BLOCK_VALVES,
|
||||
BlockShellyValve,
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def _async_setup_rpc_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
@@ -157,25 +176,6 @@ def async_setup_rpc_entry(
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_block_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ShellyConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up valve for device."""
|
||||
coordinator = config_entry.runtime_data.block
|
||||
assert coordinator
|
||||
|
||||
async_setup_block_attribute_entities(
|
||||
hass,
|
||||
async_add_entities,
|
||||
coordinator,
|
||||
BLOCK_VALVES,
|
||||
BlockShellyValve,
|
||||
)
|
||||
|
||||
|
||||
class BlockShellyValve(ShellyBlockAttributeEntity, ValveEntity):
|
||||
"""Entity that controls a valve on block based Shelly devices."""
|
||||
|
||||
|
@@ -76,6 +76,7 @@
|
||||
"name": "State",
|
||||
"state": {
|
||||
"baseline": "Baseline",
|
||||
"weaning_baseline": "Baseline (Weaning)",
|
||||
"level1": "Level 1",
|
||||
"level2": "Level 2",
|
||||
"level3": "Level 3",
|
||||
|
@@ -12,5 +12,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/tilt_ble",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["tilt-ble==0.3.1"]
|
||||
"requirements": ["tilt-ble==1.0.1"]
|
||||
}
|
||||
|
@@ -42,6 +42,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Optional(CONF_API_KEY, default=""): str})
|
||||
PLACEHOLDER = {"example_url": "https://uptime.example.com:3001"}
|
||||
|
||||
|
||||
async def validate_connection(
|
||||
@@ -100,6 +101,7 @@ class UptimeKumaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
data_schema=STEP_USER_DATA_SCHEMA, suggested_values=user_input
|
||||
),
|
||||
errors=errors,
|
||||
description_placeholders=PLACEHOLDER,
|
||||
)
|
||||
|
||||
async def async_step_reauth(
|
||||
@@ -170,6 +172,7 @@ class UptimeKumaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
suggested_values=user_input or entry.data,
|
||||
),
|
||||
errors=errors,
|
||||
description_placeholders=PLACEHOLDER,
|
||||
)
|
||||
|
||||
async def async_step_hassio(
|
||||
|
@@ -9,7 +9,7 @@
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]"
|
||||
},
|
||||
"data_description": {
|
||||
"url": "Enter the full URL of your Uptime Kuma instance. Be sure to include the protocol (`http` or `https`), the hostname or IP address, the port number (if it is a non-default port), and any path prefix if applicable. Example: `https://uptime.example.com`",
|
||||
"url": "Enter the full URL of your Uptime Kuma instance. Be sure to include the protocol (`http` or `https`), the hostname or IP address, the port number (if it is a non-default port), and any path prefix if applicable. Example: `{example_url}`",
|
||||
"verify_ssl": "Enable SSL certificate verification for secure connections. Disable only if connecting to an Uptime Kuma instance using a self-signed certificate or via IP address",
|
||||
"api_key": "Enter an API key. To create a new API key navigate to **Settings → API Keys** and select **Add API Key**"
|
||||
}
|
||||
|
@@ -70,17 +70,13 @@ class WyomingConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
uri = urlparse(discovery_info.config["uri"])
|
||||
for entry in self._async_current_entries(include_ignore=True):
|
||||
if (
|
||||
entry.data[CONF_HOST] == uri.hostname
|
||||
and entry.data[CONF_PORT] == uri.port
|
||||
):
|
||||
return self.async_update_reload_and_abort(
|
||||
entry,
|
||||
unique_id=discovery_info.uuid,
|
||||
reload_even_if_entry_is_unchanged=False,
|
||||
reason="already_configured",
|
||||
)
|
||||
for entry in self._iter_entries(uri.hostname, uri.port):
|
||||
return self.async_update_reload_and_abort(
|
||||
entry,
|
||||
unique_id=discovery_info.uuid,
|
||||
reload_even_if_entry_is_unchanged=False,
|
||||
reason="already_configured",
|
||||
)
|
||||
|
||||
self._hassio_discovery = discovery_info
|
||||
self.context.update(
|
||||
@@ -139,12 +135,8 @@ class WyomingConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
self.context["title_placeholders"] = {"name": self._name}
|
||||
|
||||
for entry in self._async_current_entries(include_ignore=True):
|
||||
if (
|
||||
entry.data[CONF_HOST] == service.host
|
||||
and entry.data[CONF_PORT] == service.port
|
||||
and entry.source != SOURCE_HASSIO
|
||||
):
|
||||
for entry in self._iter_entries(service.host, service.port):
|
||||
if entry.source != SOURCE_HASSIO:
|
||||
return self.async_update_reload_and_abort(
|
||||
entry,
|
||||
unique_id=unique_id,
|
||||
@@ -176,3 +168,9 @@ class WyomingConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_PORT: self._service.port,
|
||||
},
|
||||
)
|
||||
|
||||
def _iter_entries(self, host: str, port: int):
|
||||
"""Yield entries with matching host/port."""
|
||||
for entry in self._async_current_entries(include_ignore=True):
|
||||
if entry.data.get(CONF_HOST) == host and entry.data.get(CONF_PORT) == port:
|
||||
yield entry
|
||||
|
@@ -27,13 +27,16 @@ type YaleConfigEntry = ConfigEntry[YaleData]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: YaleConfigEntry) -> bool:
|
||||
"""Set up yale from a config entry."""
|
||||
"""Set up Yale from a config entry."""
|
||||
session = async_create_yale_clientsession(hass)
|
||||
implementation = (
|
||||
await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||
hass, entry
|
||||
try:
|
||||
implementation = (
|
||||
await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||
hass, entry
|
||||
)
|
||||
)
|
||||
)
|
||||
except ValueError as err:
|
||||
raise ConfigEntryNotReady("OAuth implementation not available") from err
|
||||
oauth_session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)
|
||||
yale_gateway = YaleGateway(Path(hass.config.config_dir), session, oauth_session)
|
||||
try:
|
||||
|
@@ -161,6 +161,7 @@ class-const-naming-style = "any"
|
||||
# possibly-used-before-assignment - too many errors / not necessarily issues
|
||||
# ---
|
||||
# Pylint CodeStyle plugin
|
||||
# consider-math-not-float
|
||||
# consider-using-namedtuple-or-dataclass - too opinionated
|
||||
# consider-using-assignment-expr - decision to use := better left to devs
|
||||
disable = [
|
||||
@@ -181,6 +182,7 @@ disable = [
|
||||
"too-many-boolean-expressions",
|
||||
"too-many-positional-arguments",
|
||||
"wrong-import-order",
|
||||
"consider-math-not-float",
|
||||
"consider-using-namedtuple-or-dataclass",
|
||||
"consider-using-assignment-expr",
|
||||
"possibly-used-before-assignment",
|
||||
|
24
requirements_all.txt
generated
24
requirements_all.txt
generated
@@ -185,7 +185,7 @@ aioairzone-cloud==0.7.2
|
||||
aioairzone==1.0.1
|
||||
|
||||
# homeassistant.components.alexa_devices
|
||||
aioamazondevices==6.4.0
|
||||
aioamazondevices==6.4.3
|
||||
|
||||
# homeassistant.components.ambient_network
|
||||
# homeassistant.components.ambient_station
|
||||
@@ -247,7 +247,7 @@ aioelectricitymaps==1.1.1
|
||||
aioemonitor==1.0.5
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==41.13.0
|
||||
aioesphomeapi==41.14.0
|
||||
|
||||
# homeassistant.components.flo
|
||||
aioflo==2021.11.0
|
||||
@@ -384,7 +384,7 @@ aioruuvigateway==0.1.0
|
||||
aiosenz==1.0.0
|
||||
|
||||
# homeassistant.components.shelly
|
||||
aioshelly==13.12.0
|
||||
aioshelly==13.13.0
|
||||
|
||||
# homeassistant.components.skybell
|
||||
aioskybell==22.7.0
|
||||
@@ -895,7 +895,7 @@ enocean==0.50
|
||||
enturclient==0.2.4
|
||||
|
||||
# homeassistant.components.environment_canada
|
||||
env-canada==0.11.3
|
||||
env-canada==0.12.1
|
||||
|
||||
# homeassistant.components.season
|
||||
ephem==4.1.6
|
||||
@@ -1545,7 +1545,7 @@ nextcord==3.1.0
|
||||
nextdns==4.1.0
|
||||
|
||||
# homeassistant.components.niko_home_control
|
||||
nhc==0.6.1
|
||||
nhc==0.7.0
|
||||
|
||||
# homeassistant.components.nibe_heatpump
|
||||
nibe==2.19.0
|
||||
@@ -1720,7 +1720,7 @@ plexauth==0.0.6
|
||||
plexwebsocket==0.0.14
|
||||
|
||||
# homeassistant.components.plugwise
|
||||
plugwise==1.7.8
|
||||
plugwise==1.8.0
|
||||
|
||||
# homeassistant.components.serial_pm
|
||||
pmsensor==0.4
|
||||
@@ -1744,7 +1744,7 @@ proliphix==0.4.1
|
||||
prometheus-client==0.21.0
|
||||
|
||||
# homeassistant.components.prowl
|
||||
prowlpy==1.0.2
|
||||
prowlpy==1.1.1
|
||||
|
||||
# homeassistant.components.proxmoxve
|
||||
proxmoxer==2.0.1
|
||||
@@ -1815,7 +1815,7 @@ pyAtome==0.1.1
|
||||
pyCEC==0.5.2
|
||||
|
||||
# homeassistant.components.control4
|
||||
pyControl4==1.2.0
|
||||
pyControl4==1.5.0
|
||||
|
||||
# homeassistant.components.duotecno
|
||||
pyDuotecno==2024.10.1
|
||||
@@ -2054,7 +2054,7 @@ pygti==0.9.4
|
||||
pyhaversion==22.8.0
|
||||
|
||||
# homeassistant.components.heos
|
||||
pyheos==1.0.5
|
||||
pyheos==1.0.6
|
||||
|
||||
# homeassistant.components.hive
|
||||
pyhive-integration==1.0.2
|
||||
@@ -2263,7 +2263,7 @@ pyotgw==2.2.2
|
||||
pyotp==2.9.0
|
||||
|
||||
# homeassistant.components.overkiz
|
||||
pyoverkiz==1.17.2
|
||||
pyoverkiz==1.19.0
|
||||
|
||||
# homeassistant.components.onewire
|
||||
pyownet==0.10.0.post1
|
||||
@@ -2296,7 +2296,7 @@ pypoint==3.0.0
|
||||
pyportainer==1.0.3
|
||||
|
||||
# homeassistant.components.probe_plus
|
||||
pyprobeplus==1.0.1
|
||||
pyprobeplus==1.1.0
|
||||
|
||||
# homeassistant.components.profiler
|
||||
pyprof2calltree==1.4.5
|
||||
@@ -2991,7 +2991,7 @@ thinqconnect==1.0.8
|
||||
tikteck==0.4
|
||||
|
||||
# homeassistant.components.tilt_ble
|
||||
tilt-ble==0.3.1
|
||||
tilt-ble==1.0.1
|
||||
|
||||
# homeassistant.components.tilt_pi
|
||||
tilt-pi==0.2.1
|
||||
|
@@ -7,7 +7,7 @@
|
||||
|
||||
-c homeassistant/package_constraints.txt
|
||||
-r requirements_test_pre_commit.txt
|
||||
astroid==3.3.11
|
||||
astroid==4.0.1
|
||||
coverage==7.10.6
|
||||
freezegun==1.5.2
|
||||
go2rtc-client==0.2.1
|
||||
@@ -18,7 +18,7 @@ mock-open==1.4.0
|
||||
mypy-dev==1.19.0a4
|
||||
pre-commit==4.2.0
|
||||
pydantic==2.12.0
|
||||
pylint==3.3.9
|
||||
pylint==4.0.0
|
||||
pylint-per-file-ignores==1.4.0
|
||||
pipdeptree==2.26.1
|
||||
pytest-asyncio==1.2.0
|
||||
|
24
requirements_test_all.txt
generated
24
requirements_test_all.txt
generated
@@ -173,7 +173,7 @@ aioairzone-cloud==0.7.2
|
||||
aioairzone==1.0.1
|
||||
|
||||
# homeassistant.components.alexa_devices
|
||||
aioamazondevices==6.4.0
|
||||
aioamazondevices==6.4.3
|
||||
|
||||
# homeassistant.components.ambient_network
|
||||
# homeassistant.components.ambient_station
|
||||
@@ -235,7 +235,7 @@ aioelectricitymaps==1.1.1
|
||||
aioemonitor==1.0.5
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==41.13.0
|
||||
aioesphomeapi==41.14.0
|
||||
|
||||
# homeassistant.components.flo
|
||||
aioflo==2021.11.0
|
||||
@@ -366,7 +366,7 @@ aioruuvigateway==0.1.0
|
||||
aiosenz==1.0.0
|
||||
|
||||
# homeassistant.components.shelly
|
||||
aioshelly==13.12.0
|
||||
aioshelly==13.13.0
|
||||
|
||||
# homeassistant.components.skybell
|
||||
aioskybell==22.7.0
|
||||
@@ -777,7 +777,7 @@ energyzero==2.1.1
|
||||
enocean==0.50
|
||||
|
||||
# homeassistant.components.environment_canada
|
||||
env-canada==0.11.3
|
||||
env-canada==0.12.1
|
||||
|
||||
# homeassistant.components.season
|
||||
ephem==4.1.6
|
||||
@@ -1328,7 +1328,7 @@ nextcord==3.1.0
|
||||
nextdns==4.1.0
|
||||
|
||||
# homeassistant.components.niko_home_control
|
||||
nhc==0.6.1
|
||||
nhc==0.7.0
|
||||
|
||||
# homeassistant.components.nibe_heatpump
|
||||
nibe==2.19.0
|
||||
@@ -1461,7 +1461,7 @@ plexauth==0.0.6
|
||||
plexwebsocket==0.0.14
|
||||
|
||||
# homeassistant.components.plugwise
|
||||
plugwise==1.7.8
|
||||
plugwise==1.8.0
|
||||
|
||||
# homeassistant.components.poolsense
|
||||
poolsense==0.0.8
|
||||
@@ -1479,7 +1479,7 @@ prayer-times-calculator-offline==1.0.3
|
||||
prometheus-client==0.21.0
|
||||
|
||||
# homeassistant.components.prowl
|
||||
prowlpy==1.0.2
|
||||
prowlpy==1.1.1
|
||||
|
||||
# homeassistant.components.hardware
|
||||
# homeassistant.components.recorder
|
||||
@@ -1538,7 +1538,7 @@ py-synologydsm-api==2.7.3
|
||||
pyCEC==0.5.2
|
||||
|
||||
# homeassistant.components.control4
|
||||
pyControl4==1.2.0
|
||||
pyControl4==1.5.0
|
||||
|
||||
# homeassistant.components.duotecno
|
||||
pyDuotecno==2024.10.1
|
||||
@@ -1717,7 +1717,7 @@ pygti==0.9.4
|
||||
pyhaversion==22.8.0
|
||||
|
||||
# homeassistant.components.heos
|
||||
pyheos==1.0.5
|
||||
pyheos==1.0.6
|
||||
|
||||
# homeassistant.components.hive
|
||||
pyhive-integration==1.0.2
|
||||
@@ -1893,7 +1893,7 @@ pyotgw==2.2.2
|
||||
pyotp==2.9.0
|
||||
|
||||
# homeassistant.components.overkiz
|
||||
pyoverkiz==1.17.2
|
||||
pyoverkiz==1.19.0
|
||||
|
||||
# homeassistant.components.onewire
|
||||
pyownet==0.10.0.post1
|
||||
@@ -1923,7 +1923,7 @@ pypoint==3.0.0
|
||||
pyportainer==1.0.3
|
||||
|
||||
# homeassistant.components.probe_plus
|
||||
pyprobeplus==1.0.1
|
||||
pyprobeplus==1.1.0
|
||||
|
||||
# homeassistant.components.profiler
|
||||
pyprof2calltree==1.4.5
|
||||
@@ -2474,7 +2474,7 @@ thermopro-ble==0.13.1
|
||||
thinqconnect==1.0.8
|
||||
|
||||
# homeassistant.components.tilt_ble
|
||||
tilt-ble==0.3.1
|
||||
tilt-ble==1.0.1
|
||||
|
||||
# homeassistant.components.tilt_pi
|
||||
tilt-pi==0.2.1
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"""The tests for the august platform."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
import pytest
|
||||
@@ -33,6 +33,8 @@ from .mocks import (
|
||||
_mock_inoperative_august_lock_detail,
|
||||
_mock_lock_with_offline_key,
|
||||
_mock_operative_august_lock_detail,
|
||||
mock_august_config_entry,
|
||||
mock_client_credentials,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@@ -284,3 +286,18 @@ async def test_oauth_migration_on_legacy_entry(hass: HomeAssistant) -> None:
|
||||
assert len(flows) == 1
|
||||
assert flows[0]["step_id"] == "pick_implementation"
|
||||
assert flows[0]["context"]["source"] == "reauth"
|
||||
|
||||
|
||||
async def test_oauth_implementation_not_available(hass: HomeAssistant) -> None:
|
||||
"""Test that unavailable OAuth implementation raises ConfigEntryNotReady."""
|
||||
await mock_client_credentials(hass)
|
||||
entry = await mock_august_config_entry(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.august.config_entry_oauth2_flow.async_get_config_entry_implementation",
|
||||
side_effect=ValueError("Implementation not available"),
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
@@ -15,12 +15,17 @@ from homeassistant.data_entry_flow import FlowResultType
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
FAKE_CONFIG = {
|
||||
CONF_STATION: "ON/s1234567",
|
||||
CONF_STATION: "123",
|
||||
CONF_LANGUAGE: "English",
|
||||
CONF_LATITUDE: 42.42,
|
||||
CONF_LONGITUDE: -42.42,
|
||||
}
|
||||
FAKE_TITLE = "Universal title!"
|
||||
FAKE_STATIONS = [
|
||||
{"label": "Toronto, ON", "value": "123"},
|
||||
{"label": "Ottawa, ON", "value": "456"},
|
||||
{"label": "Montreal, QC", "value": "789"},
|
||||
]
|
||||
|
||||
|
||||
def mocked_ec():
|
||||
@@ -40,10 +45,19 @@ def mocked_ec():
|
||||
)
|
||||
|
||||
|
||||
def mocked_stations():
|
||||
"""Mock the station list."""
|
||||
return patch(
|
||||
"homeassistant.components.environment_canada.config_flow.get_ec_sites_list",
|
||||
return_value=FAKE_STATIONS,
|
||||
)
|
||||
|
||||
|
||||
async def test_create_entry(hass: HomeAssistant) -> None:
|
||||
"""Test creating an entry."""
|
||||
with (
|
||||
mocked_ec(),
|
||||
mocked_stations(),
|
||||
patch(
|
||||
"homeassistant.components.environment_canada.async_setup_entry",
|
||||
return_value=True,
|
||||
@@ -66,12 +80,13 @@ async def test_create_same_entry_twice(hass: HomeAssistant) -> None:
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=FAKE_CONFIG,
|
||||
unique_id="ON/s1234567-english",
|
||||
unique_id="123-english",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with (
|
||||
mocked_ec(),
|
||||
mocked_stations(),
|
||||
patch(
|
||||
"homeassistant.components.environment_canada.async_setup_entry",
|
||||
return_value=True,
|
||||
@@ -101,9 +116,12 @@ async def test_create_same_entry_twice(hass: HomeAssistant) -> None:
|
||||
async def test_exception_handling(hass: HomeAssistant, error) -> None:
|
||||
"""Test exception handling."""
|
||||
exc, base_error = error
|
||||
with patch(
|
||||
"homeassistant.components.environment_canada.config_flow.ECWeather",
|
||||
side_effect=exc,
|
||||
with (
|
||||
mocked_stations(),
|
||||
patch(
|
||||
"homeassistant.components.environment_canada.config_flow.ECWeather",
|
||||
side_effect=exc,
|
||||
),
|
||||
):
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
@@ -121,6 +139,7 @@ async def test_lat_lon_not_specified(hass: HomeAssistant) -> None:
|
||||
"""Test that the import step works when coordinates are not specified."""
|
||||
with (
|
||||
mocked_ec(),
|
||||
mocked_stations(),
|
||||
patch(
|
||||
"homeassistant.components.environment_canada.async_setup_entry",
|
||||
return_value=True,
|
||||
@@ -136,3 +155,31 @@ async def test_lat_lon_not_specified(hass: HomeAssistant) -> None:
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == FAKE_CONFIG
|
||||
assert result["title"] == FAKE_TITLE
|
||||
|
||||
|
||||
async def test_coordinates_without_station(hass: HomeAssistant) -> None:
|
||||
"""Test setup with coordinates but no station ID."""
|
||||
with (
|
||||
mocked_ec(),
|
||||
mocked_stations(),
|
||||
patch(
|
||||
"homeassistant.components.environment_canada.async_setup_entry",
|
||||
return_value=True,
|
||||
),
|
||||
):
|
||||
# Config with coordinates but no station
|
||||
config_no_station = {
|
||||
CONF_LANGUAGE: "English",
|
||||
CONF_LATITUDE: 42.42,
|
||||
CONF_LONGITUDE: -42.42,
|
||||
}
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
flow["flow_id"], config_no_station
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == FAKE_CONFIG
|
||||
assert result["title"] == FAKE_TITLE
|
||||
|
@@ -32,7 +32,6 @@ from homeassistant.components.stream import (
|
||||
from homeassistant.config_entries import ConfigFlowResult
|
||||
from homeassistant.const import (
|
||||
CONF_AUTHENTICATION,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
@@ -56,16 +55,17 @@ TESTDATA = {
|
||||
CONF_VERIFY_SSL: False,
|
||||
}
|
||||
|
||||
TESTDATA_ONLYSTILL = TESTDATA.copy()
|
||||
TESTDATA_ONLYSTILL.pop(CONF_STREAM_SOURCE)
|
||||
|
||||
TESTDATA_ONLYSTREAM = TESTDATA.copy()
|
||||
TESTDATA_ONLYSTREAM.pop(CONF_STILL_IMAGE_URL)
|
||||
|
||||
TESTDATA_OPTIONS = {
|
||||
CONF_LIMIT_REFETCH_TO_URL_CHANGE: False,
|
||||
**TESTDATA,
|
||||
}
|
||||
|
||||
TESTDATA_YAML = {
|
||||
CONF_NAME: "Yaml Defined Name",
|
||||
**TESTDATA,
|
||||
}
|
||||
|
||||
|
||||
@respx.mock
|
||||
@pytest.mark.usefixtures("fakeimg_png")
|
||||
@@ -135,11 +135,9 @@ async def test_form_only_stillimage(
|
||||
mock_setup_entry: _patch[MagicMock],
|
||||
) -> None:
|
||||
"""Test we complete ok if the user wants still images only."""
|
||||
data = TESTDATA.copy()
|
||||
data.pop(CONF_STREAM_SOURCE)
|
||||
result1 = await hass.config_entries.flow.async_configure(
|
||||
user_flow["flow_id"],
|
||||
data,
|
||||
TESTDATA_ONLYSTILL,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result1["type"] is FlowResultType.FORM
|
||||
@@ -235,11 +233,9 @@ async def test_form_only_stillimage_gif(
|
||||
mock_setup_entry: _patch[MagicMock],
|
||||
) -> None:
|
||||
"""Test we complete ok if the user wants a gif."""
|
||||
data = TESTDATA.copy()
|
||||
data.pop(CONF_STREAM_SOURCE)
|
||||
result1 = await hass.config_entries.flow.async_configure(
|
||||
user_flow["flow_id"],
|
||||
data,
|
||||
TESTDATA_ONLYSTILL,
|
||||
)
|
||||
assert result1["type"] is FlowResultType.FORM
|
||||
assert result1["step_id"] == "user_confirm"
|
||||
@@ -262,11 +258,9 @@ async def test_form_only_svg_whitespace(
|
||||
"""Test we complete ok if svg starts with whitespace, issue #68889."""
|
||||
fakeimgbytes_wspace_svg = bytes(" \n ", encoding="utf-8") + fakeimgbytes_svg
|
||||
respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_wspace_svg)
|
||||
data = TESTDATA.copy()
|
||||
data.pop(CONF_STREAM_SOURCE)
|
||||
result1 = await hass.config_entries.flow.async_configure(
|
||||
user_flow["flow_id"],
|
||||
data,
|
||||
TESTDATA_ONLYSTILL,
|
||||
)
|
||||
assert result1["type"] is FlowResultType.FORM
|
||||
assert result1["step_id"] == "user_confirm"
|
||||
@@ -296,11 +290,9 @@ async def test_form_only_still_sample(
|
||||
image_path = os.path.join(os.path.dirname(__file__), image_file)
|
||||
image_bytes = await hass.async_add_executor_job(Path(image_path).read_bytes)
|
||||
respx.get("http://127.0.0.1/testurl/1").respond(stream=image_bytes)
|
||||
data = TESTDATA.copy()
|
||||
data.pop(CONF_STREAM_SOURCE)
|
||||
result1 = await hass.config_entries.flow.async_configure(
|
||||
user_flow["flow_id"],
|
||||
data,
|
||||
TESTDATA_ONLYSTILL,
|
||||
)
|
||||
assert result1["type"] is FlowResultType.FORM
|
||||
assert result1["step_id"] == "user_confirm"
|
||||
@@ -364,8 +356,7 @@ async def test_form_still_template(
|
||||
# There is no need to mock the request if its an
|
||||
# invalid url because we will never make the request
|
||||
respx.get(url).respond(stream=fakeimgbytes_png)
|
||||
data = TESTDATA.copy()
|
||||
data.pop(CONF_STREAM_SOURCE)
|
||||
data = TESTDATA_ONLYSTILL.copy()
|
||||
data[CONF_STILL_IMAGE_URL] = template
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
user_flow["flow_id"],
|
||||
@@ -417,8 +408,7 @@ async def test_form_only_stream(
|
||||
mock_create_stream: _patch[MagicMock],
|
||||
) -> None:
|
||||
"""Test we complete ok if the user wants stream only."""
|
||||
data = TESTDATA.copy()
|
||||
data.pop(CONF_STILL_IMAGE_URL)
|
||||
data = TESTDATA_ONLYSTREAM.copy()
|
||||
data[CONF_STREAM_SOURCE] = "rtsp://user:pass@127.0.0.1/testurl/2"
|
||||
result1 = await hass.config_entries.flow.async_configure(
|
||||
user_flow["flow_id"],
|
||||
@@ -592,8 +582,6 @@ async def test_form_stream_timeout(
|
||||
@respx.mock
|
||||
async def test_form_stream_not_set_up(hass: HomeAssistant, user_flow) -> None:
|
||||
"""Test we handle if stream has not been set up."""
|
||||
TESTDATA_ONLY_STREAM = TESTDATA.copy()
|
||||
TESTDATA_ONLY_STREAM.pop(CONF_STILL_IMAGE_URL)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.generic.config_flow.create_stream",
|
||||
@@ -601,7 +589,7 @@ async def test_form_stream_not_set_up(hass: HomeAssistant, user_flow) -> None:
|
||||
):
|
||||
result1 = await hass.config_entries.flow.async_configure(
|
||||
user_flow["flow_id"],
|
||||
TESTDATA_ONLY_STREAM,
|
||||
TESTDATA_ONLYSTREAM,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -612,8 +600,6 @@ async def test_form_stream_not_set_up(hass: HomeAssistant, user_flow) -> None:
|
||||
@respx.mock
|
||||
async def test_form_stream_other_error(hass: HomeAssistant, user_flow) -> None:
|
||||
"""Test the unknown error for streams."""
|
||||
TESTDATA_ONLY_STREAM = TESTDATA.copy()
|
||||
TESTDATA_ONLY_STREAM.pop(CONF_STILL_IMAGE_URL)
|
||||
|
||||
with (
|
||||
patch(
|
||||
@@ -624,7 +610,7 @@ async def test_form_stream_other_error(hass: HomeAssistant, user_flow) -> None:
|
||||
):
|
||||
await hass.config_entries.flow.async_configure(
|
||||
user_flow["flow_id"],
|
||||
TESTDATA_ONLY_STREAM,
|
||||
TESTDATA_ONLYSTREAM,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -794,14 +780,12 @@ async def test_options_only_stream(
|
||||
mock_create_stream: _patch[MagicMock],
|
||||
) -> None:
|
||||
"""Test the options flow without a still_image_url."""
|
||||
data = TESTDATA.copy()
|
||||
data.pop(CONF_STILL_IMAGE_URL)
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
title="Test Camera",
|
||||
domain=DOMAIN,
|
||||
data={},
|
||||
options=data,
|
||||
options=TESTDATA_ONLYSTREAM,
|
||||
)
|
||||
mock_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
@@ -813,7 +797,7 @@ async def test_options_only_stream(
|
||||
# try updating the config options
|
||||
result2 = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input=data,
|
||||
user_input=TESTDATA_ONLYSTREAM,
|
||||
)
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "user_confirm"
|
||||
@@ -830,7 +814,8 @@ async def test_options_still_and_stream_not_provided(
|
||||
mock_setup_entry: _patch[MagicMock],
|
||||
) -> None:
|
||||
"""Test we show a suitable error if neither still or stream URL are provided."""
|
||||
data = TESTDATA.copy()
|
||||
data = TESTDATA_ONLYSTILL.copy()
|
||||
data.pop(CONF_STILL_IMAGE_URL)
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
title="Test Camera",
|
||||
@@ -845,8 +830,6 @@ async def test_options_still_and_stream_not_provided(
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
data.pop(CONF_STILL_IMAGE_URL)
|
||||
data.pop(CONF_STREAM_SOURCE)
|
||||
result2 = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input=data,
|
||||
|
@@ -12599,7 +12599,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_energy_export_tariff_1:device-registry]
|
||||
@@ -12690,7 +12690,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_energy_export_tariff_2:device-registry]
|
||||
@@ -12781,7 +12781,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_energy_export_tariff_3:device-registry]
|
||||
@@ -12872,7 +12872,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_energy_export_tariff_4:device-registry]
|
||||
@@ -12963,7 +12963,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_energy_import:device-registry]
|
||||
@@ -13054,7 +13054,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_energy_import_tariff_1:device-registry]
|
||||
@@ -13145,7 +13145,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_energy_import_tariff_2:device-registry]
|
||||
@@ -13236,7 +13236,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_energy_import_tariff_3:device-registry]
|
||||
@@ -13327,7 +13327,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_energy_import_tariff_4:device-registry]
|
||||
@@ -13418,7 +13418,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_frequency:device-registry]
|
||||
@@ -15249,7 +15249,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_energy_import:device-registry]
|
||||
|
@@ -19,9 +19,9 @@
|
||||
}),
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'Husqvarna',
|
||||
'model': 'AUTOMOWER® 450XH',
|
||||
'model_id': None,
|
||||
'manufacturer': 'HUSQVARNA',
|
||||
'model': 'AUTOMOWER®',
|
||||
'model_id': '450XH',
|
||||
'name': 'Test Mower 1',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
|
@@ -565,25 +565,6 @@ MOCK_SUBENTRY_SENSOR_COMPONENT_LAST_RESET = {
|
||||
"entity_picture": "https://example.com/e9261f6feed443e7b7d5f3fbe2a47412",
|
||||
},
|
||||
}
|
||||
MOCK_SUBENTRY_SIREN_COMPONENT = {
|
||||
"3faf1318023c46c5aea26707eeb6f12e": {
|
||||
"platform": "siren",
|
||||
"name": "Siren",
|
||||
"entity_category": None,
|
||||
"command_topic": "test-topic",
|
||||
"state_topic": "test-topic",
|
||||
"command_template": "{{ value }}",
|
||||
"command_off_template": "{{ value }}",
|
||||
"value_template": "{{ value_json.value }}",
|
||||
"payload_off": "OFF",
|
||||
"payload_on": "ON",
|
||||
"available_tones": ["Happy hour", "Cooling alarm"],
|
||||
"support_volume_set": True,
|
||||
"support_duration": True,
|
||||
"entity_picture": "https://example.com/3faf1318023c46c5aea26707eeb6f12e",
|
||||
"optimistic": True,
|
||||
},
|
||||
}
|
||||
MOCK_SUBENTRY_SWITCH_COMPONENT = {
|
||||
"3faf1318016c46c5aea26707eeb6f12e": {
|
||||
"platform": "switch",
|
||||
@@ -717,10 +698,6 @@ MOCK_SENSOR_SUBENTRY_DATA_LAST_RESET_TEMPLATE = {
|
||||
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
|
||||
"components": MOCK_SUBENTRY_SENSOR_COMPONENT_LAST_RESET,
|
||||
}
|
||||
MOCK_SIREN_SUBENTRY_DATA = {
|
||||
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
|
||||
"components": MOCK_SUBENTRY_SIREN_COMPONENT,
|
||||
}
|
||||
MOCK_SWITCH_SUBENTRY_DATA = {
|
||||
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
|
||||
"components": MOCK_SUBENTRY_SWITCH_COMPONENT,
|
||||
|
@@ -17,10 +17,7 @@ import voluptuous as vol
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import mqtt
|
||||
from homeassistant.components.hassio import AddonError
|
||||
from homeassistant.components.mqtt.config_flow import (
|
||||
PWD_NOT_CHANGED,
|
||||
TRANSLATION_DESCRIPTION_PLACEHOLDERS,
|
||||
)
|
||||
from homeassistant.components.mqtt.config_flow import PWD_NOT_CHANGED
|
||||
from homeassistant.components.mqtt.util import learn_more_url
|
||||
from homeassistant.config_entries import ConfigSubentry, ConfigSubentryData
|
||||
from homeassistant.const import (
|
||||
@@ -60,7 +57,6 @@ from .common import (
|
||||
MOCK_SENSOR_SUBENTRY_DATA,
|
||||
MOCK_SENSOR_SUBENTRY_DATA_LAST_RESET_TEMPLATE,
|
||||
MOCK_SENSOR_SUBENTRY_DATA_STATE_CLASS,
|
||||
MOCK_SIREN_SUBENTRY_DATA,
|
||||
MOCK_SWITCH_SUBENTRY_DATA,
|
||||
)
|
||||
|
||||
@@ -3656,41 +3652,6 @@ async def test_migrate_of_incompatible_config_entry(
|
||||
"Milk notifier Energy",
|
||||
id="sensor_total",
|
||||
),
|
||||
pytest.param(
|
||||
MOCK_SIREN_SUBENTRY_DATA,
|
||||
{"name": "Milk notifier", "mqtt_settings": {"qos": 0}},
|
||||
{"name": "Siren"},
|
||||
{},
|
||||
(),
|
||||
{
|
||||
"command_topic": "test-topic",
|
||||
"command_template": "{{ value }}",
|
||||
"state_topic": "test-topic",
|
||||
"value_template": "{{ value_json.value }}",
|
||||
"optimistic": True,
|
||||
"available_tones": ["Happy hour", "Cooling alarm"],
|
||||
"support_duration": True,
|
||||
"support_volume_set": True,
|
||||
"siren_advanced_settings": {
|
||||
"command_off_template": "{{ value }}",
|
||||
},
|
||||
},
|
||||
(
|
||||
(
|
||||
{"command_topic": "test-topic#invalid"},
|
||||
{"command_topic": "invalid_publish_topic"},
|
||||
),
|
||||
(
|
||||
{
|
||||
"command_topic": "test-topic",
|
||||
"state_topic": "test-topic#invalid",
|
||||
},
|
||||
{"state_topic": "invalid_subscribe_topic"},
|
||||
),
|
||||
),
|
||||
"Milk notifier Siren",
|
||||
id="siren",
|
||||
),
|
||||
pytest.param(
|
||||
MOCK_SWITCH_SUBENTRY_DATA,
|
||||
{"name": "Milk notifier", "mqtt_settings": {"qos": 0}},
|
||||
@@ -3796,16 +3757,12 @@ async def test_subentry_configflow(
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {}
|
||||
assert (
|
||||
result["description_placeholders"]
|
||||
== {
|
||||
"mqtt_device": device_name,
|
||||
"platform": component["platform"],
|
||||
"entity": entity_name,
|
||||
"url": learn_more_url(component["platform"]),
|
||||
}
|
||||
| TRANSLATION_DESCRIPTION_PLACEHOLDERS
|
||||
)
|
||||
assert result["description_placeholders"] == {
|
||||
"mqtt_device": device_name,
|
||||
"platform": component["platform"],
|
||||
"entity": entity_name,
|
||||
"url": learn_more_url(component["platform"]),
|
||||
}
|
||||
|
||||
# Process entity details step
|
||||
assert result["step_id"] == "entity_platform_config"
|
||||
@@ -3827,16 +3784,12 @@ async def test_subentry_configflow(
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {}
|
||||
assert (
|
||||
result["description_placeholders"]
|
||||
== {
|
||||
"mqtt_device": device_name,
|
||||
"platform": component["platform"],
|
||||
"entity": entity_name,
|
||||
"url": learn_more_url(component["platform"]),
|
||||
}
|
||||
| TRANSLATION_DESCRIPTION_PLACEHOLDERS
|
||||
)
|
||||
assert result["description_placeholders"] == {
|
||||
"mqtt_device": device_name,
|
||||
"platform": component["platform"],
|
||||
"entity": entity_name,
|
||||
"url": learn_more_url(component["platform"]),
|
||||
}
|
||||
|
||||
# Process mqtt platform config flow
|
||||
# Test an invalid mqtt user input case
|
||||
@@ -5143,16 +5096,12 @@ async def test_subentry_configflow_section_feature(
|
||||
user_input={"platform": "fan"},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert (
|
||||
result["description_placeholders"]
|
||||
== {
|
||||
"mqtt_device": "Bla",
|
||||
"platform": "fan",
|
||||
"entity": "Bla",
|
||||
"url": learn_more_url("fan"),
|
||||
}
|
||||
| TRANSLATION_DESCRIPTION_PLACEHOLDERS
|
||||
)
|
||||
assert result["description_placeholders"] == {
|
||||
"mqtt_device": "Bla",
|
||||
"platform": "fan",
|
||||
"entity": "Bla",
|
||||
"url": learn_more_url("fan"),
|
||||
}
|
||||
|
||||
# Process entity details step
|
||||
assert result["step_id"] == "entity_platform_config"
|
||||
|
@@ -213,12 +213,14 @@ async def test_generate_data_with_attachments(
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
@freeze_time("2025-06-14 22:59:00")
|
||||
@pytest.mark.parametrize("image_model", ["gpt-image-1", "gpt-image-1-mini"])
|
||||
async def test_generate_image(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_create_stream: AsyncMock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
image_model: str,
|
||||
) -> None:
|
||||
"""Test AI Task image generation."""
|
||||
entity_id = "ai_task.openai_ai_task"
|
||||
@@ -232,6 +234,12 @@ async def test_generate_image(
|
||||
if entry.subentry_type == "ai_task_data"
|
||||
)
|
||||
)
|
||||
hass.config_entries.async_update_subentry(
|
||||
mock_config_entry,
|
||||
ai_task_entry,
|
||||
data={"image_model": image_model},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert entity_entry is not None
|
||||
assert entity_entry.config_entry_id == mock_config_entry.entry_id
|
||||
assert entity_entry.config_subentry_id == ai_task_entry.subentry_id
|
||||
@@ -258,7 +266,7 @@ async def test_generate_image(
|
||||
assert result["width"] == 1536
|
||||
assert result["revised_prompt"] == "Mock revised prompt."
|
||||
assert result["mime_type"] == "image/png"
|
||||
assert result["model"] == "gpt-image-1"
|
||||
assert result["model"] == image_model
|
||||
|
||||
mock_upload_media.assert_called_once()
|
||||
image_data = mock_upload_media.call_args[0][1]
|
||||
|
@@ -14,6 +14,7 @@ from homeassistant.components.openai_conversation.config_flow import (
|
||||
from homeassistant.components.openai_conversation.const import (
|
||||
CONF_CHAT_MODEL,
|
||||
CONF_CODE_INTERPRETER,
|
||||
CONF_IMAGE_MODEL,
|
||||
CONF_MAX_TOKENS,
|
||||
CONF_PROMPT,
|
||||
CONF_REASONING_EFFORT,
|
||||
@@ -917,6 +918,7 @@ async def test_creating_ai_task_subentry_advanced(
|
||||
assert result4.get("data") == {
|
||||
CONF_RECOMMENDED: False,
|
||||
CONF_CHAT_MODEL: "gpt-4o",
|
||||
CONF_IMAGE_MODEL: "gpt-image-1",
|
||||
CONF_MAX_TOKENS: 200,
|
||||
CONF_TEMPERATURE: 0.5,
|
||||
CONF_TOP_P: 0.9,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"""Test fixtures for Prowl."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -27,7 +27,7 @@ BAD_API_RESPONSE = {"base": "bad_api_response"}
|
||||
|
||||
@pytest.fixture
|
||||
async def configure_prowl_through_yaml(
|
||||
hass: HomeAssistant, mock_prowlpy: Generator[Mock]
|
||||
hass: HomeAssistant, mock_prowlpy: Generator[AsyncMock]
|
||||
) -> Generator[None]:
|
||||
"""Configure the notify domain with YAML for the Prowl platform."""
|
||||
await async_setup_component(
|
||||
@@ -48,7 +48,9 @@ async def configure_prowl_through_yaml(
|
||||
|
||||
@pytest.fixture
|
||||
async def prowl_notification_entity(
|
||||
hass: HomeAssistant, mock_prowlpy: Mock, mock_prowlpy_config_entry: MockConfigEntry
|
||||
hass: HomeAssistant,
|
||||
mock_prowlpy: AsyncMock,
|
||||
mock_prowlpy_config_entry: MockConfigEntry,
|
||||
) -> Generator[MockConfigEntry]:
|
||||
"""Configure a Prowl Notification Entity."""
|
||||
mock_prowlpy.verify_key.return_value = True
|
||||
@@ -61,11 +63,24 @@ async def prowl_notification_entity(
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_prowlpy() -> Generator[Mock]:
|
||||
def mock_prowlpy() -> Generator[AsyncMock]:
|
||||
"""Mock the prowlpy library."""
|
||||
mock_instance = AsyncMock()
|
||||
|
||||
with patch("homeassistant.components.prowl.notify.prowlpy.Prowl") as MockProwl:
|
||||
mock_instance = MockProwl.return_value
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.prowl.notify.prowlpy.AsyncProwl",
|
||||
return_value=mock_instance,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.prowl.helpers.prowlpy.AsyncProwl",
|
||||
return_value=mock_instance,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.prowl.__init__.prowlpy.AsyncProwl",
|
||||
return_value=mock_instance,
|
||||
),
|
||||
):
|
||||
yield mock_instance
|
||||
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"""Test Prowl config flow."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import prowlpy
|
||||
|
||||
@@ -13,7 +13,7 @@ from homeassistant.data_entry_flow import FlowResultType
|
||||
from .conftest import BAD_API_RESPONSE, CONF_INPUT, INVALID_API_KEY_ERROR, TIMEOUT_ERROR
|
||||
|
||||
|
||||
async def test_flow_user(hass: HomeAssistant, mock_prowlpy: Mock) -> None:
|
||||
async def test_flow_user(hass: HomeAssistant, mock_prowlpy: AsyncMock) -> None:
|
||||
"""Test user initialized flow."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
@@ -30,7 +30,9 @@ async def test_flow_user(hass: HomeAssistant, mock_prowlpy: Mock) -> None:
|
||||
assert result["data"] == {CONF_API_KEY: CONF_INPUT[CONF_API_KEY]}
|
||||
|
||||
|
||||
async def test_flow_duplicate_api_key(hass: HomeAssistant, mock_prowlpy: Mock) -> None:
|
||||
async def test_flow_duplicate_api_key(
|
||||
hass: HomeAssistant, mock_prowlpy: AsyncMock
|
||||
) -> None:
|
||||
"""Test user initialized flow."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
@@ -52,7 +54,7 @@ async def test_flow_duplicate_api_key(hass: HomeAssistant, mock_prowlpy: Mock) -
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
|
||||
|
||||
async def test_flow_user_bad_key(hass: HomeAssistant, mock_prowlpy: Mock) -> None:
|
||||
async def test_flow_user_bad_key(hass: HomeAssistant, mock_prowlpy: AsyncMock) -> None:
|
||||
"""Test user submitting a bad API key."""
|
||||
mock_prowlpy.verify_key.side_effect = prowlpy.APIError("Invalid API key")
|
||||
|
||||
@@ -70,7 +72,9 @@ async def test_flow_user_bad_key(hass: HomeAssistant, mock_prowlpy: Mock) -> Non
|
||||
assert result["errors"] == INVALID_API_KEY_ERROR
|
||||
|
||||
|
||||
async def test_flow_user_prowl_timeout(hass: HomeAssistant, mock_prowlpy: Mock) -> None:
|
||||
async def test_flow_user_prowl_timeout(
|
||||
hass: HomeAssistant, mock_prowlpy: AsyncMock
|
||||
) -> None:
|
||||
"""Test Prowl API timeout."""
|
||||
mock_prowlpy.verify_key.side_effect = TimeoutError
|
||||
|
||||
@@ -88,7 +92,7 @@ async def test_flow_user_prowl_timeout(hass: HomeAssistant, mock_prowlpy: Mock)
|
||||
assert result["errors"] == TIMEOUT_ERROR
|
||||
|
||||
|
||||
async def test_flow_api_failure(hass: HomeAssistant, mock_prowlpy: Mock) -> None:
|
||||
async def test_flow_api_failure(hass: HomeAssistant, mock_prowlpy: AsyncMock) -> None:
|
||||
"""Test Prowl API failure."""
|
||||
mock_prowlpy.verify_key.side_effect = prowlpy.APIError(BAD_API_RESPONSE)
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"""Testing the Prowl initialisation."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import prowlpy
|
||||
import pytest
|
||||
@@ -18,7 +18,7 @@ from tests.common import MockConfigEntry
|
||||
async def test_load_reload_unload_config_entry(
|
||||
hass: HomeAssistant,
|
||||
mock_prowlpy_config_entry: MockConfigEntry,
|
||||
mock_prowlpy: Mock,
|
||||
mock_prowlpy: AsyncMock,
|
||||
) -> None:
|
||||
"""Test the Prowl configuration entry loading/reloading/unloading."""
|
||||
mock_prowlpy_config_entry.add_to_hass(hass)
|
||||
@@ -57,7 +57,7 @@ async def test_load_reload_unload_config_entry(
|
||||
async def test_config_entry_failures(
|
||||
hass: HomeAssistant,
|
||||
mock_prowlpy_config_entry: MockConfigEntry,
|
||||
mock_prowlpy: Mock,
|
||||
mock_prowlpy: AsyncMock,
|
||||
prowlpy_side_effect,
|
||||
expected_config_state: ConfigEntryState,
|
||||
) -> None:
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"""Test the Prowl notifications."""
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import prowlpy
|
||||
import pytest
|
||||
@@ -29,7 +29,7 @@ EXPECTED_SEND_PARAMETERS = {
|
||||
@pytest.mark.usefixtures("configure_prowl_through_yaml")
|
||||
async def test_send_notification_service(
|
||||
hass: HomeAssistant,
|
||||
mock_prowlpy: Mock,
|
||||
mock_prowlpy: AsyncMock,
|
||||
) -> None:
|
||||
"""Set up Prowl, call notify service, and check API call."""
|
||||
assert hass.services.has_service(notify.DOMAIN, DOMAIN)
|
||||
@@ -40,12 +40,12 @@ async def test_send_notification_service(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_prowlpy.send.assert_called_once_with(**EXPECTED_SEND_PARAMETERS)
|
||||
mock_prowlpy.post.assert_called_once_with(**EXPECTED_SEND_PARAMETERS)
|
||||
|
||||
|
||||
async def test_send_notification_entity_service(
|
||||
hass: HomeAssistant,
|
||||
mock_prowlpy: Mock,
|
||||
mock_prowlpy: AsyncMock,
|
||||
mock_prowlpy_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Set up Prowl via config entry, call notify service, and check API call."""
|
||||
@@ -65,7 +65,7 @@ async def test_send_notification_entity_service(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_prowlpy.send.assert_called_once_with(**EXPECTED_SEND_PARAMETERS)
|
||||
mock_prowlpy.post.assert_called_once_with(**EXPECTED_SEND_PARAMETERS)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -102,7 +102,7 @@ async def test_send_notification_entity_service(
|
||||
)
|
||||
async def test_fail_send_notification_entity_service(
|
||||
hass: HomeAssistant,
|
||||
mock_prowlpy: Mock,
|
||||
mock_prowlpy: AsyncMock,
|
||||
mock_prowlpy_config_entry: MockConfigEntry,
|
||||
prowlpy_side_effect: Exception,
|
||||
raised_exception: type[Exception],
|
||||
@@ -113,7 +113,7 @@ async def test_fail_send_notification_entity_service(
|
||||
await hass.config_entries.async_setup(mock_prowlpy_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_prowlpy.send.side_effect = prowlpy_side_effect
|
||||
mock_prowlpy.post.side_effect = prowlpy_side_effect
|
||||
|
||||
assert hass.services.has_service(notify.DOMAIN, notify.SERVICE_SEND_MESSAGE)
|
||||
with pytest.raises(raised_exception, match=exception_message):
|
||||
@@ -128,7 +128,7 @@ async def test_fail_send_notification_entity_service(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_prowlpy.send.assert_called_once_with(**EXPECTED_SEND_PARAMETERS)
|
||||
mock_prowlpy.post.assert_called_once_with(**EXPECTED_SEND_PARAMETERS)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -166,13 +166,13 @@ async def test_fail_send_notification_entity_service(
|
||||
@pytest.mark.usefixtures("configure_prowl_through_yaml")
|
||||
async def test_fail_send_notification(
|
||||
hass: HomeAssistant,
|
||||
mock_prowlpy: Mock,
|
||||
mock_prowlpy: AsyncMock,
|
||||
prowlpy_side_effect: Exception,
|
||||
raised_exception: type[Exception],
|
||||
exception_message: str | None,
|
||||
) -> None:
|
||||
"""Sending a message via Prowl with a failure."""
|
||||
mock_prowlpy.send.side_effect = prowlpy_side_effect
|
||||
mock_prowlpy.post.side_effect = prowlpy_side_effect
|
||||
|
||||
assert hass.services.has_service(notify.DOMAIN, DOMAIN)
|
||||
with pytest.raises(raised_exception, match=exception_message):
|
||||
@@ -183,7 +183,7 @@ async def test_fail_send_notification(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_prowlpy.send.assert_called_once_with(**EXPECTED_SEND_PARAMETERS)
|
||||
mock_prowlpy.post.assert_called_once_with(**EXPECTED_SEND_PARAMETERS)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -204,12 +204,12 @@ async def test_fail_send_notification(
|
||||
@pytest.mark.usefixtures("configure_prowl_through_yaml")
|
||||
async def test_other_exception_send_notification(
|
||||
hass: HomeAssistant,
|
||||
mock_prowlpy: Mock,
|
||||
mock_prowlpy: AsyncMock,
|
||||
service_data: dict[str, Any],
|
||||
expected_send_parameters: dict[str, Any],
|
||||
) -> None:
|
||||
"""Sending a message via Prowl with a general unhandled exception."""
|
||||
mock_prowlpy.send.side_effect = SyntaxError
|
||||
mock_prowlpy.post.side_effect = SyntaxError
|
||||
|
||||
assert hass.services.has_service(notify.DOMAIN, DOMAIN)
|
||||
with pytest.raises(SyntaxError):
|
||||
@@ -220,4 +220,4 @@ async def test_other_exception_send_notification(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_prowlpy.send.assert_called_once_with(**expected_send_parameters)
|
||||
mock_prowlpy.post.assert_called_once_with(**expected_send_parameters)
|
||||
|
@@ -324,3 +324,32 @@ async def test_zeroconf_discovery_already_configured(
|
||||
|
||||
assert result.get("type") is FlowResultType.ABORT
|
||||
assert entry.unique_id == "test_zeroconf_name._wyoming._tcp.local._Test Satellite"
|
||||
|
||||
|
||||
async def test_bad_config_entry(hass: HomeAssistant) -> None:
|
||||
"""Test we can continue if a config entry is missing info."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={}, # no host/port
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
# hassio
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
data=ADDON_DISCOVERY,
|
||||
context={"source": config_entries.SOURCE_HASSIO},
|
||||
)
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
|
||||
# zeroconf
|
||||
with patch(
|
||||
"homeassistant.components.wyoming.data.load_wyoming_info",
|
||||
return_value=SATELLITE_INFO,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
data=ZEROCONF_DISCOVERY,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
)
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"""The tests for the yale platform."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
import pytest
|
||||
@@ -28,6 +28,8 @@ from .mocks import (
|
||||
_mock_inoperative_yale_lock_detail,
|
||||
_mock_lock_with_offline_key,
|
||||
_mock_operative_yale_lock_detail,
|
||||
mock_client_credentials,
|
||||
mock_yale_config_entry,
|
||||
)
|
||||
|
||||
from tests.typing import WebSocketGenerator
|
||||
@@ -234,3 +236,18 @@ async def test_device_remove_devices(
|
||||
)
|
||||
response = await client.remove_device(dead_device_entry.id, config_entry.entry_id)
|
||||
assert response["success"]
|
||||
|
||||
|
||||
async def test_oauth_implementation_not_available(hass: HomeAssistant) -> None:
|
||||
"""Test that unavailable OAuth implementation raises ConfigEntryNotReady."""
|
||||
await mock_client_credentials(hass)
|
||||
entry = await mock_yale_config_entry(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.yale.config_entry_oauth2_flow.async_get_config_entry_implementation",
|
||||
side_effect=ValueError("Implementation not available"),
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
Reference in New Issue
Block a user