mirror of
https://github.com/home-assistant/core.git
synced 2025-08-07 22:55:10 +02:00
Merge branch 'dev' into subscribe_config_flow_init_remove
This commit is contained in:
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -653,7 +653,7 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Dependency review
|
||||
uses: actions/dependency-review-action@v4.5.0
|
||||
uses: actions/dependency-review-action@v4.6.0
|
||||
with:
|
||||
license-check: false # We use our own license audit checks
|
||||
|
||||
|
@@ -859,8 +859,14 @@ async def _async_set_up_integrations(
|
||||
integrations, all_integrations = await _async_resolve_domains_and_preload(
|
||||
hass, config
|
||||
)
|
||||
all_domains = set(all_integrations)
|
||||
domains = set(integrations)
|
||||
# Detect all cycles
|
||||
integrations_after_dependencies = (
|
||||
await loader.resolve_integrations_after_dependencies(
|
||||
hass, all_integrations.values(), set(all_integrations)
|
||||
)
|
||||
)
|
||||
all_domains = set(integrations_after_dependencies)
|
||||
domains = set(integrations) & all_domains
|
||||
|
||||
_LOGGER.info(
|
||||
"Domains to be set up: %s | %s",
|
||||
@@ -868,6 +874,8 @@ async def _async_set_up_integrations(
|
||||
all_domains - domains,
|
||||
)
|
||||
|
||||
async_set_domains_to_be_loaded(hass, all_domains)
|
||||
|
||||
# Initialize recorder
|
||||
if "recorder" in all_domains:
|
||||
recorder.async_initialize_recorder(hass)
|
||||
@@ -900,24 +908,12 @@ async def _async_set_up_integrations(
|
||||
stage_dep_domains_unfiltered = {
|
||||
dep
|
||||
for domain in stage_domains
|
||||
for dep in all_integrations[domain].all_dependencies
|
||||
for dep in integrations_after_dependencies[domain]
|
||||
if dep not in stage_domains
|
||||
}
|
||||
stage_dep_domains = stage_dep_domains_unfiltered - hass.config.components
|
||||
|
||||
stage_all_domains = stage_domains | stage_dep_domains
|
||||
stage_all_integrations = {
|
||||
domain: all_integrations[domain] for domain in stage_all_domains
|
||||
}
|
||||
# Detect all cycles
|
||||
stage_integrations_after_dependencies = (
|
||||
await loader.resolve_integrations_after_dependencies(
|
||||
hass, stage_all_integrations.values(), stage_all_domains
|
||||
)
|
||||
)
|
||||
stage_all_domains = set(stage_integrations_after_dependencies)
|
||||
stage_domains &= stage_all_domains
|
||||
stage_dep_domains &= stage_all_domains
|
||||
|
||||
_LOGGER.info(
|
||||
"Setting up stage %s: %s | %s\nDependencies: %s | %s",
|
||||
@@ -928,8 +924,6 @@ async def _async_set_up_integrations(
|
||||
stage_dep_domains_unfiltered - stage_dep_domains,
|
||||
)
|
||||
|
||||
async_set_domains_to_be_loaded(hass, stage_all_domains)
|
||||
|
||||
if timeout is None:
|
||||
await _async_setup_multi_components(hass, stage_all_domains, config)
|
||||
continue
|
||||
|
5
homeassistant/brands/eve.json
Normal file
5
homeassistant/brands/eve.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"domain": "eve",
|
||||
"name": "Eve",
|
||||
"iot_standards": ["matter"]
|
||||
}
|
@@ -72,10 +72,10 @@
|
||||
"level": {
|
||||
"name": "Level",
|
||||
"state": {
|
||||
"high": "High",
|
||||
"low": "Low",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "Moderate",
|
||||
"very_high": "Very high"
|
||||
"very_high": "[%key:common::state::very_high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,10 +89,10 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
"very_high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::very_high%]"
|
||||
"very_high": "[%key:common::state::very_high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,10 +123,10 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
"very_high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::very_high%]"
|
||||
"very_high": "[%key:common::state::very_high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,10 +167,10 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
"very_high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::very_high%]"
|
||||
"very_high": "[%key:common::state::very_high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,10 +181,10 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
"very_high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::very_high%]"
|
||||
"very_high": "[%key:common::state::very_high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,10 +195,10 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
"very_high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::very_high%]"
|
||||
"very_high": "[%key:common::state::very_high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -11,5 +11,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aioairzone"],
|
||||
"requirements": ["aioairzone==0.9.9"]
|
||||
"requirements": ["aioairzone==1.0.0"]
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@ from aioairzone.const import (
|
||||
AZD_HUMIDITY,
|
||||
AZD_TEMP,
|
||||
AZD_TEMP_UNIT,
|
||||
AZD_THERMOSTAT_BATTERY,
|
||||
AZD_THERMOSTAT_SIGNAL,
|
||||
AZD_WEBSERVER,
|
||||
AZD_WIFI_RSSI,
|
||||
AZD_ZONES,
|
||||
@@ -73,6 +75,20 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
key=AZD_THERMOSTAT_BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
key=AZD_THERMOSTAT_SIGNAL,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
translation_key="thermostat_signal",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
@@ -76,6 +76,9 @@
|
||||
"sensor": {
|
||||
"rssi": {
|
||||
"name": "RSSI"
|
||||
},
|
||||
"thermostat_signal": {
|
||||
"name": "Signal strength"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import voluptuous as vol
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_IGNORE,
|
||||
SOURCE_REAUTH,
|
||||
SOURCE_ZEROCONF,
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
@@ -381,7 +382,9 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_IDENTIFIERS: list(combined_identifiers),
|
||||
},
|
||||
)
|
||||
if entry.source != SOURCE_IGNORE:
|
||||
# Don't reload ignored entries or in the middle of reauth,
|
||||
# e.g. if the user is entering a new PIN
|
||||
if entry.source != SOURCE_IGNORE and self.source != SOURCE_REAUTH:
|
||||
self.hass.config_entries.async_schedule_reload(entry.entry_id)
|
||||
if not allow_exist:
|
||||
raise DeviceAlreadyConfigured
|
||||
|
@@ -36,9 +36,9 @@
|
||||
"wi_fi_strength": {
|
||||
"name": "Wi-Fi strength",
|
||||
"state": {
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High"
|
||||
"low": "[%key:common::state::low%]",
|
||||
"medium": "[%key:common::state::medium%]",
|
||||
"high": "[%key:common::state::high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -103,8 +103,8 @@
|
||||
"temperature_range": {
|
||||
"name": "Temperature range",
|
||||
"state": {
|
||||
"low": "Low",
|
||||
"high": "High"
|
||||
"low": "[%key:common::state::low%]",
|
||||
"high": "[%key:common::state::high%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -124,8 +124,8 @@
|
||||
"battery": {
|
||||
"name": "Battery",
|
||||
"state": {
|
||||
"off": "Normal",
|
||||
"on": "Low"
|
||||
"off": "[%key:common::state::normal%]",
|
||||
"on": "[%key:common::state::low%]"
|
||||
}
|
||||
},
|
||||
"battery_charging": {
|
||||
@@ -145,7 +145,7 @@
|
||||
"cold": {
|
||||
"name": "Cold",
|
||||
"state": {
|
||||
"off": "[%key:component::binary_sensor::entity_component::battery::state::off%]",
|
||||
"off": "[%key:common::state::normal%]",
|
||||
"on": "Cold"
|
||||
}
|
||||
},
|
||||
@@ -180,7 +180,7 @@
|
||||
"heat": {
|
||||
"name": "Heat",
|
||||
"state": {
|
||||
"off": "[%key:component::binary_sensor::entity_component::battery::state::off%]",
|
||||
"off": "[%key:common::state::normal%]",
|
||||
"on": "Hot"
|
||||
}
|
||||
},
|
||||
|
@@ -19,7 +19,7 @@
|
||||
"bleak-retry-connector==3.9.0",
|
||||
"bluetooth-adapters==0.21.4",
|
||||
"bluetooth-auto-recovery==1.4.5",
|
||||
"bluetooth-data-tools==1.26.1",
|
||||
"bluetooth-data-tools==1.27.0",
|
||||
"dbus-fast==2.43.0",
|
||||
"habluetooth==3.37.0"
|
||||
]
|
||||
|
@@ -16,6 +16,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
||||
|
||||
from .const import DOMAIN
|
||||
@@ -91,11 +92,22 @@ class BondConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
self._discovered[CONF_ACCESS_TOKEN] = token
|
||||
try:
|
||||
_, hub_name = await _validate_input(self.hass, self._discovered)
|
||||
bond_id, hub_name = await _validate_input(self.hass, self._discovered)
|
||||
except InputValidationError:
|
||||
return
|
||||
await self.async_set_unique_id(bond_id)
|
||||
self._abort_if_unique_id_configured(updates={CONF_HOST: host})
|
||||
self._discovered[CONF_NAME] = hub_name
|
||||
|
||||
async def async_step_dhcp(
|
||||
self, discovery_info: DhcpServiceInfo
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow initialized by dhcp discovery."""
|
||||
host = discovery_info.ip
|
||||
bond_id = discovery_info.hostname.partition("-")[2].upper()
|
||||
await self.async_set_unique_id(bond_id)
|
||||
return await self.async_step_any_discovery(bond_id, host)
|
||||
|
||||
async def async_step_zeroconf(
|
||||
self, discovery_info: ZeroconfServiceInfo
|
||||
) -> ConfigFlowResult:
|
||||
@@ -104,11 +116,17 @@ class BondConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
host: str = discovery_info.host
|
||||
bond_id = name.partition(".")[0]
|
||||
await self.async_set_unique_id(bond_id)
|
||||
return await self.async_step_any_discovery(bond_id, host)
|
||||
|
||||
async def async_step_any_discovery(
|
||||
self, bond_id: str, host: str
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow initialized by discovery."""
|
||||
for entry in self._async_current_entries():
|
||||
if entry.unique_id != bond_id:
|
||||
continue
|
||||
updates = {CONF_HOST: host}
|
||||
if entry.state == ConfigEntryState.SETUP_ERROR and (
|
||||
if entry.state is ConfigEntryState.SETUP_ERROR and (
|
||||
token := await async_get_token(self.hass, host)
|
||||
):
|
||||
updates[CONF_ACCESS_TOKEN] = token
|
||||
@@ -153,10 +171,14 @@ class BondConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_HOST: self._discovered[CONF_HOST],
|
||||
}
|
||||
try:
|
||||
_, hub_name = await _validate_input(self.hass, data)
|
||||
bond_id, hub_name = await _validate_input(self.hass, data)
|
||||
except InputValidationError as error:
|
||||
errors["base"] = error.base
|
||||
else:
|
||||
await self.async_set_unique_id(bond_id)
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: self._discovered[CONF_HOST]}
|
||||
)
|
||||
return self.async_create_entry(
|
||||
title=hub_name,
|
||||
data=data,
|
||||
@@ -185,8 +207,10 @@ class BondConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
except InputValidationError as error:
|
||||
errors["base"] = error.base
|
||||
else:
|
||||
await self.async_set_unique_id(bond_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
await self.async_set_unique_id(bond_id, raise_on_progress=False)
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: user_input[CONF_HOST]}
|
||||
)
|
||||
return self.async_create_entry(title=hub_name, data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
|
@@ -3,6 +3,16 @@
|
||||
"name": "Bond",
|
||||
"codeowners": ["@bdraco", "@prystupa", "@joshs85", "@marciogranzotto"],
|
||||
"config_flow": true,
|
||||
"dhcp": [
|
||||
{
|
||||
"hostname": "bond-*",
|
||||
"macaddress": "3C6A2C1*"
|
||||
},
|
||||
{
|
||||
"hostname": "bond-*",
|
||||
"macaddress": "F44E38*"
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/bond",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["bond_async"],
|
||||
|
@@ -9,7 +9,7 @@ from bosch_alarm_mode2 import Panel
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .const import CONF_INSTALLER_CODE, CONF_USER_CODE, DOMAIN
|
||||
@@ -34,10 +34,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: BoschAlarmConfigEntry) -
|
||||
await panel.connect()
|
||||
except (PermissionError, ValueError) as err:
|
||||
await panel.disconnect()
|
||||
raise ConfigEntryNotReady from err
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN, translation_key="authentication_failed"
|
||||
) from err
|
||||
except (TimeoutError, OSError, ConnectionRefusedError, SSLError) as err:
|
||||
await panel.disconnect()
|
||||
raise ConfigEntryNotReady("Connection failed") from err
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_connect",
|
||||
) from err
|
||||
|
||||
entry.runtime_data = panel
|
||||
|
||||
|
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
import ssl
|
||||
from typing import Any
|
||||
@@ -163,3 +164,55 @@ class BoschAlarmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
data_schema=self.add_suggested_values_to_schema(schema, user_input),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Perform reauth upon an authentication error."""
|
||||
self._data = dict(entry_data)
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the reauth step."""
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
# Each model variant requires a different authentication flow
|
||||
if "Solution" in self._data[CONF_MODEL]:
|
||||
schema = STEP_AUTH_DATA_SCHEMA_SOLUTION
|
||||
elif "AMAX" in self._data[CONF_MODEL]:
|
||||
schema = STEP_AUTH_DATA_SCHEMA_AMAX
|
||||
else:
|
||||
schema = STEP_AUTH_DATA_SCHEMA_BG
|
||||
|
||||
if user_input is not None:
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
self._data.update(user_input)
|
||||
try:
|
||||
(_, _) = await try_connect(self._data, Panel.LOAD_EXTENDED_INFO)
|
||||
except (PermissionError, ValueError) as e:
|
||||
errors["base"] = "invalid_auth"
|
||||
_LOGGER.error("Authentication Error: %s", e)
|
||||
except (
|
||||
OSError,
|
||||
ConnectionRefusedError,
|
||||
ssl.SSLError,
|
||||
TimeoutError,
|
||||
) as e:
|
||||
_LOGGER.error("Connection Error: %s", e)
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
return self.async_update_reload_and_abort(
|
||||
reauth_entry,
|
||||
data_updates=user_input,
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reauth_confirm",
|
||||
data_schema=self.add_suggested_values_to_schema(schema, user_input),
|
||||
errors=errors,
|
||||
)
|
||||
|
73
homeassistant/components/bosch_alarm/diagnostics.py
Normal file
73
homeassistant/components/bosch_alarm/diagnostics.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""Diagnostics for bosch alarm."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.const import CONF_PASSWORD
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import BoschAlarmConfigEntry
|
||||
from .const import CONF_INSTALLER_CODE, CONF_USER_CODE
|
||||
|
||||
TO_REDACT = [CONF_INSTALLER_CODE, CONF_USER_CODE, CONF_PASSWORD]
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: BoschAlarmConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
|
||||
return {
|
||||
"entry_data": async_redact_data(entry.data, TO_REDACT),
|
||||
"data": {
|
||||
"model": entry.runtime_data.model,
|
||||
"serial_number": entry.runtime_data.serial_number,
|
||||
"protocol_version": entry.runtime_data.protocol_version,
|
||||
"firmware_version": entry.runtime_data.firmware_version,
|
||||
"areas": [
|
||||
{
|
||||
"id": area_id,
|
||||
"name": area.name,
|
||||
"all_ready": area.all_ready,
|
||||
"part_ready": area.part_ready,
|
||||
"faults": area.faults,
|
||||
"alarms": area.alarms,
|
||||
"disarmed": area.is_disarmed(),
|
||||
"arming": area.is_arming(),
|
||||
"pending": area.is_pending(),
|
||||
"part_armed": area.is_part_armed(),
|
||||
"all_armed": area.is_all_armed(),
|
||||
"armed": area.is_armed(),
|
||||
"triggered": area.is_triggered(),
|
||||
}
|
||||
for area_id, area in entry.runtime_data.areas.items()
|
||||
],
|
||||
"points": [
|
||||
{
|
||||
"id": point_id,
|
||||
"name": point.name,
|
||||
"open": point.is_open(),
|
||||
"normal": point.is_normal(),
|
||||
}
|
||||
for point_id, point in entry.runtime_data.points.items()
|
||||
],
|
||||
"doors": [
|
||||
{
|
||||
"id": door_id,
|
||||
"name": door.name,
|
||||
"open": door.is_open(),
|
||||
"locked": door.is_locked(),
|
||||
}
|
||||
for door_id, door in entry.runtime_data.doors.items()
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": output_id,
|
||||
"name": output.name,
|
||||
"active": output.is_active(),
|
||||
}
|
||||
for output_id, output in entry.runtime_data.outputs.items()
|
||||
],
|
||||
"history_events": entry.runtime_data.events,
|
||||
},
|
||||
}
|
@@ -40,7 +40,7 @@ rules:
|
||||
integration-owner: done
|
||||
log-when-unavailable: todo
|
||||
parallel-updates: todo
|
||||
reauthentication-flow: todo
|
||||
reauthentication-flow: done
|
||||
test-coverage: done
|
||||
|
||||
# Gold
|
||||
|
@@ -22,6 +22,18 @@
|
||||
"installer_code": "The installer code from your panel",
|
||||
"user_code": "The user code from your panel"
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"installer_code": "[%key:component::bosch_alarm::config::step::auth::data::installer_code%]",
|
||||
"user_code": "[%key:component::bosch_alarm::config::step::auth::data::user_code%]"
|
||||
},
|
||||
"data_description": {
|
||||
"password": "[%key:component::bosch_alarm::config::step::auth::data_description::password%]",
|
||||
"installer_code": "[%key:component::bosch_alarm::config::step::auth::data_description::installer_code%]",
|
||||
"user_code": "[%key:component::bosch_alarm::config::step::auth::data_description::user_code%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -30,7 +42,16 @@
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"cannot_connect": {
|
||||
"message": "Could not connect to panel."
|
||||
},
|
||||
"authentication_failed": {
|
||||
"message": "Incorrect credentials for panel."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -74,7 +74,7 @@
|
||||
},
|
||||
"get_events": {
|
||||
"name": "Get events",
|
||||
"description": "Get events on a calendar within a time range.",
|
||||
"description": "Retrieves events on a calendar within a time range.",
|
||||
"fields": {
|
||||
"start_date_time": {
|
||||
"name": "Start time",
|
||||
|
@@ -127,7 +127,11 @@ class CloudOAuth2Implementation(config_entry_oauth2_flow.AbstractOAuth2Implement
|
||||
flow_id=flow_id, user_input=tokens
|
||||
)
|
||||
|
||||
self.hass.async_create_task(await_tokens())
|
||||
# It's a background task because it should be cancelled on shutdown and there's nothing else
|
||||
# we can do in such case. There's also no need to wait for this during setup.
|
||||
self.hass.async_create_background_task(
|
||||
await_tokens(), name="Awaiting OAuth tokens"
|
||||
)
|
||||
|
||||
return authorize_url
|
||||
|
||||
|
@@ -162,7 +162,7 @@ class ComelitHumidifierEntity(CoordinatorEntity[ComelitSerialBridge], Humidifier
|
||||
|
||||
async def async_set_humidity(self, humidity: int) -> None:
|
||||
"""Set new target humidity."""
|
||||
if self.mode == HumidifierComelitMode.OFF:
|
||||
if not self._attr_is_on:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="humidity_while_off",
|
||||
@@ -190,9 +190,13 @@ class ComelitHumidifierEntity(CoordinatorEntity[ComelitSerialBridge], Humidifier
|
||||
await self.coordinator.api.set_humidity_status(
|
||||
self._device.index, self._set_command
|
||||
)
|
||||
self._attr_is_on = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off."""
|
||||
await self.coordinator.api.set_humidity_status(
|
||||
self._device.index, HumidifierComelitCommand.OFF
|
||||
)
|
||||
self._attr_is_on = False
|
||||
self.async_write_ha_state()
|
||||
|
@@ -52,7 +52,9 @@
|
||||
"rest": "Rest",
|
||||
"sabotated": "Sabotated"
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"humidifier": {
|
||||
"humidifier": {
|
||||
"name": "Humidifier"
|
||||
},
|
||||
|
@@ -21,6 +21,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
from homeassistant.util.ssl import client_context_no_verify
|
||||
|
||||
from .const import KEY_MAC, TIMEOUT
|
||||
from .coordinator import DaikinConfigEntry, DaikinCoordinator
|
||||
@@ -48,6 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: DaikinConfigEntry) -> bo
|
||||
key=entry.data.get(CONF_API_KEY),
|
||||
uuid=entry.data.get(CONF_UUID),
|
||||
password=entry.data.get(CONF_PASSWORD),
|
||||
ssl_context=client_context_no_verify(),
|
||||
)
|
||||
_LOGGER.debug("Connection to %s successful", host)
|
||||
except TimeoutError as err:
|
||||
|
@@ -6,6 +6,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/daikin",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pydaikin"],
|
||||
"requirements": ["pydaikin==2.14.1"],
|
||||
"requirements": ["pydaikin==2.15.0"],
|
||||
"zeroconf": ["_dkapi._tcp.local."]
|
||||
}
|
||||
|
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["dsmr_parser"],
|
||||
"requirements": ["dsmr-parser==1.4.2"]
|
||||
"requirements": ["dsmr-parser==1.4.3"]
|
||||
}
|
||||
|
@@ -51,8 +51,8 @@
|
||||
"electricity_active_tariff": {
|
||||
"name": "Active tariff",
|
||||
"state": {
|
||||
"low": "Low",
|
||||
"normal": "Normal"
|
||||
"low": "[%key:common::state::low%]",
|
||||
"normal": "[%key:common::state::normal%]"
|
||||
}
|
||||
},
|
||||
"electricity_delivered_tariff_1": {
|
||||
|
@@ -140,8 +140,8 @@
|
||||
"electricity_tariff": {
|
||||
"name": "Electricity tariff",
|
||||
"state": {
|
||||
"low": "Low",
|
||||
"high": "High"
|
||||
"low": "[%key:common::state::low%]",
|
||||
"high": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"power_failure_count": {
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
||||
"requirements": ["py-sucks==0.9.10", "deebot-client==12.4.0"]
|
||||
"requirements": ["py-sucks==0.9.10", "deebot-client==12.5.0"]
|
||||
}
|
||||
|
@@ -176,9 +176,9 @@
|
||||
"water_amount": {
|
||||
"name": "Water flow level",
|
||||
"state": {
|
||||
"high": "High",
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"medium": "[%key:common::state::medium%]",
|
||||
"ultrahigh": "Ultrahigh"
|
||||
}
|
||||
},
|
||||
|
@@ -9,7 +9,7 @@ from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from .const import DOMAIN
|
||||
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
|
||||
|
||||
PLATFORMS = [Platform.CLIMATE, Platform.LIGHT]
|
||||
PLATFORMS = [Platform.CLIMATE, Platform.LIGHT, Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
18
homeassistant/components/eheimdigital/icons.json
Normal file
18
homeassistant/components/eheimdigital/icons.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"current_speed": {
|
||||
"default": "mdi:pump"
|
||||
},
|
||||
"service_hours": {
|
||||
"default": "mdi:wrench-clock"
|
||||
},
|
||||
"error_code": {
|
||||
"default": "mdi:alert-octagon",
|
||||
"state": {
|
||||
"no_error": "mdi:check-circle"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
114
homeassistant/components/eheimdigital/sensor.py
Normal file
114
homeassistant/components/eheimdigital/sensor.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""EHEIM Digital sensors."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Generic, TypeVar, override
|
||||
|
||||
from eheimdigital.classic_vario import EheimDigitalClassicVario
|
||||
from eheimdigital.device import EheimDigitalDevice
|
||||
from eheimdigital.types import FilterErrorCode
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
||||
from homeassistant.components.sensor.const import SensorDeviceClass
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTime
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
|
||||
from .entity import EheimDigitalEntity
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
_DeviceT_co = TypeVar("_DeviceT_co", bound=EheimDigitalDevice, covariant=True)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class EheimDigitalSensorDescription(SensorEntityDescription, Generic[_DeviceT_co]):
|
||||
"""Class describing EHEIM Digital sensor entities."""
|
||||
|
||||
value_fn: Callable[[_DeviceT_co], float | str | None]
|
||||
|
||||
|
||||
CLASSICVARIO_DESCRIPTIONS: tuple[
|
||||
EheimDigitalSensorDescription[EheimDigitalClassicVario], ...
|
||||
] = (
|
||||
EheimDigitalSensorDescription[EheimDigitalClassicVario](
|
||||
key="current_speed",
|
||||
translation_key="current_speed",
|
||||
value_fn=lambda device: device.current_speed,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
EheimDigitalSensorDescription[EheimDigitalClassicVario](
|
||||
key="service_hours",
|
||||
translation_key="service_hours",
|
||||
value_fn=lambda device: device.service_hours,
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
native_unit_of_measurement=UnitOfTime.HOURS,
|
||||
suggested_unit_of_measurement=UnitOfTime.DAYS,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
EheimDigitalSensorDescription[EheimDigitalClassicVario](
|
||||
key="error_code",
|
||||
translation_key="error_code",
|
||||
value_fn=(
|
||||
lambda device: device.error_code.name.lower()
|
||||
if device.error_code is not None
|
||||
else None
|
||||
),
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=[name.lower() for name in FilterErrorCode._member_names_],
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: EheimDigitalConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the callbacks for the coordinator so lights can be added as devices are found."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
def async_setup_device_entities(
|
||||
device_address: dict[str, EheimDigitalDevice],
|
||||
) -> None:
|
||||
"""Set up the light entities for one or multiple devices."""
|
||||
entities: list[EheimDigitalSensor[EheimDigitalDevice]] = []
|
||||
for device in device_address.values():
|
||||
if isinstance(device, EheimDigitalClassicVario):
|
||||
entities += [
|
||||
EheimDigitalSensor[EheimDigitalClassicVario](
|
||||
coordinator, device, description
|
||||
)
|
||||
for description in CLASSICVARIO_DESCRIPTIONS
|
||||
]
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
coordinator.add_platform_callback(async_setup_device_entities)
|
||||
async_setup_device_entities(coordinator.hub.devices)
|
||||
|
||||
|
||||
class EheimDigitalSensor(
|
||||
EheimDigitalEntity[_DeviceT_co], SensorEntity, Generic[_DeviceT_co]
|
||||
):
|
||||
"""Represent a EHEIM Digital sensor entity."""
|
||||
|
||||
entity_description: EheimDigitalSensorDescription[_DeviceT_co]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: EheimDigitalUpdateCoordinator,
|
||||
device: _DeviceT_co,
|
||||
description: EheimDigitalSensorDescription[_DeviceT_co],
|
||||
) -> None:
|
||||
"""Initialize an EHEIM Digital number entity."""
|
||||
super().__init__(coordinator, device)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{self._device_address}_{description.key}"
|
||||
|
||||
@override
|
||||
def _async_update_attrs(self) -> None:
|
||||
self._attr_native_value = self.entity_description.value_fn(self._device)
|
@@ -46,6 +46,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"current_speed": {
|
||||
"name": "Current speed"
|
||||
},
|
||||
"service_hours": {
|
||||
"name": "Remaining hours until service"
|
||||
},
|
||||
"error_code": {
|
||||
"name": "Error code",
|
||||
"state": {
|
||||
"no_error": "No error",
|
||||
"rotor_stuck": "Rotor stuck",
|
||||
"air_in_filter": "Air in filter"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -66,16 +66,19 @@ async def _get_fixture_collection(envoy: Envoy, serial: str) -> dict[str, Any]:
|
||||
]
|
||||
|
||||
for end_point in end_points:
|
||||
response = await envoy.request(end_point)
|
||||
fixture_data[end_point] = response.text.replace("\n", "").replace(
|
||||
serial, CLEAN_TEXT
|
||||
)
|
||||
fixture_data[f"{end_point}_log"] = json_dumps(
|
||||
{
|
||||
"headers": dict(response.headers.items()),
|
||||
"code": response.status_code,
|
||||
}
|
||||
)
|
||||
try:
|
||||
response = await envoy.request(end_point)
|
||||
fixture_data[end_point] = response.text.replace("\n", "").replace(
|
||||
serial, CLEAN_TEXT
|
||||
)
|
||||
fixture_data[f"{end_point}_log"] = json_dumps(
|
||||
{
|
||||
"headers": dict(response.headers.items()),
|
||||
"code": response.status_code,
|
||||
}
|
||||
)
|
||||
except EnvoyError as err:
|
||||
fixture_data[f"{end_point}_log"] = {"Error": repr(err)}
|
||||
return fixture_data
|
||||
|
||||
|
||||
@@ -160,10 +163,7 @@ async def async_get_config_entry_diagnostics(
|
||||
|
||||
fixture_data: dict[str, Any] = {}
|
||||
if entry.options.get(OPTION_DIAGNOSTICS_INCLUDE_FIXTURES, False):
|
||||
try:
|
||||
fixture_data = await _get_fixture_collection(envoy=envoy, serial=old_serial)
|
||||
except EnvoyError as err:
|
||||
fixture_data["Error"] = repr(err)
|
||||
fixture_data = await _get_fixture_collection(envoy=envoy, serial=old_serial)
|
||||
|
||||
diagnostic_data: dict[str, Any] = {
|
||||
"config_entry": async_redact_data(entry.as_dict(), TO_REDACT),
|
||||
|
@@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyenphase"],
|
||||
"requirements": ["pyenphase==1.25.1"],
|
||||
"requirements": ["pyenphase==1.25.5"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_enphase-envoy._tcp.local."
|
||||
|
@@ -22,5 +22,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["eq3btsmart"],
|
||||
"requirements": ["eq3btsmart==1.4.1", "bleak-esphome==2.12.0"]
|
||||
"requirements": ["eq3btsmart==1.4.1", "bleak-esphome==2.13.1"]
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ from aioesphomeapi import (
|
||||
APIConnectionError,
|
||||
APIVersion,
|
||||
DeviceInfo as EsphomeDeviceInfo,
|
||||
EncryptionHelloAPIError,
|
||||
EncryptionPlaintextAPIError,
|
||||
EntityInfo,
|
||||
HomeassistantServiceCall,
|
||||
InvalidAuthAPIError,
|
||||
@@ -571,7 +571,7 @@ class ESPHomeManager:
|
||||
if isinstance(
|
||||
err,
|
||||
(
|
||||
EncryptionHelloAPIError,
|
||||
EncryptionPlaintextAPIError,
|
||||
RequiresEncryptionAPIError,
|
||||
InvalidEncryptionKeyAPIError,
|
||||
InvalidAuthAPIError,
|
||||
|
@@ -16,9 +16,9 @@
|
||||
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
|
||||
"mqtt": ["esphome/discover/#"],
|
||||
"requirements": [
|
||||
"aioesphomeapi==29.8.0",
|
||||
"aioesphomeapi==29.9.0",
|
||||
"esphome-dashboard-api==1.2.3",
|
||||
"bleak-esphome==2.12.0"
|
||||
"bleak-esphome==2.13.1"
|
||||
],
|
||||
"zeroconf": ["_esphomelib._tcp.local."]
|
||||
}
|
||||
|
@@ -152,7 +152,7 @@ class EvoZone(EvoChild, EvoClimateEntity):
|
||||
super().__init__(coordinator, evo_device)
|
||||
self._evo_id = evo_device.id
|
||||
|
||||
if evo_device.model.startswith("VisionProWifi"):
|
||||
if evo_device.id == evo_device.tcs.id:
|
||||
# this system does not have a distinct ID for the zone
|
||||
self._attr_unique_id = f"{evo_device.id}z"
|
||||
else:
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["evohome", "evohomeasync", "evohomeasync2"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["evohome-async==1.0.4"]
|
||||
"requirements": ["evohome-async==1.0.5"]
|
||||
}
|
||||
|
@@ -301,6 +301,7 @@ class FibaroController:
|
||||
device.ha_id = (
|
||||
f"{slugify(room_name)}_{slugify(device.name)}_{device.fibaro_id}"
|
||||
)
|
||||
platform = None
|
||||
if device.enabled and (not device.is_plugin or self._import_plugins):
|
||||
platform = self._map_device_to_platform(device)
|
||||
if platform is None:
|
||||
|
@@ -53,5 +53,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/flux_led",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["flux_led"],
|
||||
"requirements": ["flux-led==1.1.3"]
|
||||
"requirements": ["flux-led==1.2.0"]
|
||||
}
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/forecast_solar",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["forecast-solar==4.0.0"]
|
||||
"requirements": ["forecast-solar==4.1.0"]
|
||||
}
|
||||
|
@@ -20,6 +20,9 @@ from .entity import FritzBoxBaseCoordinatorEntity, FritzEntityDescription
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class FritzBinarySensorEntityDescription(
|
||||
|
@@ -31,6 +31,9 @@ from .entity import FritzDeviceBase
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Set a sane value to avoid too many updates
|
||||
PARALLEL_UPDATES = 5
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class FritzButtonDescription(ButtonEntityDescription):
|
||||
|
@@ -22,6 +22,9 @@ from .entity import FritzDeviceBase
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
@@ -18,6 +18,9 @@ from .entity import FritzBoxBaseEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
@@ -14,9 +14,7 @@ rules:
|
||||
docs-actions: done
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions:
|
||||
status: todo
|
||||
comment: include the proper docs snippet
|
||||
docs-removal-instructions: done
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name:
|
||||
@@ -31,15 +29,11 @@ rules:
|
||||
action-exceptions: done
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters: done
|
||||
docs-installation-parameters:
|
||||
status: todo
|
||||
comment: add the proper configuration_basic block
|
||||
docs-installation-parameters: done
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
log-when-unavailable: done
|
||||
parallel-updates:
|
||||
status: todo
|
||||
comment: not set at the moment, we use a coordinator
|
||||
parallel-updates: done
|
||||
reauthentication-flow: done
|
||||
test-coverage:
|
||||
status: todo
|
||||
@@ -50,7 +44,7 @@ rules:
|
||||
diagnostics: done
|
||||
discovery-update-info: todo
|
||||
discovery: done
|
||||
docs-data-update: todo
|
||||
docs-data-update: done
|
||||
docs-examples: done
|
||||
docs-known-limitations:
|
||||
status: exempt
|
||||
|
@@ -32,6 +32,9 @@ from .entity import FritzBoxBaseCoordinatorEntity, FritzEntityDescription
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
def _uptime_calculation(seconds_uptime: float, last_value: datetime | None) -> datetime:
|
||||
"""Calculate uptime with deviation."""
|
||||
|
@@ -38,6 +38,9 @@ from .entity import FritzBoxBaseEntity, FritzDeviceBase
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Set a sane value to avoid too many updates
|
||||
PARALLEL_UPDATES = 5
|
||||
|
||||
|
||||
async def _async_deflection_entities_list(
|
||||
avm_wrapper: AvmWrapper, device_friendly_name: str
|
||||
|
@@ -20,6 +20,9 @@ from .entity import FritzBoxBaseCoordinatorEntity, FritzEntityDescription
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Set a sane value to avoid too many updates
|
||||
PARALLEL_UPDATES = 5
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class FritzUpdateEntityDescription(UpdateEntityDescription, FritzEntityDescription):
|
||||
|
@@ -137,6 +137,7 @@ SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = (
|
||||
key="battery",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
suitable=lambda device: device.battery_level is not None,
|
||||
native_value=lambda device: device.battery_level,
|
||||
|
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20250401.0"]
|
||||
"requirements": ["home-assistant-frontend==20250404.0"]
|
||||
}
|
||||
|
@@ -79,9 +79,9 @@
|
||||
"state": {
|
||||
"no_data": "No data",
|
||||
"too_low": "Too low",
|
||||
"low": "Low",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"perfect": "Perfect",
|
||||
"high": "High",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"too_high": "Too high"
|
||||
}
|
||||
},
|
||||
@@ -90,9 +90,9 @@
|
||||
"state": {
|
||||
"no_data": "[%key:component::fyta::entity::sensor::temperature_status::state::no_data%]",
|
||||
"too_low": "[%key:component::fyta::entity::sensor::temperature_status::state::too_low%]",
|
||||
"low": "[%key:component::fyta::entity::sensor::temperature_status::state::low%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"perfect": "[%key:component::fyta::entity::sensor::temperature_status::state::perfect%]",
|
||||
"high": "[%key:component::fyta::entity::sensor::temperature_status::state::high%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"too_high": "[%key:component::fyta::entity::sensor::temperature_status::state::too_high%]"
|
||||
}
|
||||
},
|
||||
@@ -101,9 +101,9 @@
|
||||
"state": {
|
||||
"no_data": "[%key:component::fyta::entity::sensor::temperature_status::state::no_data%]",
|
||||
"too_low": "[%key:component::fyta::entity::sensor::temperature_status::state::too_low%]",
|
||||
"low": "[%key:component::fyta::entity::sensor::temperature_status::state::low%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"perfect": "[%key:component::fyta::entity::sensor::temperature_status::state::perfect%]",
|
||||
"high": "[%key:component::fyta::entity::sensor::temperature_status::state::high%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"too_high": "[%key:component::fyta::entity::sensor::temperature_status::state::too_high%]"
|
||||
}
|
||||
},
|
||||
@@ -112,9 +112,9 @@
|
||||
"state": {
|
||||
"no_data": "[%key:component::fyta::entity::sensor::temperature_status::state::no_data%]",
|
||||
"too_low": "[%key:component::fyta::entity::sensor::temperature_status::state::too_low%]",
|
||||
"low": "[%key:component::fyta::entity::sensor::temperature_status::state::low%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"perfect": "[%key:component::fyta::entity::sensor::temperature_status::state::perfect%]",
|
||||
"high": "[%key:component::fyta::entity::sensor::temperature_status::state::high%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"too_high": "[%key:component::fyta::entity::sensor::temperature_status::state::too_high%]"
|
||||
}
|
||||
},
|
||||
@@ -123,9 +123,9 @@
|
||||
"state": {
|
||||
"no_data": "[%key:component::fyta::entity::sensor::temperature_status::state::no_data%]",
|
||||
"too_low": "[%key:component::fyta::entity::sensor::temperature_status::state::too_low%]",
|
||||
"low": "[%key:component::fyta::entity::sensor::temperature_status::state::low%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"perfect": "[%key:component::fyta::entity::sensor::temperature_status::state::perfect%]",
|
||||
"high": "[%key:component::fyta::entity::sensor::temperature_status::state::high%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"too_high": "[%key:component::fyta::entity::sensor::temperature_status::state::too_high%]"
|
||||
}
|
||||
},
|
||||
|
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/google",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["googleapiclient"],
|
||||
"requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.0.3"]
|
||||
"requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.1.0"]
|
||||
}
|
||||
|
@@ -179,28 +179,30 @@ class GoogleGenerativeAIOptionsFlow(OptionsFlow):
|
||||
) -> ConfigFlowResult:
|
||||
"""Manage the options."""
|
||||
options: dict[str, Any] | MappingProxyType[str, Any] = self.config_entry.options
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
if user_input[CONF_RECOMMENDED] == self.last_rendered_recommended:
|
||||
if user_input[CONF_LLM_HASS_API] == "none":
|
||||
user_input.pop(CONF_LLM_HASS_API)
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
if not (
|
||||
user_input.get(CONF_LLM_HASS_API, "none") != "none"
|
||||
and user_input.get(CONF_USE_GOOGLE_SEARCH_TOOL, False) is True
|
||||
):
|
||||
# Don't allow to save options that enable the Google Seearch tool with an Assist API
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
errors[CONF_USE_GOOGLE_SEARCH_TOOL] = "invalid_google_search_option"
|
||||
|
||||
# Re-render the options again, now with the recommended options shown/hidden
|
||||
self.last_rendered_recommended = user_input[CONF_RECOMMENDED]
|
||||
|
||||
options = {
|
||||
CONF_RECOMMENDED: user_input[CONF_RECOMMENDED],
|
||||
CONF_PROMPT: user_input[CONF_PROMPT],
|
||||
CONF_LLM_HASS_API: user_input[CONF_LLM_HASS_API],
|
||||
}
|
||||
options = user_input
|
||||
|
||||
schema = await google_generative_ai_config_option_schema(
|
||||
self.hass, options, self._genai_client
|
||||
)
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=vol.Schema(schema),
|
||||
step_id="init", data_schema=vol.Schema(schema), errors=errors
|
||||
)
|
||||
|
||||
|
||||
|
@@ -40,9 +40,13 @@
|
||||
"enable_google_search_tool": "Enable Google Search tool"
|
||||
},
|
||||
"data_description": {
|
||||
"prompt": "Instruct how the LLM should respond. This can be a template."
|
||||
"prompt": "Instruct how the LLM should respond. This can be a template.",
|
||||
"enable_google_search_tool": "Only works with \"No control\" in the \"Control Home Assistant\" setting. See docs for a workaround using it with \"Assist\"."
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_google_search_option": "Google Search cannot be enabled alongside any Assist capability, this can only be used when Assist is set to \"No control\"."
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
@@ -265,6 +265,11 @@
|
||||
"version_latest": {
|
||||
"name": "Newest version"
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"update": {
|
||||
"name": "[%key:component::update::title%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
@@ -39,7 +39,7 @@ from .entity import (
|
||||
from .update_helper import update_addon, update_core
|
||||
|
||||
ENTITY_DESCRIPTION = UpdateEntityDescription(
|
||||
name="Update",
|
||||
translation_key="update",
|
||||
key=ATTR_VERSION_LATEST,
|
||||
)
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
ATTR_PASSWORD = "password"
|
||||
ATTR_USERNAME = "username"
|
||||
ATTR_DESTINATION_POSITION = "destination_position"
|
||||
ATTR_QUEUE_IDS = "queue_ids"
|
||||
DOMAIN = "heos"
|
||||
ENTRY_TITLE = "HEOS System"
|
||||
@@ -9,6 +10,7 @@ SERVICE_GET_QUEUE = "get_queue"
|
||||
SERVICE_GROUP_VOLUME_SET = "group_volume_set"
|
||||
SERVICE_GROUP_VOLUME_DOWN = "group_volume_down"
|
||||
SERVICE_GROUP_VOLUME_UP = "group_volume_up"
|
||||
SERVICE_MOVE_QUEUE_ITEM = "move_queue_item"
|
||||
SERVICE_REMOVE_FROM_QUEUE = "remove_from_queue"
|
||||
SERVICE_SIGN_IN = "sign_in"
|
||||
SERVICE_SIGN_OUT = "sign_out"
|
||||
|
@@ -6,6 +6,9 @@
|
||||
"remove_from_queue": {
|
||||
"service": "mdi:playlist-remove"
|
||||
},
|
||||
"move_queue_item": {
|
||||
"service": "mdi:playlist-edit"
|
||||
},
|
||||
"group_volume_set": {
|
||||
"service": "mdi:volume-medium"
|
||||
},
|
||||
|
@@ -479,6 +479,13 @@ class HeosMediaPlayer(CoordinatorEntity[HeosCoordinator], MediaPlayerEntity):
|
||||
"""Remove items from the queue."""
|
||||
await self._player.remove_from_queue(queue_ids)
|
||||
|
||||
@catch_action_error("move queue item")
|
||||
async def async_move_queue_item(
|
||||
self, queue_ids: list[int], destination_position: int
|
||||
) -> None:
|
||||
"""Move items in the queue."""
|
||||
await self._player.move_queue_item(queue_ids, destination_position)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if the device is available."""
|
||||
|
@@ -19,6 +19,7 @@ from homeassistant.helpers import (
|
||||
from homeassistant.helpers.typing import VolDictType, VolSchemaType
|
||||
|
||||
from .const import (
|
||||
ATTR_DESTINATION_POSITION,
|
||||
ATTR_PASSWORD,
|
||||
ATTR_QUEUE_IDS,
|
||||
ATTR_USERNAME,
|
||||
@@ -27,6 +28,7 @@ from .const import (
|
||||
SERVICE_GROUP_VOLUME_DOWN,
|
||||
SERVICE_GROUP_VOLUME_SET,
|
||||
SERVICE_GROUP_VOLUME_UP,
|
||||
SERVICE_MOVE_QUEUE_ITEM,
|
||||
SERVICE_REMOVE_FROM_QUEUE,
|
||||
SERVICE_SIGN_IN,
|
||||
SERVICE_SIGN_OUT,
|
||||
@@ -87,6 +89,16 @@ REMOVE_FROM_QUEUE_SCHEMA: Final[VolDictType] = {
|
||||
GROUP_VOLUME_SET_SCHEMA: Final[VolDictType] = {
|
||||
vol.Required(ATTR_MEDIA_VOLUME_LEVEL): cv.small_float
|
||||
}
|
||||
MOVE_QEUEUE_ITEM_SCHEMA: Final[VolDictType] = {
|
||||
vol.Required(ATTR_QUEUE_IDS): vol.All(
|
||||
cv.ensure_list,
|
||||
[vol.All(vol.Coerce(int), vol.Range(min=1, max=1000))],
|
||||
vol.Unique(),
|
||||
),
|
||||
vol.Required(ATTR_DESTINATION_POSITION): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=1, max=1000)
|
||||
),
|
||||
}
|
||||
|
||||
MEDIA_PLAYER_ENTITY_SERVICES: Final = (
|
||||
# Player queue services
|
||||
@@ -96,6 +108,9 @@ MEDIA_PLAYER_ENTITY_SERVICES: Final = (
|
||||
EntityServiceDescription(
|
||||
SERVICE_REMOVE_FROM_QUEUE, "async_remove_from_queue", REMOVE_FROM_QUEUE_SCHEMA
|
||||
),
|
||||
EntityServiceDescription(
|
||||
SERVICE_MOVE_QUEUE_ITEM, "async_move_queue_item", MOVE_QEUEUE_ITEM_SCHEMA
|
||||
),
|
||||
# Group volume services
|
||||
EntityServiceDescription(
|
||||
SERVICE_GROUP_VOLUME_SET,
|
||||
|
@@ -17,6 +17,26 @@ remove_from_queue:
|
||||
multiple: true
|
||||
type: number
|
||||
|
||||
move_queue_item:
|
||||
target:
|
||||
entity:
|
||||
integration: heos
|
||||
domain: media_player
|
||||
fields:
|
||||
queue_ids:
|
||||
required: true
|
||||
selector:
|
||||
text:
|
||||
multiple: true
|
||||
type: number
|
||||
destination_position:
|
||||
required: true
|
||||
selector:
|
||||
number:
|
||||
min: 1
|
||||
max: 1000
|
||||
step: 1
|
||||
|
||||
group_volume_set:
|
||||
target:
|
||||
entity:
|
||||
|
@@ -100,6 +100,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"move_queue_item": {
|
||||
"name": "Move queue item",
|
||||
"description": "Move one or more items within the play queue.",
|
||||
"fields": {
|
||||
"queue_ids": {
|
||||
"name": "Queue IDs",
|
||||
"description": "The IDs (indexes) of the items in the queue to move."
|
||||
},
|
||||
"destination_position": {
|
||||
"name": "Destination position",
|
||||
"description": "The position index in the queue to move the items to."
|
||||
}
|
||||
}
|
||||
},
|
||||
"group_volume_down": {
|
||||
"name": "Turn down group volume",
|
||||
"description": "Turns down the group volume."
|
||||
|
@@ -34,9 +34,9 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_username": "Failed to sign into Hive. Your email address is not recognised.",
|
||||
"invalid_password": "Failed to sign into Hive. Incorrect password, please try again.",
|
||||
"invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.",
|
||||
"invalid_username": "Failed to sign in to Hive. Your email address is not recognised.",
|
||||
"invalid_password": "Failed to sign in to Hive. Incorrect password, please try again.",
|
||||
"invalid_code": "Failed to sign in to Hive. Your two-factor authentication code was incorrect.",
|
||||
"no_internet_available": "An Internet connection is required to connect to Hive.",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
|
@@ -74,6 +74,19 @@ class HomeConnectApplianceData:
|
||||
self.settings.update(other.settings)
|
||||
self.status.update(other.status)
|
||||
|
||||
@classmethod
|
||||
def empty(cls, appliance: HomeAppliance) -> HomeConnectApplianceData:
|
||||
"""Return empty data."""
|
||||
return cls(
|
||||
commands=set(),
|
||||
events={},
|
||||
info=appliance,
|
||||
options={},
|
||||
programs=[],
|
||||
settings={},
|
||||
status={},
|
||||
)
|
||||
|
||||
|
||||
class HomeConnectCoordinator(
|
||||
DataUpdateCoordinator[dict[str, HomeConnectApplianceData]]
|
||||
@@ -362,15 +375,7 @@ class HomeConnectCoordinator(
|
||||
model=appliance.vib,
|
||||
)
|
||||
if appliance.ha_id not in self.data:
|
||||
self.data[appliance.ha_id] = HomeConnectApplianceData(
|
||||
commands=set(),
|
||||
events={},
|
||||
info=appliance,
|
||||
options={},
|
||||
programs=[],
|
||||
settings={},
|
||||
status={},
|
||||
)
|
||||
self.data[appliance.ha_id] = HomeConnectApplianceData.empty(appliance)
|
||||
else:
|
||||
self.data[appliance.ha_id].info.connected = appliance.connected
|
||||
old_appliances.remove(appliance.ha_id)
|
||||
@@ -406,6 +411,15 @@ class HomeConnectCoordinator(
|
||||
name=appliance.name,
|
||||
model=appliance.vib,
|
||||
)
|
||||
if not appliance.connected:
|
||||
_LOGGER.debug(
|
||||
"Appliance %s is not connected, skipping data fetch",
|
||||
appliance.ha_id,
|
||||
)
|
||||
if appliance_data_to_update:
|
||||
appliance_data_to_update.info.connected = False
|
||||
return appliance_data_to_update
|
||||
return HomeConnectApplianceData.empty(appliance)
|
||||
try:
|
||||
settings = {
|
||||
setting.key: setting
|
||||
|
@@ -7,6 +7,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/home_connect",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aiohomeconnect"],
|
||||
"requirements": ["aiohomeconnect==0.16.3"],
|
||||
"requirements": ["aiohomeconnect==0.17.0"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
@@ -487,9 +487,9 @@
|
||||
},
|
||||
"warming_level": {
|
||||
"options": {
|
||||
"cooking_oven_enum_type_warming_level_low": "Low",
|
||||
"cooking_oven_enum_type_warming_level_medium": "Medium",
|
||||
"cooking_oven_enum_type_warming_level_high": "High"
|
||||
"cooking_oven_enum_type_warming_level_low": "[%key:common::state::low%]",
|
||||
"cooking_oven_enum_type_warming_level_medium": "[%key:common::state::medium%]",
|
||||
"cooking_oven_enum_type_warming_level_high": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"washer_temperature": {
|
||||
@@ -522,9 +522,9 @@
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m_1400": "1400 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m_1600": "1600 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_off": "[%key:common::state::off%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_low": "Low",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_medium": "Medium",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_high": "High"
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_low": "[%key:common::state::low%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_medium": "[%key:common::state::medium%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_high": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"vario_perfect": {
|
||||
@@ -1468,9 +1468,9 @@
|
||||
"warming_level": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::cooking_oven_option_warming_level::name%]",
|
||||
"state": {
|
||||
"cooking_oven_enum_type_warming_level_low": "[%key:component::home_connect::selector::warming_level::options::cooking_oven_enum_type_warming_level_low%]",
|
||||
"cooking_oven_enum_type_warming_level_medium": "[%key:component::home_connect::selector::warming_level::options::cooking_oven_enum_type_warming_level_medium%]",
|
||||
"cooking_oven_enum_type_warming_level_high": "[%key:component::home_connect::selector::warming_level::options::cooking_oven_enum_type_warming_level_high%]"
|
||||
"cooking_oven_enum_type_warming_level_low": "[%key:common::state::low%]",
|
||||
"cooking_oven_enum_type_warming_level_medium": "[%key:common::state::medium%]",
|
||||
"cooking_oven_enum_type_warming_level_high": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"washer_temperature": {
|
||||
@@ -1505,9 +1505,9 @@
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m_1400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1400%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m_1600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1600%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_off": "[%key:common::state::off%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_low": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_low%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_medium": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_medium%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_high": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_high%]"
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_low": "[%key:common::state::low%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_medium": "[%key:common::state::medium%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_high": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"vario_perfect": {
|
||||
|
@@ -71,7 +71,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
# Postpone loading the config entry if the device is missing
|
||||
device_path = entry.data[DEVICE]
|
||||
if not await hass.async_add_executor_job(os.path.exists, device_path):
|
||||
raise ConfigEntryNotReady
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="device_disconnected",
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, ["update"])
|
||||
|
||||
|
@@ -5,17 +5,21 @@ from __future__ import annotations
|
||||
from homeassistant.components.hardware.models import HardwareInfo, USBInfo
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
from .config_flow import HomeAssistantSkyConnectConfigFlow
|
||||
from .const import DOMAIN
|
||||
from .util import get_hardware_variant
|
||||
|
||||
DOCUMENTATION_URL = "https://skyconnect.home-assistant.io/documentation/"
|
||||
EXPECTED_ENTRY_VERSION = (
|
||||
HomeAssistantSkyConnectConfigFlow.VERSION,
|
||||
HomeAssistantSkyConnectConfigFlow.MINOR_VERSION,
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_info(hass: HomeAssistant) -> list[HardwareInfo]:
|
||||
"""Return board info."""
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
|
||||
return [
|
||||
HardwareInfo(
|
||||
board=None,
|
||||
@@ -31,4 +35,6 @@ def async_info(hass: HomeAssistant) -> list[HardwareInfo]:
|
||||
url=DOCUMENTATION_URL,
|
||||
)
|
||||
for entry in entries
|
||||
# Ignore unmigrated config entries in the hardware page
|
||||
if (entry.version, entry.minor_version) == EXPECTED_ENTRY_VERSION
|
||||
]
|
||||
|
@@ -195,5 +195,10 @@
|
||||
"run_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::run_zigbee_flasher_addon%]",
|
||||
"uninstall_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::uninstall_zigbee_flasher_addon%]"
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"device_disconnected": {
|
||||
"message": "The device is not plugged in"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -659,13 +659,7 @@ class HomeKitClimateEntity(HomeKitBaseClimateEntity):
|
||||
# e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit.
|
||||
# Can be 0 - 2 (Off, Heat, Cool)
|
||||
|
||||
# If the HVAC is switched off, it must be idle
|
||||
# This works around a bug in some devices (like Eve radiator valves) that
|
||||
# return they are heating when they are not.
|
||||
target = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET)
|
||||
if target == HeatingCoolingTargetValues.OFF:
|
||||
return HVACAction.IDLE
|
||||
|
||||
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_CURRENT)
|
||||
current_hass_value = CURRENT_MODE_HOMEKIT_TO_HASS.get(value)
|
||||
|
||||
@@ -679,6 +673,12 @@ class HomeKitClimateEntity(HomeKitBaseClimateEntity):
|
||||
):
|
||||
return HVACAction.FAN
|
||||
|
||||
# If the HVAC is switched off, it must be idle
|
||||
# This works around a bug in some devices (like Eve radiator valves) that
|
||||
# return they are heating when they are not.
|
||||
if target == HeatingCoolingTargetValues.OFF:
|
||||
return HVACAction.IDLE
|
||||
|
||||
return current_hass_value
|
||||
|
||||
@property
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Please enter the credentials used to log into mytotalconnectcomfort.com.",
|
||||
"description": "Please enter the credentials used to log in to mytotalconnectcomfort.com.",
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
|
@@ -3,25 +3,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Final
|
||||
|
||||
from aiohttp import hdrs
|
||||
from aiohttp.web import Application, Request, StreamResponse, middleware
|
||||
from aiohttp.web_exceptions import HTTPException
|
||||
from multidict import CIMultiDict, istr
|
||||
|
||||
from homeassistant.core import callback
|
||||
|
||||
REFERRER_POLICY: Final[istr] = istr("Referrer-Policy")
|
||||
X_CONTENT_TYPE_OPTIONS: Final[istr] = istr("X-Content-Type-Options")
|
||||
X_FRAME_OPTIONS: Final[istr] = istr("X-Frame-Options")
|
||||
|
||||
|
||||
@callback
|
||||
def setup_headers(app: Application, use_x_frame_options: bool) -> None:
|
||||
"""Create headers middleware for the app."""
|
||||
|
||||
added_headers = {
|
||||
"Referrer-Policy": "no-referrer",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"Server": "", # Empty server header, to prevent aiohttp of setting one.
|
||||
}
|
||||
added_headers = CIMultiDict(
|
||||
{
|
||||
REFERRER_POLICY: "no-referrer",
|
||||
X_CONTENT_TYPE_OPTIONS: "nosniff",
|
||||
hdrs.SERVER: "", # Empty server header, to prevent aiohttp of setting one.
|
||||
}
|
||||
)
|
||||
|
||||
if use_x_frame_options:
|
||||
added_headers["X-Frame-Options"] = "SAMEORIGIN"
|
||||
added_headers[X_FRAME_OPTIONS] = "SAMEORIGIN"
|
||||
|
||||
@middleware
|
||||
async def headers_middleware(
|
||||
|
@@ -9,7 +9,7 @@
|
||||
"requirements": [
|
||||
"huawei-lte-api==1.10.0",
|
||||
"stringcase==1.2.0",
|
||||
"url-normalize==1.4.3"
|
||||
"url-normalize==2.2.0"
|
||||
],
|
||||
"ssdp": [
|
||||
{
|
||||
|
@@ -197,5 +197,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_effect_none": {
|
||||
"title": "Light turned on with deprecated effect",
|
||||
"description": "A light was turned on with the deprecated effect `None`. This has been replaced with `off`. Please update any automations, scenes, or scripts that use this effect."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -29,6 +29,7 @@ from homeassistant.components.light import (
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.util import color as color_util
|
||||
|
||||
from ..bridge import HueBridge
|
||||
@@ -44,6 +45,9 @@ FALLBACK_MIN_KELVIN = 6500
|
||||
FALLBACK_MAX_KELVIN = 2000
|
||||
FALLBACK_KELVIN = 5800 # halfway
|
||||
|
||||
# HA 2025.4 replaced the deprecated effect "None" with HA default "off"
|
||||
DEPRECATED_EFFECT_NONE = "None"
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -233,6 +237,23 @@ class HueLight(HueBaseEntity, LightEntity):
|
||||
self._color_temp_active = color_temp is not None
|
||||
flash = kwargs.get(ATTR_FLASH)
|
||||
effect = effect_str = kwargs.get(ATTR_EFFECT)
|
||||
if effect_str == DEPRECATED_EFFECT_NONE:
|
||||
# deprecated effect "None" is now "off"
|
||||
effect_str = EFFECT_OFF
|
||||
async_create_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
"deprecated_effect_none",
|
||||
breaks_in_ha_version="2025.10.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_effect_none",
|
||||
)
|
||||
self.logger.warning(
|
||||
"Detected deprecated effect 'None' in %s, use 'off' instead. "
|
||||
"This will stop working in HA 2025.10",
|
||||
self.entity_id,
|
||||
)
|
||||
if effect_str == EFFECT_OFF:
|
||||
# ignore effect if set to "off" and we have no effect active
|
||||
# the special effect "off" is only used to stop an active effect
|
||||
|
@@ -5,5 +5,6 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["imgw_pib==1.0.10"]
|
||||
}
|
||||
|
88
homeassistant/components/imgw_pib/quality_scale.yaml
Normal file
88
homeassistant/components/imgw_pib/quality_scale.yaml
Normal file
@@ -0,0 +1,88 @@
|
||||
rules:
|
||||
# Bronze
|
||||
action-setup:
|
||||
status: exempt
|
||||
comment: The integration does not register services.
|
||||
appropriate-polling: done
|
||||
brands: done
|
||||
common-modules: done
|
||||
config-flow-test-coverage: done
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions:
|
||||
status: exempt
|
||||
comment: The integration does not register services.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
runtime-data: done
|
||||
test-before-configure: done
|
||||
test-before-setup: done
|
||||
unique-config-entry: done
|
||||
|
||||
# Silver
|
||||
action-exceptions:
|
||||
status: exempt
|
||||
comment: The integration does not register services.
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
comment: No options to configure.
|
||||
docs-installation-parameters: done
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
log-when-unavailable: done
|
||||
parallel-updates: done
|
||||
reauthentication-flow:
|
||||
status: exempt
|
||||
comment: No authentication required.
|
||||
test-coverage: done
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: done
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
comment: The integration is a cloud service and thus does not support discovery.
|
||||
discovery:
|
||||
status: exempt
|
||||
comment: The integration is a cloud service and thus does not support discovery.
|
||||
docs-data-update: todo
|
||||
docs-examples: todo
|
||||
docs-known-limitations: todo
|
||||
docs-supported-devices:
|
||||
status: exempt
|
||||
comment: This is a service, which doesn't integrate with any devices.
|
||||
docs-supported-functions: todo
|
||||
docs-troubleshooting:
|
||||
status: exempt
|
||||
comment: No known issues that could be resolved by the user.
|
||||
docs-use-cases: todo
|
||||
dynamic-devices:
|
||||
status: exempt
|
||||
comment: This integration has a fixed single service.
|
||||
entity-category: done
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default:
|
||||
status: exempt
|
||||
comment: This integration does not have any entities that should disabled by default.
|
||||
entity-translations: done
|
||||
exception-translations: done
|
||||
icon-translations: done
|
||||
reconfiguration-flow:
|
||||
status: exempt
|
||||
comment: Only parameter that could be changed station_id would force a new config entry.
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: This integration doesn't have any cases where raising an issue is needed.
|
||||
stale-devices:
|
||||
status: exempt
|
||||
comment: This integration has a fixed single service.
|
||||
|
||||
# Platinum
|
||||
async-dependency: done
|
||||
inject-websession: done
|
||||
strict-typing: done
|
@@ -10,8 +10,8 @@
|
||||
},
|
||||
"data_description": {
|
||||
"host": "Hostname or IP-address of the Intergas gateway.",
|
||||
"username": "The username to log into the gateway. This is `admin` in most cases.",
|
||||
"password": "The password to log into the gateway, is printed at the bottom of the gateway or is `intergas` for some older devices."
|
||||
"username": "The username to log in to the gateway. This is `admin` in most cases.",
|
||||
"password": "The password to log in to the gateway, is printed at the bottom of the gateway or is `intergas` for some older devices."
|
||||
}
|
||||
},
|
||||
"dhcp_auth": {
|
||||
@@ -22,8 +22,8 @@
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
},
|
||||
"data_description": {
|
||||
"username": "The username to log into the gateway. This is `admin` in most cases.",
|
||||
"password": "The password to log into the gateway, is printed at the bottom of the Gateway or is `intergas` for some older devices."
|
||||
"username": "[%key:component::incomfort::config::step::user::data_description::username%]",
|
||||
"password": "[%key:component::incomfort::config::step::user::data_description::password%]"
|
||||
}
|
||||
},
|
||||
"dhcp_confirm": {
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
from inkbird_ble import INKBIRDBluetoothDeviceData, SensorUpdate
|
||||
@@ -9,13 +10,16 @@ from inkbird_ble import INKBIRDBluetoothDeviceData, SensorUpdate
|
||||
from homeassistant.components.bluetooth import (
|
||||
BluetoothScanningMode,
|
||||
BluetoothServiceInfo,
|
||||
BluetoothServiceInfoBleak,
|
||||
async_ble_device_from_address,
|
||||
)
|
||||
from homeassistant.components.bluetooth.passive_update_processor import (
|
||||
PassiveBluetoothProcessorCoordinator,
|
||||
from homeassistant.components.bluetooth.active_update_processor import (
|
||||
ActiveBluetoothProcessorCoordinator,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
|
||||
from .const import CONF_DEVICE_TYPE, DOMAIN
|
||||
|
||||
@@ -23,34 +27,87 @@ PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
FALLBACK_POLL_INTERVAL = timedelta(seconds=180)
|
||||
|
||||
|
||||
class INKBIRDActiveBluetoothProcessorCoordinator(ActiveBluetoothProcessorCoordinator):
|
||||
"""Coordinator for INKBIRD Bluetooth devices."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
data: INKBIRDBluetoothDeviceData,
|
||||
) -> None:
|
||||
"""Initialize the INKBIRD Bluetooth processor coordinator."""
|
||||
self._data = data
|
||||
self._entry = entry
|
||||
address = entry.unique_id
|
||||
assert address is not None
|
||||
entry.async_on_unload(
|
||||
async_track_time_interval(
|
||||
hass, self._async_schedule_poll, FALLBACK_POLL_INTERVAL
|
||||
)
|
||||
)
|
||||
super().__init__(
|
||||
hass=hass,
|
||||
logger=_LOGGER,
|
||||
address=address,
|
||||
mode=BluetoothScanningMode.ACTIVE,
|
||||
update_method=self._async_on_update,
|
||||
needs_poll_method=self._async_needs_poll,
|
||||
poll_method=self._async_poll_data,
|
||||
)
|
||||
|
||||
async def _async_poll_data(
|
||||
self, last_service_info: BluetoothServiceInfoBleak
|
||||
) -> SensorUpdate:
|
||||
"""Poll the device."""
|
||||
return await self._data.async_poll(last_service_info.device)
|
||||
|
||||
@callback
|
||||
def _async_needs_poll(
|
||||
self, service_info: BluetoothServiceInfoBleak, last_poll: float | None
|
||||
) -> bool:
|
||||
return (
|
||||
not self.hass.is_stopping
|
||||
and self._data.poll_needed(service_info, last_poll)
|
||||
and bool(
|
||||
async_ble_device_from_address(
|
||||
self.hass, service_info.device.address, connectable=True
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_on_update(self, service_info: BluetoothServiceInfo) -> SensorUpdate:
|
||||
"""Handle update callback from the passive BLE processor."""
|
||||
update = self._data.update(service_info)
|
||||
if (
|
||||
self._entry.data.get(CONF_DEVICE_TYPE) is None
|
||||
and self._data.device_type is not None
|
||||
):
|
||||
device_type_str = str(self._data.device_type)
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self._entry,
|
||||
data={**self._entry.data, CONF_DEVICE_TYPE: device_type_str},
|
||||
)
|
||||
return update
|
||||
|
||||
@callback
|
||||
def _async_schedule_poll(self, _: datetime) -> None:
|
||||
"""Schedule a poll of the device."""
|
||||
if self._last_service_info and self._async_needs_poll(
|
||||
self._last_service_info, self._last_poll
|
||||
):
|
||||
self._debounced_poll.async_schedule_call()
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up INKBIRD BLE device from a config entry."""
|
||||
address = entry.unique_id
|
||||
assert address is not None
|
||||
device_type: str | None = entry.data.get(CONF_DEVICE_TYPE)
|
||||
data = INKBIRDBluetoothDeviceData(device_type)
|
||||
|
||||
@callback
|
||||
def _async_on_update(service_info: BluetoothServiceInfo) -> SensorUpdate:
|
||||
"""Handle update callback from the passive BLE processor."""
|
||||
nonlocal device_type
|
||||
update = data.update(service_info)
|
||||
if device_type is None and data.device_type is not None:
|
||||
device_type_str = str(data.device_type)
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, data={**entry.data, CONF_DEVICE_TYPE: device_type_str}
|
||||
)
|
||||
device_type = device_type_str
|
||||
return update
|
||||
|
||||
coordinator = PassiveBluetoothProcessorCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
address=address,
|
||||
mode=BluetoothScanningMode.ACTIVE,
|
||||
update_method=_async_on_update,
|
||||
)
|
||||
coordinator = INKBIRDActiveBluetoothProcessorCoordinator(hass, entry, data)
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
# only start after all platforms have had a chance to subscribe
|
||||
|
@@ -40,5 +40,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/inkbird",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["inkbird-ble==0.9.0"]
|
||||
"requirements": ["inkbird-ble==0.10.1"]
|
||||
}
|
||||
|
@@ -123,7 +123,7 @@
|
||||
"state": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
"slow": "[%key:component::iron_os::common::slow%]",
|
||||
"medium": "Medium",
|
||||
"medium": "[%key:common::state::medium%]",
|
||||
"fast": "[%key:component::iron_os::common::fast%]"
|
||||
}
|
||||
},
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/kulersky",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["bleak", "pykulersky"],
|
||||
"requirements": ["pykulersky==0.5.2"]
|
||||
"requirements": ["pykulersky==0.5.8"]
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ from homeassistant.components.sensor import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
CONF_DOMAIN,
|
||||
CONF_ENTITIES,
|
||||
CONF_SOURCE,
|
||||
@@ -49,6 +50,7 @@ DEVICE_CLASS_MAPPING = {
|
||||
pypck.lcn_defs.VarUnit.METERPERSECOND: SensorDeviceClass.SPEED,
|
||||
pypck.lcn_defs.VarUnit.VOLT: SensorDeviceClass.VOLTAGE,
|
||||
pypck.lcn_defs.VarUnit.AMPERE: SensorDeviceClass.CURRENT,
|
||||
pypck.lcn_defs.VarUnit.PPM: SensorDeviceClass.CO2,
|
||||
}
|
||||
|
||||
UNIT_OF_MEASUREMENT_MAPPING = {
|
||||
@@ -60,6 +62,7 @@ UNIT_OF_MEASUREMENT_MAPPING = {
|
||||
pypck.lcn_defs.VarUnit.METERPERSECOND: UnitOfSpeed.METERS_PER_SECOND,
|
||||
pypck.lcn_defs.VarUnit.VOLT: UnitOfElectricPotential.VOLT,
|
||||
pypck.lcn_defs.VarUnit.AMPERE: UnitOfElectricCurrent.AMPERE,
|
||||
pypck.lcn_defs.VarUnit.PPM: CONCENTRATION_PARTS_PER_MILLION,
|
||||
}
|
||||
|
||||
|
||||
|
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ld2410_ble",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["bluetooth-data-tools==1.26.1", "ld2410-ble==0.1.1"]
|
||||
"requirements": ["bluetooth-data-tools==1.27.0", "ld2410-ble==0.1.1"]
|
||||
}
|
||||
|
@@ -35,5 +35,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/led_ble",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["bluetooth-data-tools==1.26.1", "led-ble==1.1.6"]
|
||||
"requirements": ["bluetooth-data-tools==1.27.0", "led-ble==1.1.6"]
|
||||
}
|
||||
|
@@ -119,9 +119,9 @@
|
||||
"fan_mode": {
|
||||
"state": {
|
||||
"slow": "Slow",
|
||||
"low": "Low",
|
||||
"mid": "Medium",
|
||||
"high": "High",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"mid": "[%key:common::state::medium%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"power": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::high%]",
|
||||
"auto": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]"
|
||||
}
|
||||
@@ -303,7 +303,7 @@
|
||||
"state": {
|
||||
"invalid": "Invalid",
|
||||
"weak": "Weak",
|
||||
"normal": "Normal",
|
||||
"normal": "[%key:common::state::normal%]",
|
||||
"strong": "Strong",
|
||||
"very_strong": "Very strong"
|
||||
}
|
||||
@@ -390,17 +390,17 @@
|
||||
"temperature_state": {
|
||||
"name": "[%key:component::sensor::entity_component::temperature::name%]",
|
||||
"state": {
|
||||
"high": "High",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"normal": "Good",
|
||||
"low": "Low"
|
||||
"low": "[%key:common::state::low%]"
|
||||
}
|
||||
},
|
||||
"temperature_state_for_location": {
|
||||
"name": "[%key:component::lg_thinq::entity::number::target_temperature_for_location::name%]",
|
||||
"state": {
|
||||
"high": "[%key:component::lg_thinq::entity::sensor::temperature_state::state::high%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"normal": "[%key:component::lg_thinq::entity::sensor::temperature_state::state::normal%]",
|
||||
"low": "[%key:component::lg_thinq::entity::sensor::temperature_state::state::low%]"
|
||||
"low": "[%key:common::state::low%]"
|
||||
}
|
||||
},
|
||||
"current_state": {
|
||||
@@ -607,7 +607,7 @@
|
||||
"intensive_dry": "Spot",
|
||||
"macro": "Custom mode",
|
||||
"mop": "Mop",
|
||||
"normal": "Normal",
|
||||
"normal": "[%key:common::state::normal%]",
|
||||
"off": "[%key:common::state::off%]",
|
||||
"quiet_humidity": "Silent",
|
||||
"rapid_humidity": "Jet",
|
||||
@@ -626,7 +626,7 @@
|
||||
"auto": "Low power",
|
||||
"high": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::high%]",
|
||||
"mop": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::mop%]",
|
||||
"normal": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::normal%]",
|
||||
"normal": "[%key:common::state::normal%]",
|
||||
"off": "[%key:common::state::off%]",
|
||||
"turbo": "[%key:component::lg_thinq::entity::select::wind_strength::state::power%]"
|
||||
}
|
||||
@@ -653,7 +653,7 @@
|
||||
"heavy": "Intensive",
|
||||
"delicate": "Delicate",
|
||||
"turbo": "[%key:component::lg_thinq::entity::select::wind_strength::state::power%]",
|
||||
"normal": "Normal",
|
||||
"normal": "[%key:common::state::normal%]",
|
||||
"rinse": "Rinse",
|
||||
"refresh": "Refresh",
|
||||
"express": "Express",
|
||||
@@ -781,8 +781,8 @@
|
||||
"name": "Battery",
|
||||
"state": {
|
||||
"high": "Full",
|
||||
"mid": "Medium",
|
||||
"low": "Low",
|
||||
"mid": "[%key:common::state::medium%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"warning": "Empty"
|
||||
}
|
||||
},
|
||||
@@ -876,9 +876,9 @@
|
||||
"name": "Speed",
|
||||
"state": {
|
||||
"slow": "[%key:component::lg_thinq::entity::climate::climate_air_conditioner::state_attributes::fan_mode::state::slow%]",
|
||||
"low": "Low",
|
||||
"mid": "Medium",
|
||||
"high": "High",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"mid": "[%key:common::state::medium%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"power": "Turbo",
|
||||
"turbo": "[%key:component::lg_thinq::entity::select::wind_strength::state::power%]",
|
||||
"auto": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]",
|
||||
|
@@ -118,9 +118,9 @@
|
||||
"brightness_level": {
|
||||
"name": "Panel brightness",
|
||||
"state": {
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High"
|
||||
"low": "[%key:common::state::low%]",
|
||||
"medium": "[%key:common::state::medium%]",
|
||||
"high": "[%key:common::state::high%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["ical"],
|
||||
"requirements": ["ical==9.0.3"]
|
||||
"requirements": ["ical==9.1.0"]
|
||||
}
|
||||
|
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/local_todo",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["ical==9.0.3"]
|
||||
"requirements": ["ical==9.1.0"]
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, Final, NamedTuple, cast
|
||||
from typing import TYPE_CHECKING, Any, Final, NamedTuple, cast, final
|
||||
|
||||
from propcache.api import cached_property
|
||||
from sqlalchemy.engine.row import Row
|
||||
@@ -114,6 +114,7 @@ DATA_POS: Final = 11
|
||||
CONTEXT_POS: Final = 12
|
||||
|
||||
|
||||
@final # Final to allow direct checking of the type instead of using isinstance
|
||||
class EventAsRow(NamedTuple):
|
||||
"""Convert an event to a row.
|
||||
|
||||
|
@@ -265,4 +265,61 @@ DISCOVERY_SCHEMAS = [
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(clusters.SmokeCoAlarm.Attributes.InterconnectCOAlarm,),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="EnergyEvseChargingStatusSensor",
|
||||
translation_key="evse_charging_status",
|
||||
device_class=BinarySensorDeviceClass.BATTERY_CHARGING,
|
||||
measurement_to_ha={
|
||||
clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn: False,
|
||||
clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand: False,
|
||||
clusters.EnergyEvse.Enums.StateEnum.kPluggedInDemand: False,
|
||||
clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging: True,
|
||||
clusters.EnergyEvse.Enums.StateEnum.kPluggedInDischarging: False,
|
||||
clusters.EnergyEvse.Enums.StateEnum.kSessionEnding: False,
|
||||
clusters.EnergyEvse.Enums.StateEnum.kFault: False,
|
||||
}.get,
|
||||
),
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(clusters.EnergyEvse.Attributes.State,),
|
||||
allow_multi=True, # also used for sensor entity
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="EnergyEvsePlugStateSensor",
|
||||
translation_key="evse_plug_state",
|
||||
device_class=BinarySensorDeviceClass.PLUG,
|
||||
measurement_to_ha={
|
||||
clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn: False,
|
||||
clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand: True,
|
||||
clusters.EnergyEvse.Enums.StateEnum.kPluggedInDemand: True,
|
||||
clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging: True,
|
||||
clusters.EnergyEvse.Enums.StateEnum.kPluggedInDischarging: True,
|
||||
clusters.EnergyEvse.Enums.StateEnum.kSessionEnding: False,
|
||||
clusters.EnergyEvse.Enums.StateEnum.kFault: False,
|
||||
}.get,
|
||||
),
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(clusters.EnergyEvse.Attributes.State,),
|
||||
allow_multi=True, # also used for sensor entity
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="EnergyEvseSupplyStateSensor",
|
||||
translation_key="evse_supply_charging_state",
|
||||
device_class=BinarySensorDeviceClass.RUNNING,
|
||||
measurement_to_ha={
|
||||
clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled: False,
|
||||
clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled: True,
|
||||
clusters.EnergyEvse.Enums.SupplyStateEnum.kDischargingEnabled: False,
|
||||
clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabledDiagnostics: False,
|
||||
}.get,
|
||||
),
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(clusters.EnergyEvse.Attributes.SupplyState,),
|
||||
allow_multi=True, # also used for sensor entity
|
||||
),
|
||||
]
|
||||
|
@@ -61,6 +61,7 @@ class MatterEntityDescription(EntityDescription):
|
||||
# convert the value from the primary attribute to the value used by HA
|
||||
measurement_to_ha: Callable[[Any], Any] | None = None
|
||||
ha_to_native_value: Callable[[Any], Any] | None = None
|
||||
command_timeout: int | None = None
|
||||
|
||||
|
||||
class MatterEntity(Entity):
|
||||
|
@@ -71,6 +71,15 @@
|
||||
},
|
||||
"battery_replacement_description": {
|
||||
"default": "mdi:battery-sync-outline"
|
||||
},
|
||||
"evse_state": {
|
||||
"default": "mdi:ev-station"
|
||||
},
|
||||
"evse_supply_state": {
|
||||
"default": "mdi:ev-station"
|
||||
},
|
||||
"evse_fault_state": {
|
||||
"default": "mdi:ev-station"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
@@ -80,6 +89,9 @@
|
||||
"on": "mdi:lock",
|
||||
"off": "mdi:lock-off"
|
||||
}
|
||||
},
|
||||
"evse_charging_switch": {
|
||||
"default": "mdi:ev-station"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -77,6 +77,25 @@ OPERATIONAL_STATE_MAP = {
|
||||
clusters.RvcOperationalState.Enums.OperationalStateEnum.kDocked: "docked",
|
||||
}
|
||||
|
||||
EVSE_FAULT_STATE_MAP = {
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kNoError: "no_error",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kMeterFailure: "meter_failure",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kOverVoltage: "over_voltage",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kUnderVoltage: "under_voltage",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kOverCurrent: "over_current",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kContactWetFailure: "contact_wet_failure",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kContactDryFailure: "contact_dry_failure",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kPowerLoss: "power_loss",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kPowerQuality: "power_quality",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kPilotShortCircuit: "pilot_short_circuit",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kEmergencyStop: "emergency_stop",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kEVDisconnected: "ev_disconnected",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kWrongPowerSupply: "wrong_power_supply",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kLiveNeutralSwap: "live_neutral_swap",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kOverTemperature: "over_temperature",
|
||||
clusters.EnergyEvse.Enums.FaultStateEnum.kOther: "other",
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -904,4 +923,77 @@ DISCOVERY_SCHEMAS = [
|
||||
# don't discover this entry if the supported state list is empty
|
||||
secondary_value_is_not=[],
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="EnergyEvseFaultState",
|
||||
translation_key="evse_fault_state",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
options=list(EVSE_FAULT_STATE_MAP.values()),
|
||||
measurement_to_ha=EVSE_FAULT_STATE_MAP.get,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.EnergyEvse.Attributes.FaultState,),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="EnergyEvseCircuitCapacity",
|
||||
translation_key="evse_circuit_capacity",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE,
|
||||
suggested_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.EnergyEvse.Attributes.CircuitCapacity,),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="EnergyEvseMinimumChargeCurrent",
|
||||
translation_key="evse_min_charge_current",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE,
|
||||
suggested_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.EnergyEvse.Attributes.MinimumChargeCurrent,),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="EnergyEvseMaximumChargeCurrent",
|
||||
translation_key="evse_max_charge_current",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE,
|
||||
suggested_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.EnergyEvse.Attributes.MaximumChargeCurrent,),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="EnergyEvseUserMaximumChargeCurrent",
|
||||
translation_key="evse_user_max_charge_current",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE,
|
||||
suggested_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
suggested_display_precision=2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent,),
|
||||
),
|
||||
]
|
||||
|
@@ -76,6 +76,15 @@
|
||||
},
|
||||
"muted": {
|
||||
"name": "Muted"
|
||||
},
|
||||
"evse_charging_status": {
|
||||
"name": "Charging status"
|
||||
},
|
||||
"evse_plug": {
|
||||
"name": "Plug state"
|
||||
},
|
||||
"evse_supply_charging_state": {
|
||||
"name": "Supply charging state"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
@@ -135,9 +144,9 @@
|
||||
"state_attributes": {
|
||||
"preset_mode": {
|
||||
"state": {
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"medium": "[%key:common::state::medium%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"auto": "Auto",
|
||||
"natural_wind": "Natural wind",
|
||||
"sleep_wind": "Sleep wind"
|
||||
@@ -189,9 +198,9 @@
|
||||
"sensitivity_level": {
|
||||
"name": "Sensitivity",
|
||||
"state": {
|
||||
"low": "[%key:component::matter::entity::fan::fan::state_attributes::preset_mode::state::low%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"standard": "Standard",
|
||||
"high": "[%key:component::matter::entity::fan::fan::state_attributes::preset_mode::state::high%]"
|
||||
"high": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"startup_on_off": {
|
||||
@@ -213,7 +222,7 @@
|
||||
"name": "Number of rinses",
|
||||
"state": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
"normal": "Normal",
|
||||
"normal": "[%key:common::state::normal%]",
|
||||
"extra": "Extra",
|
||||
"max": "Max"
|
||||
}
|
||||
@@ -229,8 +238,8 @@
|
||||
"contamination_state": {
|
||||
"name": "Contamination state",
|
||||
"state": {
|
||||
"normal": "Normal",
|
||||
"low": "[%key:component::matter::entity::fan::fan::state_attributes::preset_mode::state::low%]",
|
||||
"normal": "[%key:common::state::normal%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"warning": "Warning",
|
||||
"critical": "Critical"
|
||||
}
|
||||
@@ -278,6 +287,42 @@
|
||||
},
|
||||
"current_phase": {
|
||||
"name": "Current phase"
|
||||
},
|
||||
"evse_fault_state": {
|
||||
"name": "Fault state",
|
||||
"state": {
|
||||
"no_error": "OK",
|
||||
"meter_failure": "Meter failure",
|
||||
"over_voltage": "Overvoltage",
|
||||
"under_voltage": "Undervoltage",
|
||||
"over_current": "Overcurrent",
|
||||
"contact_wet_failure": "Contact wet failure",
|
||||
"contact_dry_failure": "Contact dry failure",
|
||||
"power_loss": "Power loss",
|
||||
"power_quality": "Power quality",
|
||||
"pilot_short_circuit": "Pilot short circuit",
|
||||
"emergency_stop": "Emergency stop",
|
||||
"ev_disconnected": "EV disconnected",
|
||||
"wrong_power_supply": "Wrong power supply",
|
||||
"live_neutral_swap": "Live/neutral swap",
|
||||
"over_temperature": "Overtemperature",
|
||||
"other": "Other fault"
|
||||
}
|
||||
},
|
||||
"evse_circuit_capacity": {
|
||||
"name": "Circuit capacity"
|
||||
},
|
||||
"evse_charge_current": {
|
||||
"name": "Charge current"
|
||||
},
|
||||
"evse_min_charge_current": {
|
||||
"name": "Min charge current"
|
||||
},
|
||||
"evse_max_charge_current": {
|
||||
"name": "Max charge current"
|
||||
},
|
||||
"evse_user_max_charge_current": {
|
||||
"name": "User max charge current"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
@@ -289,6 +334,9 @@
|
||||
},
|
||||
"child_lock": {
|
||||
"name": "Child lock"
|
||||
},
|
||||
"evse_charging_switch": {
|
||||
"name": "Enable charging"
|
||||
}
|
||||
},
|
||||
"vacuum": {
|
||||
|
@@ -2,10 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from chip.clusters.Objects import ClusterCommand, NullValue
|
||||
from matter_server.client.models import device_types
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
@@ -22,6 +24,13 @@ from .entity import MatterEntity, MatterEntityDescription
|
||||
from .helpers import get_matter
|
||||
from .models import MatterDiscoverySchema
|
||||
|
||||
EVSE_SUPPLY_STATE_MAP = {
|
||||
clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled: False,
|
||||
clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled: True,
|
||||
clusters.EnergyEvse.Enums.SupplyStateEnum.kDischargingEnabled: False,
|
||||
clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabledDiagnostics: False,
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -58,6 +67,66 @@ class MatterSwitch(MatterEntity, SwitchEntity):
|
||||
)
|
||||
|
||||
|
||||
class MatterGenericCommandSwitch(MatterSwitch):
|
||||
"""Representation of a Matter switch."""
|
||||
|
||||
entity_description: MatterGenericCommandSwitchEntityDescription
|
||||
|
||||
_platform_translation_key = "switch"
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn switch on."""
|
||||
if self.entity_description.on_command:
|
||||
# custom command defined to set the new value
|
||||
await self.send_device_command(
|
||||
self.entity_description.on_command(),
|
||||
self.entity_description.command_timeout,
|
||||
)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn switch off."""
|
||||
if self.entity_description.off_command:
|
||||
await self.send_device_command(
|
||||
self.entity_description.off_command(),
|
||||
self.entity_description.command_timeout,
|
||||
)
|
||||
|
||||
@callback
|
||||
def _update_from_device(self) -> None:
|
||||
"""Update from device."""
|
||||
value = self.get_matter_attribute_value(self._entity_info.primary_attribute)
|
||||
if value_convert := self.entity_description.measurement_to_ha:
|
||||
value = value_convert(value)
|
||||
self._attr_is_on = value
|
||||
|
||||
async def send_device_command(
|
||||
self,
|
||||
command: ClusterCommand,
|
||||
command_timeout: int | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Send device command with timeout."""
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
command=command,
|
||||
timed_request_timeout_ms=command_timeout,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MatterGenericCommandSwitchEntityDescription(
|
||||
SwitchEntityDescription, MatterEntityDescription
|
||||
):
|
||||
"""Describe Matter Generic command Switch entities."""
|
||||
|
||||
# command: a custom callback to create the command to send to the device
|
||||
on_command: Callable[[], Any] | None = None
|
||||
off_command: Callable[[], Any] | None = None
|
||||
command_timeout: int | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MatterNumericSwitchEntityDescription(
|
||||
SwitchEntityDescription, MatterEntityDescription
|
||||
@@ -194,4 +263,26 @@ DISCOVERY_SCHEMAS = [
|
||||
),
|
||||
vendor_id=(4874,),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SWITCH,
|
||||
entity_description=MatterGenericCommandSwitchEntityDescription(
|
||||
key="EnergyEvseChargingSwitch",
|
||||
translation_key="evse_charging_switch",
|
||||
on_command=lambda: clusters.EnergyEvse.Commands.EnableCharging(
|
||||
chargingEnabledUntil=NullValue,
|
||||
minimumChargeCurrent=0,
|
||||
maximumChargeCurrent=0,
|
||||
),
|
||||
off_command=clusters.EnergyEvse.Commands.Disable,
|
||||
command_timeout=3000,
|
||||
measurement_to_ha=EVSE_SUPPLY_STATE_MAP.get,
|
||||
),
|
||||
entity_class=MatterGenericCommandSwitch,
|
||||
required_attributes=(
|
||||
clusters.EnergyEvse.Attributes.SupplyState,
|
||||
clusters.EnergyEvse.Attributes.AcceptedCommandList,
|
||||
),
|
||||
value_contains=clusters.EnergyEvse.Commands.EnableCharging.command_id,
|
||||
allow_multi=True,
|
||||
),
|
||||
]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user