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
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Dependency review
|
- name: Dependency review
|
||||||
uses: actions/dependency-review-action@v4.5.0
|
uses: actions/dependency-review-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
license-check: false # We use our own license audit checks
|
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(
|
integrations, all_integrations = await _async_resolve_domains_and_preload(
|
||||||
hass, config
|
hass, config
|
||||||
)
|
)
|
||||||
all_domains = set(all_integrations)
|
# Detect all cycles
|
||||||
domains = set(integrations)
|
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(
|
_LOGGER.info(
|
||||||
"Domains to be set up: %s | %s",
|
"Domains to be set up: %s | %s",
|
||||||
@@ -868,6 +874,8 @@ async def _async_set_up_integrations(
|
|||||||
all_domains - domains,
|
all_domains - domains,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async_set_domains_to_be_loaded(hass, all_domains)
|
||||||
|
|
||||||
# Initialize recorder
|
# Initialize recorder
|
||||||
if "recorder" in all_domains:
|
if "recorder" in all_domains:
|
||||||
recorder.async_initialize_recorder(hass)
|
recorder.async_initialize_recorder(hass)
|
||||||
@@ -900,24 +908,12 @@ async def _async_set_up_integrations(
|
|||||||
stage_dep_domains_unfiltered = {
|
stage_dep_domains_unfiltered = {
|
||||||
dep
|
dep
|
||||||
for domain in stage_domains
|
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
|
if dep not in stage_domains
|
||||||
}
|
}
|
||||||
stage_dep_domains = stage_dep_domains_unfiltered - hass.config.components
|
stage_dep_domains = stage_dep_domains_unfiltered - hass.config.components
|
||||||
|
|
||||||
stage_all_domains = stage_domains | stage_dep_domains
|
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(
|
_LOGGER.info(
|
||||||
"Setting up stage %s: %s | %s\nDependencies: %s | %s",
|
"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,
|
stage_dep_domains_unfiltered - stage_dep_domains,
|
||||||
)
|
)
|
||||||
|
|
||||||
async_set_domains_to_be_loaded(hass, stage_all_domains)
|
|
||||||
|
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
await _async_setup_multi_components(hass, stage_all_domains, config)
|
await _async_setup_multi_components(hass, stage_all_domains, config)
|
||||||
continue
|
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": {
|
"level": {
|
||||||
"name": "Level",
|
"name": "Level",
|
||||||
"state": {
|
"state": {
|
||||||
"high": "High",
|
"high": "[%key:common::state::high%]",
|
||||||
"low": "Low",
|
"low": "[%key:common::state::low%]",
|
||||||
"moderate": "Moderate",
|
"moderate": "Moderate",
|
||||||
"very_high": "Very high"
|
"very_high": "[%key:common::state::very_high%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,10 +89,10 @@
|
|||||||
"level": {
|
"level": {
|
||||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
"high": "[%key:common::state::high%]",
|
||||||
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
"low": "[%key:common::state::low%]",
|
||||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
"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": {
|
"level": {
|
||||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
"high": "[%key:common::state::high%]",
|
||||||
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
"low": "[%key:common::state::low%]",
|
||||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
"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": {
|
"level": {
|
||||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
"high": "[%key:common::state::high%]",
|
||||||
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
"low": "[%key:common::state::low%]",
|
||||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
"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": {
|
"level": {
|
||||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
"high": "[%key:common::state::high%]",
|
||||||
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
"low": "[%key:common::state::low%]",
|
||||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
"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": {
|
"level": {
|
||||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"high": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::high%]",
|
"high": "[%key:common::state::high%]",
|
||||||
"low": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::low%]",
|
"low": "[%key:common::state::low%]",
|
||||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
"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",
|
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["aioairzone"],
|
"loggers": ["aioairzone"],
|
||||||
"requirements": ["aioairzone==0.9.9"]
|
"requirements": ["aioairzone==1.0.0"]
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,8 @@ from aioairzone.const import (
|
|||||||
AZD_HUMIDITY,
|
AZD_HUMIDITY,
|
||||||
AZD_TEMP,
|
AZD_TEMP,
|
||||||
AZD_TEMP_UNIT,
|
AZD_TEMP_UNIT,
|
||||||
|
AZD_THERMOSTAT_BATTERY,
|
||||||
|
AZD_THERMOSTAT_SIGNAL,
|
||||||
AZD_WEBSERVER,
|
AZD_WEBSERVER,
|
||||||
AZD_WIFI_RSSI,
|
AZD_WIFI_RSSI,
|
||||||
AZD_ZONES,
|
AZD_ZONES,
|
||||||
@@ -73,6 +75,20 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
|||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
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": {
|
"sensor": {
|
||||||
"rssi": {
|
"rssi": {
|
||||||
"name": "RSSI"
|
"name": "RSSI"
|
||||||
|
},
|
||||||
|
"thermostat_signal": {
|
||||||
|
"name": "Signal strength"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@ import voluptuous as vol
|
|||||||
from homeassistant.components import zeroconf
|
from homeassistant.components import zeroconf
|
||||||
from homeassistant.config_entries import (
|
from homeassistant.config_entries import (
|
||||||
SOURCE_IGNORE,
|
SOURCE_IGNORE,
|
||||||
|
SOURCE_REAUTH,
|
||||||
SOURCE_ZEROCONF,
|
SOURCE_ZEROCONF,
|
||||||
ConfigEntry,
|
ConfigEntry,
|
||||||
ConfigFlow,
|
ConfigFlow,
|
||||||
@@ -381,7 +382,9 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
CONF_IDENTIFIERS: list(combined_identifiers),
|
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)
|
self.hass.config_entries.async_schedule_reload(entry.entry_id)
|
||||||
if not allow_exist:
|
if not allow_exist:
|
||||||
raise DeviceAlreadyConfigured
|
raise DeviceAlreadyConfigured
|
||||||
|
@@ -36,9 +36,9 @@
|
|||||||
"wi_fi_strength": {
|
"wi_fi_strength": {
|
||||||
"name": "Wi-Fi strength",
|
"name": "Wi-Fi strength",
|
||||||
"state": {
|
"state": {
|
||||||
"low": "Low",
|
"low": "[%key:common::state::low%]",
|
||||||
"medium": "Medium",
|
"medium": "[%key:common::state::medium%]",
|
||||||
"high": "High"
|
"high": "[%key:common::state::high%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -103,8 +103,8 @@
|
|||||||
"temperature_range": {
|
"temperature_range": {
|
||||||
"name": "Temperature range",
|
"name": "Temperature range",
|
||||||
"state": {
|
"state": {
|
||||||
"low": "Low",
|
"low": "[%key:common::state::low%]",
|
||||||
"high": "High"
|
"high": "[%key:common::state::high%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -124,8 +124,8 @@
|
|||||||
"battery": {
|
"battery": {
|
||||||
"name": "Battery",
|
"name": "Battery",
|
||||||
"state": {
|
"state": {
|
||||||
"off": "Normal",
|
"off": "[%key:common::state::normal%]",
|
||||||
"on": "Low"
|
"on": "[%key:common::state::low%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"battery_charging": {
|
"battery_charging": {
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
"cold": {
|
"cold": {
|
||||||
"name": "Cold",
|
"name": "Cold",
|
||||||
"state": {
|
"state": {
|
||||||
"off": "[%key:component::binary_sensor::entity_component::battery::state::off%]",
|
"off": "[%key:common::state::normal%]",
|
||||||
"on": "Cold"
|
"on": "Cold"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
"heat": {
|
"heat": {
|
||||||
"name": "Heat",
|
"name": "Heat",
|
||||||
"state": {
|
"state": {
|
||||||
"off": "[%key:component::binary_sensor::entity_component::battery::state::off%]",
|
"off": "[%key:common::state::normal%]",
|
||||||
"on": "Hot"
|
"on": "Hot"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
"bleak-retry-connector==3.9.0",
|
"bleak-retry-connector==3.9.0",
|
||||||
"bluetooth-adapters==0.21.4",
|
"bluetooth-adapters==0.21.4",
|
||||||
"bluetooth-auto-recovery==1.4.5",
|
"bluetooth-auto-recovery==1.4.5",
|
||||||
"bluetooth-data-tools==1.26.1",
|
"bluetooth-data-tools==1.27.0",
|
||||||
"dbus-fast==2.43.0",
|
"dbus-fast==2.43.0",
|
||||||
"habluetooth==3.37.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.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
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 homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@@ -91,11 +92,22 @@ class BondConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
self._discovered[CONF_ACCESS_TOKEN] = token
|
self._discovered[CONF_ACCESS_TOKEN] = token
|
||||||
try:
|
try:
|
||||||
_, hub_name = await _validate_input(self.hass, self._discovered)
|
bond_id, hub_name = await _validate_input(self.hass, self._discovered)
|
||||||
except InputValidationError:
|
except InputValidationError:
|
||||||
return
|
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
|
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(
|
async def async_step_zeroconf(
|
||||||
self, discovery_info: ZeroconfServiceInfo
|
self, discovery_info: ZeroconfServiceInfo
|
||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
@@ -104,11 +116,17 @@ class BondConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
host: str = discovery_info.host
|
host: str = discovery_info.host
|
||||||
bond_id = name.partition(".")[0]
|
bond_id = name.partition(".")[0]
|
||||||
await self.async_set_unique_id(bond_id)
|
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():
|
for entry in self._async_current_entries():
|
||||||
if entry.unique_id != bond_id:
|
if entry.unique_id != bond_id:
|
||||||
continue
|
continue
|
||||||
updates = {CONF_HOST: host}
|
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)
|
token := await async_get_token(self.hass, host)
|
||||||
):
|
):
|
||||||
updates[CONF_ACCESS_TOKEN] = token
|
updates[CONF_ACCESS_TOKEN] = token
|
||||||
@@ -153,10 +171,14 @@ class BondConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
CONF_HOST: self._discovered[CONF_HOST],
|
CONF_HOST: self._discovered[CONF_HOST],
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
_, hub_name = await _validate_input(self.hass, data)
|
bond_id, hub_name = await _validate_input(self.hass, data)
|
||||||
except InputValidationError as error:
|
except InputValidationError as error:
|
||||||
errors["base"] = error.base
|
errors["base"] = error.base
|
||||||
else:
|
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(
|
return self.async_create_entry(
|
||||||
title=hub_name,
|
title=hub_name,
|
||||||
data=data,
|
data=data,
|
||||||
@@ -185,8 +207,10 @@ class BondConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
except InputValidationError as error:
|
except InputValidationError as error:
|
||||||
errors["base"] = error.base
|
errors["base"] = error.base
|
||||||
else:
|
else:
|
||||||
await self.async_set_unique_id(bond_id)
|
await self.async_set_unique_id(bond_id, raise_on_progress=False)
|
||||||
self._abort_if_unique_id_configured()
|
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_create_entry(title=hub_name, data=user_input)
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
|
@@ -3,6 +3,16 @@
|
|||||||
"name": "Bond",
|
"name": "Bond",
|
||||||
"codeowners": ["@bdraco", "@prystupa", "@joshs85", "@marciogranzotto"],
|
"codeowners": ["@bdraco", "@prystupa", "@joshs85", "@marciogranzotto"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
|
"dhcp": [
|
||||||
|
{
|
||||||
|
"hostname": "bond-*",
|
||||||
|
"macaddress": "3C6A2C1*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hostname": "bond-*",
|
||||||
|
"macaddress": "F44E38*"
|
||||||
|
}
|
||||||
|
],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/bond",
|
"documentation": "https://www.home-assistant.io/integrations/bond",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["bond_async"],
|
"loggers": ["bond_async"],
|
||||||
|
@@ -9,7 +9,7 @@ from bosch_alarm_mode2 import Panel
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, Platform
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
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 homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
from .const import CONF_INSTALLER_CODE, CONF_USER_CODE, DOMAIN
|
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()
|
await panel.connect()
|
||||||
except (PermissionError, ValueError) as err:
|
except (PermissionError, ValueError) as err:
|
||||||
await panel.disconnect()
|
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:
|
except (TimeoutError, OSError, ConnectionRefusedError, SSLError) as err:
|
||||||
await panel.disconnect()
|
await panel.disconnect()
|
||||||
raise ConfigEntryNotReady("Connection failed") from err
|
raise ConfigEntryNotReady(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="cannot_connect",
|
||||||
|
) from err
|
||||||
|
|
||||||
entry.runtime_data = panel
|
entry.runtime_data = panel
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from collections.abc import Mapping
|
||||||
import logging
|
import logging
|
||||||
import ssl
|
import ssl
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -163,3 +164,55 @@ class BoschAlarmConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
data_schema=self.add_suggested_values_to_schema(schema, user_input),
|
data_schema=self.add_suggested_values_to_schema(schema, user_input),
|
||||||
errors=errors,
|
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
|
integration-owner: done
|
||||||
log-when-unavailable: todo
|
log-when-unavailable: todo
|
||||||
parallel-updates: todo
|
parallel-updates: todo
|
||||||
reauthentication-flow: todo
|
reauthentication-flow: done
|
||||||
test-coverage: done
|
test-coverage: done
|
||||||
|
|
||||||
# Gold
|
# Gold
|
||||||
|
@@ -22,6 +22,18 @@
|
|||||||
"installer_code": "The installer code from your panel",
|
"installer_code": "The installer code from your panel",
|
||||||
"user_code": "The user 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": {
|
"error": {
|
||||||
@@ -30,7 +42,16 @@
|
|||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
},
|
},
|
||||||
"abort": {
|
"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": {
|
"get_events": {
|
||||||
"name": "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": {
|
"fields": {
|
||||||
"start_date_time": {
|
"start_date_time": {
|
||||||
"name": "Start time",
|
"name": "Start time",
|
||||||
|
@@ -127,7 +127,11 @@ class CloudOAuth2Implementation(config_entry_oauth2_flow.AbstractOAuth2Implement
|
|||||||
flow_id=flow_id, user_input=tokens
|
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
|
return authorize_url
|
||||||
|
|
||||||
|
@@ -162,7 +162,7 @@ class ComelitHumidifierEntity(CoordinatorEntity[ComelitSerialBridge], Humidifier
|
|||||||
|
|
||||||
async def async_set_humidity(self, humidity: int) -> None:
|
async def async_set_humidity(self, humidity: int) -> None:
|
||||||
"""Set new target humidity."""
|
"""Set new target humidity."""
|
||||||
if self.mode == HumidifierComelitMode.OFF:
|
if not self._attr_is_on:
|
||||||
raise ServiceValidationError(
|
raise ServiceValidationError(
|
||||||
translation_domain=DOMAIN,
|
translation_domain=DOMAIN,
|
||||||
translation_key="humidity_while_off",
|
translation_key="humidity_while_off",
|
||||||
@@ -190,9 +190,13 @@ class ComelitHumidifierEntity(CoordinatorEntity[ComelitSerialBridge], Humidifier
|
|||||||
await self.coordinator.api.set_humidity_status(
|
await self.coordinator.api.set_humidity_status(
|
||||||
self._device.index, self._set_command
|
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:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn off."""
|
"""Turn off."""
|
||||||
await self.coordinator.api.set_humidity_status(
|
await self.coordinator.api.set_humidity_status(
|
||||||
self._device.index, HumidifierComelitCommand.OFF
|
self._device.index, HumidifierComelitCommand.OFF
|
||||||
)
|
)
|
||||||
|
self._attr_is_on = False
|
||||||
|
self.async_write_ha_state()
|
||||||
|
@@ -52,7 +52,9 @@
|
|||||||
"rest": "Rest",
|
"rest": "Rest",
|
||||||
"sabotated": "Sabotated"
|
"sabotated": "Sabotated"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
"humidifier": {
|
||||||
"humidifier": {
|
"humidifier": {
|
||||||
"name": "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 import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
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 .const import KEY_MAC, TIMEOUT
|
||||||
from .coordinator import DaikinConfigEntry, DaikinCoordinator
|
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),
|
key=entry.data.get(CONF_API_KEY),
|
||||||
uuid=entry.data.get(CONF_UUID),
|
uuid=entry.data.get(CONF_UUID),
|
||||||
password=entry.data.get(CONF_PASSWORD),
|
password=entry.data.get(CONF_PASSWORD),
|
||||||
|
ssl_context=client_context_no_verify(),
|
||||||
)
|
)
|
||||||
_LOGGER.debug("Connection to %s successful", host)
|
_LOGGER.debug("Connection to %s successful", host)
|
||||||
except TimeoutError as err:
|
except TimeoutError as err:
|
||||||
|
@@ -6,6 +6,6 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/daikin",
|
"documentation": "https://www.home-assistant.io/integrations/daikin",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pydaikin"],
|
"loggers": ["pydaikin"],
|
||||||
"requirements": ["pydaikin==2.14.1"],
|
"requirements": ["pydaikin==2.15.0"],
|
||||||
"zeroconf": ["_dkapi._tcp.local."]
|
"zeroconf": ["_dkapi._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@@ -7,5 +7,5 @@
|
|||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["dsmr_parser"],
|
"loggers": ["dsmr_parser"],
|
||||||
"requirements": ["dsmr-parser==1.4.2"]
|
"requirements": ["dsmr-parser==1.4.3"]
|
||||||
}
|
}
|
||||||
|
@@ -51,8 +51,8 @@
|
|||||||
"electricity_active_tariff": {
|
"electricity_active_tariff": {
|
||||||
"name": "Active tariff",
|
"name": "Active tariff",
|
||||||
"state": {
|
"state": {
|
||||||
"low": "Low",
|
"low": "[%key:common::state::low%]",
|
||||||
"normal": "Normal"
|
"normal": "[%key:common::state::normal%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electricity_delivered_tariff_1": {
|
"electricity_delivered_tariff_1": {
|
||||||
|
@@ -140,8 +140,8 @@
|
|||||||
"electricity_tariff": {
|
"electricity_tariff": {
|
||||||
"name": "Electricity tariff",
|
"name": "Electricity tariff",
|
||||||
"state": {
|
"state": {
|
||||||
"low": "Low",
|
"low": "[%key:common::state::low%]",
|
||||||
"high": "High"
|
"high": "[%key:common::state::high%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"power_failure_count": {
|
"power_failure_count": {
|
||||||
|
@@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
"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": {
|
"water_amount": {
|
||||||
"name": "Water flow level",
|
"name": "Water flow level",
|
||||||
"state": {
|
"state": {
|
||||||
"high": "High",
|
"high": "[%key:common::state::high%]",
|
||||||
"low": "Low",
|
"low": "[%key:common::state::low%]",
|
||||||
"medium": "Medium",
|
"medium": "[%key:common::state::medium%]",
|
||||||
"ultrahigh": "Ultrahigh"
|
"ultrahigh": "Ultrahigh"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -9,7 +9,7 @@ from homeassistant.helpers.device_registry import DeviceEntry
|
|||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
|
from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator
|
||||||
|
|
||||||
PLATFORMS = [Platform.CLIMATE, Platform.LIGHT]
|
PLATFORMS = [Platform.CLIMATE, Platform.LIGHT, Platform.SENSOR]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
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:
|
for end_point in end_points:
|
||||||
response = await envoy.request(end_point)
|
try:
|
||||||
fixture_data[end_point] = response.text.replace("\n", "").replace(
|
response = await envoy.request(end_point)
|
||||||
serial, CLEAN_TEXT
|
fixture_data[end_point] = response.text.replace("\n", "").replace(
|
||||||
)
|
serial, CLEAN_TEXT
|
||||||
fixture_data[f"{end_point}_log"] = json_dumps(
|
)
|
||||||
{
|
fixture_data[f"{end_point}_log"] = json_dumps(
|
||||||
"headers": dict(response.headers.items()),
|
{
|
||||||
"code": response.status_code,
|
"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
|
return fixture_data
|
||||||
|
|
||||||
|
|
||||||
@@ -160,10 +163,7 @@ async def async_get_config_entry_diagnostics(
|
|||||||
|
|
||||||
fixture_data: dict[str, Any] = {}
|
fixture_data: dict[str, Any] = {}
|
||||||
if entry.options.get(OPTION_DIAGNOSTICS_INCLUDE_FIXTURES, False):
|
if entry.options.get(OPTION_DIAGNOSTICS_INCLUDE_FIXTURES, False):
|
||||||
try:
|
fixture_data = await _get_fixture_collection(envoy=envoy, serial=old_serial)
|
||||||
fixture_data = await _get_fixture_collection(envoy=envoy, serial=old_serial)
|
|
||||||
except EnvoyError as err:
|
|
||||||
fixture_data["Error"] = repr(err)
|
|
||||||
|
|
||||||
diagnostic_data: dict[str, Any] = {
|
diagnostic_data: dict[str, Any] = {
|
||||||
"config_entry": async_redact_data(entry.as_dict(), TO_REDACT),
|
"config_entry": async_redact_data(entry.as_dict(), TO_REDACT),
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pyenphase"],
|
"loggers": ["pyenphase"],
|
||||||
"requirements": ["pyenphase==1.25.1"],
|
"requirements": ["pyenphase==1.25.5"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_enphase-envoy._tcp.local."
|
"type": "_enphase-envoy._tcp.local."
|
||||||
|
@@ -22,5 +22,5 @@
|
|||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["eq3btsmart"],
|
"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,
|
APIConnectionError,
|
||||||
APIVersion,
|
APIVersion,
|
||||||
DeviceInfo as EsphomeDeviceInfo,
|
DeviceInfo as EsphomeDeviceInfo,
|
||||||
EncryptionHelloAPIError,
|
EncryptionPlaintextAPIError,
|
||||||
EntityInfo,
|
EntityInfo,
|
||||||
HomeassistantServiceCall,
|
HomeassistantServiceCall,
|
||||||
InvalidAuthAPIError,
|
InvalidAuthAPIError,
|
||||||
@@ -571,7 +571,7 @@ class ESPHomeManager:
|
|||||||
if isinstance(
|
if isinstance(
|
||||||
err,
|
err,
|
||||||
(
|
(
|
||||||
EncryptionHelloAPIError,
|
EncryptionPlaintextAPIError,
|
||||||
RequiresEncryptionAPIError,
|
RequiresEncryptionAPIError,
|
||||||
InvalidEncryptionKeyAPIError,
|
InvalidEncryptionKeyAPIError,
|
||||||
InvalidAuthAPIError,
|
InvalidAuthAPIError,
|
||||||
|
@@ -16,9 +16,9 @@
|
|||||||
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
|
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
|
||||||
"mqtt": ["esphome/discover/#"],
|
"mqtt": ["esphome/discover/#"],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"aioesphomeapi==29.8.0",
|
"aioesphomeapi==29.9.0",
|
||||||
"esphome-dashboard-api==1.2.3",
|
"esphome-dashboard-api==1.2.3",
|
||||||
"bleak-esphome==2.12.0"
|
"bleak-esphome==2.13.1"
|
||||||
],
|
],
|
||||||
"zeroconf": ["_esphomelib._tcp.local."]
|
"zeroconf": ["_esphomelib._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@@ -152,7 +152,7 @@ class EvoZone(EvoChild, EvoClimateEntity):
|
|||||||
super().__init__(coordinator, evo_device)
|
super().__init__(coordinator, evo_device)
|
||||||
self._evo_id = evo_device.id
|
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
|
# this system does not have a distinct ID for the zone
|
||||||
self._attr_unique_id = f"{evo_device.id}z"
|
self._attr_unique_id = f"{evo_device.id}z"
|
||||||
else:
|
else:
|
||||||
|
@@ -6,5 +6,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["evohome", "evohomeasync", "evohomeasync2"],
|
"loggers": ["evohome", "evohomeasync", "evohomeasync2"],
|
||||||
"quality_scale": "legacy",
|
"quality_scale": "legacy",
|
||||||
"requirements": ["evohome-async==1.0.4"]
|
"requirements": ["evohome-async==1.0.5"]
|
||||||
}
|
}
|
||||||
|
@@ -301,6 +301,7 @@ class FibaroController:
|
|||||||
device.ha_id = (
|
device.ha_id = (
|
||||||
f"{slugify(room_name)}_{slugify(device.name)}_{device.fibaro_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):
|
if device.enabled and (not device.is_plugin or self._import_plugins):
|
||||||
platform = self._map_device_to_platform(device)
|
platform = self._map_device_to_platform(device)
|
||||||
if platform is None:
|
if platform is None:
|
||||||
|
@@ -53,5 +53,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/flux_led",
|
"documentation": "https://www.home-assistant.io/integrations/flux_led",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["flux_led"],
|
"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",
|
"documentation": "https://www.home-assistant.io/integrations/forecast_solar",
|
||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Coordinator is used to centralize the data updates
|
||||||
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class FritzBinarySensorEntityDescription(
|
class FritzBinarySensorEntityDescription(
|
||||||
|
@@ -31,6 +31,9 @@ from .entity import FritzDeviceBase
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Set a sane value to avoid too many updates
|
||||||
|
PARALLEL_UPDATES = 5
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class FritzButtonDescription(ButtonEntityDescription):
|
class FritzButtonDescription(ButtonEntityDescription):
|
||||||
|
@@ -22,6 +22,9 @@ from .entity import FritzDeviceBase
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Coordinator is used to centralize the data updates
|
||||||
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@@ -18,6 +18,9 @@ from .entity import FritzBoxBaseEntity
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Coordinator is used to centralize the data updates
|
||||||
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@@ -14,9 +14,7 @@ rules:
|
|||||||
docs-actions: done
|
docs-actions: done
|
||||||
docs-high-level-description: done
|
docs-high-level-description: done
|
||||||
docs-installation-instructions: done
|
docs-installation-instructions: done
|
||||||
docs-removal-instructions:
|
docs-removal-instructions: done
|
||||||
status: todo
|
|
||||||
comment: include the proper docs snippet
|
|
||||||
entity-event-setup: done
|
entity-event-setup: done
|
||||||
entity-unique-id: done
|
entity-unique-id: done
|
||||||
has-entity-name:
|
has-entity-name:
|
||||||
@@ -31,15 +29,11 @@ rules:
|
|||||||
action-exceptions: done
|
action-exceptions: done
|
||||||
config-entry-unloading: done
|
config-entry-unloading: done
|
||||||
docs-configuration-parameters: done
|
docs-configuration-parameters: done
|
||||||
docs-installation-parameters:
|
docs-installation-parameters: done
|
||||||
status: todo
|
|
||||||
comment: add the proper configuration_basic block
|
|
||||||
entity-unavailable: done
|
entity-unavailable: done
|
||||||
integration-owner: done
|
integration-owner: done
|
||||||
log-when-unavailable: done
|
log-when-unavailable: done
|
||||||
parallel-updates:
|
parallel-updates: done
|
||||||
status: todo
|
|
||||||
comment: not set at the moment, we use a coordinator
|
|
||||||
reauthentication-flow: done
|
reauthentication-flow: done
|
||||||
test-coverage:
|
test-coverage:
|
||||||
status: todo
|
status: todo
|
||||||
@@ -50,7 +44,7 @@ rules:
|
|||||||
diagnostics: done
|
diagnostics: done
|
||||||
discovery-update-info: todo
|
discovery-update-info: todo
|
||||||
discovery: done
|
discovery: done
|
||||||
docs-data-update: todo
|
docs-data-update: done
|
||||||
docs-examples: done
|
docs-examples: done
|
||||||
docs-known-limitations:
|
docs-known-limitations:
|
||||||
status: exempt
|
status: exempt
|
||||||
|
@@ -32,6 +32,9 @@ from .entity import FritzBoxBaseCoordinatorEntity, FritzEntityDescription
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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:
|
def _uptime_calculation(seconds_uptime: float, last_value: datetime | None) -> datetime:
|
||||||
"""Calculate uptime with deviation."""
|
"""Calculate uptime with deviation."""
|
||||||
|
@@ -38,6 +38,9 @@ from .entity import FritzBoxBaseEntity, FritzDeviceBase
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Set a sane value to avoid too many updates
|
||||||
|
PARALLEL_UPDATES = 5
|
||||||
|
|
||||||
|
|
||||||
async def _async_deflection_entities_list(
|
async def _async_deflection_entities_list(
|
||||||
avm_wrapper: AvmWrapper, device_friendly_name: str
|
avm_wrapper: AvmWrapper, device_friendly_name: str
|
||||||
|
@@ -20,6 +20,9 @@ from .entity import FritzBoxBaseCoordinatorEntity, FritzEntityDescription
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Set a sane value to avoid too many updates
|
||||||
|
PARALLEL_UPDATES = 5
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class FritzUpdateEntityDescription(UpdateEntityDescription, FritzEntityDescription):
|
class FritzUpdateEntityDescription(UpdateEntityDescription, FritzEntityDescription):
|
||||||
|
@@ -137,6 +137,7 @@ SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = (
|
|||||||
key="battery",
|
key="battery",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
device_class=SensorDeviceClass.BATTERY,
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
suitable=lambda device: device.battery_level is not None,
|
suitable=lambda device: device.battery_level is not None,
|
||||||
native_value=lambda device: device.battery_level,
|
native_value=lambda device: device.battery_level,
|
||||||
|
@@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["home-assistant-frontend==20250401.0"]
|
"requirements": ["home-assistant-frontend==20250404.0"]
|
||||||
}
|
}
|
||||||
|
@@ -79,9 +79,9 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"no_data": "No data",
|
"no_data": "No data",
|
||||||
"too_low": "Too low",
|
"too_low": "Too low",
|
||||||
"low": "Low",
|
"low": "[%key:common::state::low%]",
|
||||||
"perfect": "Perfect",
|
"perfect": "Perfect",
|
||||||
"high": "High",
|
"high": "[%key:common::state::high%]",
|
||||||
"too_high": "Too high"
|
"too_high": "Too high"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -90,9 +90,9 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"no_data": "[%key:component::fyta::entity::sensor::temperature_status::state::no_data%]",
|
"no_data": "[%key:component::fyta::entity::sensor::temperature_status::state::no_data%]",
|
||||||
"too_low": "[%key:component::fyta::entity::sensor::temperature_status::state::too_low%]",
|
"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%]",
|
"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%]"
|
"too_high": "[%key:component::fyta::entity::sensor::temperature_status::state::too_high%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -101,9 +101,9 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"no_data": "[%key:component::fyta::entity::sensor::temperature_status::state::no_data%]",
|
"no_data": "[%key:component::fyta::entity::sensor::temperature_status::state::no_data%]",
|
||||||
"too_low": "[%key:component::fyta::entity::sensor::temperature_status::state::too_low%]",
|
"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%]",
|
"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%]"
|
"too_high": "[%key:component::fyta::entity::sensor::temperature_status::state::too_high%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -112,9 +112,9 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"no_data": "[%key:component::fyta::entity::sensor::temperature_status::state::no_data%]",
|
"no_data": "[%key:component::fyta::entity::sensor::temperature_status::state::no_data%]",
|
||||||
"too_low": "[%key:component::fyta::entity::sensor::temperature_status::state::too_low%]",
|
"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%]",
|
"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%]"
|
"too_high": "[%key:component::fyta::entity::sensor::temperature_status::state::too_high%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -123,9 +123,9 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"no_data": "[%key:component::fyta::entity::sensor::temperature_status::state::no_data%]",
|
"no_data": "[%key:component::fyta::entity::sensor::temperature_status::state::no_data%]",
|
||||||
"too_low": "[%key:component::fyta::entity::sensor::temperature_status::state::too_low%]",
|
"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%]",
|
"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%]"
|
"too_high": "[%key:component::fyta::entity::sensor::temperature_status::state::too_high%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/google",
|
"documentation": "https://www.home-assistant.io/integrations/google",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["googleapiclient"],
|
"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:
|
) -> ConfigFlowResult:
|
||||||
"""Manage the options."""
|
"""Manage the options."""
|
||||||
options: dict[str, Any] | MappingProxyType[str, Any] = self.config_entry.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 is not None:
|
||||||
if user_input[CONF_RECOMMENDED] == self.last_rendered_recommended:
|
if user_input[CONF_RECOMMENDED] == self.last_rendered_recommended:
|
||||||
if user_input[CONF_LLM_HASS_API] == "none":
|
if user_input[CONF_LLM_HASS_API] == "none":
|
||||||
user_input.pop(CONF_LLM_HASS_API)
|
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
|
# Re-render the options again, now with the recommended options shown/hidden
|
||||||
self.last_rendered_recommended = user_input[CONF_RECOMMENDED]
|
self.last_rendered_recommended = user_input[CONF_RECOMMENDED]
|
||||||
|
|
||||||
options = {
|
options = user_input
|
||||||
CONF_RECOMMENDED: user_input[CONF_RECOMMENDED],
|
|
||||||
CONF_PROMPT: user_input[CONF_PROMPT],
|
|
||||||
CONF_LLM_HASS_API: user_input[CONF_LLM_HASS_API],
|
|
||||||
}
|
|
||||||
|
|
||||||
schema = await google_generative_ai_config_option_schema(
|
schema = await google_generative_ai_config_option_schema(
|
||||||
self.hass, options, self._genai_client
|
self.hass, options, self._genai_client
|
||||||
)
|
)
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="init",
|
step_id="init", data_schema=vol.Schema(schema), errors=errors
|
||||||
data_schema=vol.Schema(schema),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -40,9 +40,13 @@
|
|||||||
"enable_google_search_tool": "Enable Google Search tool"
|
"enable_google_search_tool": "Enable Google Search tool"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"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": {
|
"services": {
|
||||||
|
@@ -265,6 +265,11 @@
|
|||||||
"version_latest": {
|
"version_latest": {
|
||||||
"name": "Newest version"
|
"name": "Newest version"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"update": {
|
||||||
|
"name": "[%key:component::update::title%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
@@ -39,7 +39,7 @@ from .entity import (
|
|||||||
from .update_helper import update_addon, update_core
|
from .update_helper import update_addon, update_core
|
||||||
|
|
||||||
ENTITY_DESCRIPTION = UpdateEntityDescription(
|
ENTITY_DESCRIPTION = UpdateEntityDescription(
|
||||||
name="Update",
|
translation_key="update",
|
||||||
key=ATTR_VERSION_LATEST,
|
key=ATTR_VERSION_LATEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
ATTR_PASSWORD = "password"
|
ATTR_PASSWORD = "password"
|
||||||
ATTR_USERNAME = "username"
|
ATTR_USERNAME = "username"
|
||||||
|
ATTR_DESTINATION_POSITION = "destination_position"
|
||||||
ATTR_QUEUE_IDS = "queue_ids"
|
ATTR_QUEUE_IDS = "queue_ids"
|
||||||
DOMAIN = "heos"
|
DOMAIN = "heos"
|
||||||
ENTRY_TITLE = "HEOS System"
|
ENTRY_TITLE = "HEOS System"
|
||||||
@@ -9,6 +10,7 @@ SERVICE_GET_QUEUE = "get_queue"
|
|||||||
SERVICE_GROUP_VOLUME_SET = "group_volume_set"
|
SERVICE_GROUP_VOLUME_SET = "group_volume_set"
|
||||||
SERVICE_GROUP_VOLUME_DOWN = "group_volume_down"
|
SERVICE_GROUP_VOLUME_DOWN = "group_volume_down"
|
||||||
SERVICE_GROUP_VOLUME_UP = "group_volume_up"
|
SERVICE_GROUP_VOLUME_UP = "group_volume_up"
|
||||||
|
SERVICE_MOVE_QUEUE_ITEM = "move_queue_item"
|
||||||
SERVICE_REMOVE_FROM_QUEUE = "remove_from_queue"
|
SERVICE_REMOVE_FROM_QUEUE = "remove_from_queue"
|
||||||
SERVICE_SIGN_IN = "sign_in"
|
SERVICE_SIGN_IN = "sign_in"
|
||||||
SERVICE_SIGN_OUT = "sign_out"
|
SERVICE_SIGN_OUT = "sign_out"
|
||||||
|
@@ -6,6 +6,9 @@
|
|||||||
"remove_from_queue": {
|
"remove_from_queue": {
|
||||||
"service": "mdi:playlist-remove"
|
"service": "mdi:playlist-remove"
|
||||||
},
|
},
|
||||||
|
"move_queue_item": {
|
||||||
|
"service": "mdi:playlist-edit"
|
||||||
|
},
|
||||||
"group_volume_set": {
|
"group_volume_set": {
|
||||||
"service": "mdi:volume-medium"
|
"service": "mdi:volume-medium"
|
||||||
},
|
},
|
||||||
|
@@ -479,6 +479,13 @@ class HeosMediaPlayer(CoordinatorEntity[HeosCoordinator], MediaPlayerEntity):
|
|||||||
"""Remove items from the queue."""
|
"""Remove items from the queue."""
|
||||||
await self._player.remove_from_queue(queue_ids)
|
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
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if the device is available."""
|
"""Return True if the device is available."""
|
||||||
|
@@ -19,6 +19,7 @@ from homeassistant.helpers import (
|
|||||||
from homeassistant.helpers.typing import VolDictType, VolSchemaType
|
from homeassistant.helpers.typing import VolDictType, VolSchemaType
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
ATTR_DESTINATION_POSITION,
|
||||||
ATTR_PASSWORD,
|
ATTR_PASSWORD,
|
||||||
ATTR_QUEUE_IDS,
|
ATTR_QUEUE_IDS,
|
||||||
ATTR_USERNAME,
|
ATTR_USERNAME,
|
||||||
@@ -27,6 +28,7 @@ from .const import (
|
|||||||
SERVICE_GROUP_VOLUME_DOWN,
|
SERVICE_GROUP_VOLUME_DOWN,
|
||||||
SERVICE_GROUP_VOLUME_SET,
|
SERVICE_GROUP_VOLUME_SET,
|
||||||
SERVICE_GROUP_VOLUME_UP,
|
SERVICE_GROUP_VOLUME_UP,
|
||||||
|
SERVICE_MOVE_QUEUE_ITEM,
|
||||||
SERVICE_REMOVE_FROM_QUEUE,
|
SERVICE_REMOVE_FROM_QUEUE,
|
||||||
SERVICE_SIGN_IN,
|
SERVICE_SIGN_IN,
|
||||||
SERVICE_SIGN_OUT,
|
SERVICE_SIGN_OUT,
|
||||||
@@ -87,6 +89,16 @@ REMOVE_FROM_QUEUE_SCHEMA: Final[VolDictType] = {
|
|||||||
GROUP_VOLUME_SET_SCHEMA: Final[VolDictType] = {
|
GROUP_VOLUME_SET_SCHEMA: Final[VolDictType] = {
|
||||||
vol.Required(ATTR_MEDIA_VOLUME_LEVEL): cv.small_float
|
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 = (
|
MEDIA_PLAYER_ENTITY_SERVICES: Final = (
|
||||||
# Player queue services
|
# Player queue services
|
||||||
@@ -96,6 +108,9 @@ MEDIA_PLAYER_ENTITY_SERVICES: Final = (
|
|||||||
EntityServiceDescription(
|
EntityServiceDescription(
|
||||||
SERVICE_REMOVE_FROM_QUEUE, "async_remove_from_queue", REMOVE_FROM_QUEUE_SCHEMA
|
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
|
# Group volume services
|
||||||
EntityServiceDescription(
|
EntityServiceDescription(
|
||||||
SERVICE_GROUP_VOLUME_SET,
|
SERVICE_GROUP_VOLUME_SET,
|
||||||
|
@@ -17,6 +17,26 @@ remove_from_queue:
|
|||||||
multiple: true
|
multiple: true
|
||||||
type: number
|
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:
|
group_volume_set:
|
||||||
target:
|
target:
|
||||||
entity:
|
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": {
|
"group_volume_down": {
|
||||||
"name": "Turn down group volume",
|
"name": "Turn down group volume",
|
||||||
"description": "Turns down the group volume."
|
"description": "Turns down the group volume."
|
||||||
|
@@ -34,9 +34,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"invalid_username": "Failed to sign into Hive. Your email address is not recognised.",
|
"invalid_username": "Failed to sign in to Hive. Your email address is not recognised.",
|
||||||
"invalid_password": "Failed to sign into Hive. Incorrect password, please try again.",
|
"invalid_password": "Failed to sign in to Hive. Incorrect password, please try again.",
|
||||||
"invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.",
|
"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.",
|
"no_internet_available": "An Internet connection is required to connect to Hive.",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
},
|
},
|
||||||
|
@@ -74,6 +74,19 @@ class HomeConnectApplianceData:
|
|||||||
self.settings.update(other.settings)
|
self.settings.update(other.settings)
|
||||||
self.status.update(other.status)
|
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(
|
class HomeConnectCoordinator(
|
||||||
DataUpdateCoordinator[dict[str, HomeConnectApplianceData]]
|
DataUpdateCoordinator[dict[str, HomeConnectApplianceData]]
|
||||||
@@ -362,15 +375,7 @@ class HomeConnectCoordinator(
|
|||||||
model=appliance.vib,
|
model=appliance.vib,
|
||||||
)
|
)
|
||||||
if appliance.ha_id not in self.data:
|
if appliance.ha_id not in self.data:
|
||||||
self.data[appliance.ha_id] = HomeConnectApplianceData(
|
self.data[appliance.ha_id] = HomeConnectApplianceData.empty(appliance)
|
||||||
commands=set(),
|
|
||||||
events={},
|
|
||||||
info=appliance,
|
|
||||||
options={},
|
|
||||||
programs=[],
|
|
||||||
settings={},
|
|
||||||
status={},
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.data[appliance.ha_id].info.connected = appliance.connected
|
self.data[appliance.ha_id].info.connected = appliance.connected
|
||||||
old_appliances.remove(appliance.ha_id)
|
old_appliances.remove(appliance.ha_id)
|
||||||
@@ -406,6 +411,15 @@ class HomeConnectCoordinator(
|
|||||||
name=appliance.name,
|
name=appliance.name,
|
||||||
model=appliance.vib,
|
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:
|
try:
|
||||||
settings = {
|
settings = {
|
||||||
setting.key: setting
|
setting.key: setting
|
||||||
|
@@ -7,6 +7,6 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/home_connect",
|
"documentation": "https://www.home-assistant.io/integrations/home_connect",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["aiohomeconnect"],
|
"loggers": ["aiohomeconnect"],
|
||||||
"requirements": ["aiohomeconnect==0.16.3"],
|
"requirements": ["aiohomeconnect==0.17.0"],
|
||||||
"single_config_entry": true
|
"single_config_entry": true
|
||||||
}
|
}
|
||||||
|
@@ -487,9 +487,9 @@
|
|||||||
},
|
},
|
||||||
"warming_level": {
|
"warming_level": {
|
||||||
"options": {
|
"options": {
|
||||||
"cooking_oven_enum_type_warming_level_low": "Low",
|
"cooking_oven_enum_type_warming_level_low": "[%key:common::state::low%]",
|
||||||
"cooking_oven_enum_type_warming_level_medium": "Medium",
|
"cooking_oven_enum_type_warming_level_medium": "[%key:common::state::medium%]",
|
||||||
"cooking_oven_enum_type_warming_level_high": "High"
|
"cooking_oven_enum_type_warming_level_high": "[%key:common::state::high%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"washer_temperature": {
|
"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_1400": "1400 rpm",
|
||||||
"laundry_care_washer_enum_type_spin_speed_r_p_m_1600": "1600 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_off": "[%key:common::state::off%]",
|
||||||
"laundry_care_washer_enum_type_spin_speed_ul_low": "Low",
|
"laundry_care_washer_enum_type_spin_speed_ul_low": "[%key:common::state::low%]",
|
||||||
"laundry_care_washer_enum_type_spin_speed_ul_medium": "Medium",
|
"laundry_care_washer_enum_type_spin_speed_ul_medium": "[%key:common::state::medium%]",
|
||||||
"laundry_care_washer_enum_type_spin_speed_ul_high": "High"
|
"laundry_care_washer_enum_type_spin_speed_ul_high": "[%key:common::state::high%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vario_perfect": {
|
"vario_perfect": {
|
||||||
@@ -1468,9 +1468,9 @@
|
|||||||
"warming_level": {
|
"warming_level": {
|
||||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::cooking_oven_option_warming_level::name%]",
|
"name": "[%key:component::home_connect::services::set_program_and_options::fields::cooking_oven_option_warming_level::name%]",
|
||||||
"state": {
|
"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_low": "[%key:common::state::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_medium": "[%key:common::state::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_high": "[%key:common::state::high%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"washer_temperature": {
|
"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_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_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_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_low": "[%key:common::state::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_medium": "[%key:common::state::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_high": "[%key:common::state::high%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vario_perfect": {
|
"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
|
# Postpone loading the config entry if the device is missing
|
||||||
device_path = entry.data[DEVICE]
|
device_path = entry.data[DEVICE]
|
||||||
if not await hass.async_add_executor_job(os.path.exists, device_path):
|
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"])
|
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.components.hardware.models import HardwareInfo, USBInfo
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
|
||||||
|
from .config_flow import HomeAssistantSkyConnectConfigFlow
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .util import get_hardware_variant
|
from .util import get_hardware_variant
|
||||||
|
|
||||||
DOCUMENTATION_URL = "https://skyconnect.home-assistant.io/documentation/"
|
DOCUMENTATION_URL = "https://skyconnect.home-assistant.io/documentation/"
|
||||||
|
EXPECTED_ENTRY_VERSION = (
|
||||||
|
HomeAssistantSkyConnectConfigFlow.VERSION,
|
||||||
|
HomeAssistantSkyConnectConfigFlow.MINOR_VERSION,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_info(hass: HomeAssistant) -> list[HardwareInfo]:
|
def async_info(hass: HomeAssistant) -> list[HardwareInfo]:
|
||||||
"""Return board info."""
|
"""Return board info."""
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
HardwareInfo(
|
HardwareInfo(
|
||||||
board=None,
|
board=None,
|
||||||
@@ -31,4 +35,6 @@ def async_info(hass: HomeAssistant) -> list[HardwareInfo]:
|
|||||||
url=DOCUMENTATION_URL,
|
url=DOCUMENTATION_URL,
|
||||||
)
|
)
|
||||||
for entry in entries
|
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%]",
|
"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%]"
|
"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.
|
# e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit.
|
||||||
# Can be 0 - 2 (Off, Heat, Cool)
|
# 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)
|
target = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET)
|
||||||
if target == HeatingCoolingTargetValues.OFF:
|
|
||||||
return HVACAction.IDLE
|
|
||||||
|
|
||||||
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_CURRENT)
|
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_CURRENT)
|
||||||
current_hass_value = CURRENT_MODE_HOMEKIT_TO_HASS.get(value)
|
current_hass_value = CURRENT_MODE_HOMEKIT_TO_HASS.get(value)
|
||||||
|
|
||||||
@@ -679,6 +673,12 @@ class HomeKitClimateEntity(HomeKitBaseClimateEntity):
|
|||||||
):
|
):
|
||||||
return HVACAction.FAN
|
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
|
return current_hass_value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"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": {
|
"data": {
|
||||||
"username": "[%key:common::config_flow::data::username%]",
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
|
@@ -3,25 +3,34 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Awaitable, Callable
|
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 import Application, Request, StreamResponse, middleware
|
||||||
from aiohttp.web_exceptions import HTTPException
|
from aiohttp.web_exceptions import HTTPException
|
||||||
|
from multidict import CIMultiDict, istr
|
||||||
|
|
||||||
from homeassistant.core import callback
|
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
|
@callback
|
||||||
def setup_headers(app: Application, use_x_frame_options: bool) -> None:
|
def setup_headers(app: Application, use_x_frame_options: bool) -> None:
|
||||||
"""Create headers middleware for the app."""
|
"""Create headers middleware for the app."""
|
||||||
|
|
||||||
added_headers = {
|
added_headers = CIMultiDict(
|
||||||
"Referrer-Policy": "no-referrer",
|
{
|
||||||
"X-Content-Type-Options": "nosniff",
|
REFERRER_POLICY: "no-referrer",
|
||||||
"Server": "", # Empty server header, to prevent aiohttp of setting one.
|
X_CONTENT_TYPE_OPTIONS: "nosniff",
|
||||||
}
|
hdrs.SERVER: "", # Empty server header, to prevent aiohttp of setting one.
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if use_x_frame_options:
|
if use_x_frame_options:
|
||||||
added_headers["X-Frame-Options"] = "SAMEORIGIN"
|
added_headers[X_FRAME_OPTIONS] = "SAMEORIGIN"
|
||||||
|
|
||||||
@middleware
|
@middleware
|
||||||
async def headers_middleware(
|
async def headers_middleware(
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
"requirements": [
|
"requirements": [
|
||||||
"huawei-lte-api==1.10.0",
|
"huawei-lte-api==1.10.0",
|
||||||
"stringcase==1.2.0",
|
"stringcase==1.2.0",
|
||||||
"url-normalize==1.4.3"
|
"url-normalize==2.2.0"
|
||||||
],
|
],
|
||||||
"ssdp": [
|
"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.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
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 homeassistant.util import color as color_util
|
||||||
|
|
||||||
from ..bridge import HueBridge
|
from ..bridge import HueBridge
|
||||||
@@ -44,6 +45,9 @@ FALLBACK_MIN_KELVIN = 6500
|
|||||||
FALLBACK_MAX_KELVIN = 2000
|
FALLBACK_MAX_KELVIN = 2000
|
||||||
FALLBACK_KELVIN = 5800 # halfway
|
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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@@ -233,6 +237,23 @@ class HueLight(HueBaseEntity, LightEntity):
|
|||||||
self._color_temp_active = color_temp is not None
|
self._color_temp_active = color_temp is not None
|
||||||
flash = kwargs.get(ATTR_FLASH)
|
flash = kwargs.get(ATTR_FLASH)
|
||||||
effect = effect_str = kwargs.get(ATTR_EFFECT)
|
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:
|
if effect_str == EFFECT_OFF:
|
||||||
# ignore effect if set to "off" and we have no effect active
|
# ignore effect if set to "off" and we have no effect active
|
||||||
# the special effect "off" is only used to stop an active effect
|
# the special effect "off" is only used to stop an active effect
|
||||||
|
@@ -5,5 +5,6 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
|
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
|
"quality_scale": "silver",
|
||||||
"requirements": ["imgw_pib==1.0.10"]
|
"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": {
|
"data_description": {
|
||||||
"host": "Hostname or IP-address of the Intergas gateway.",
|
"host": "Hostname or IP-address of the Intergas gateway.",
|
||||||
"username": "The username to log into the gateway. This is `admin` in most cases.",
|
"username": "The username to log in to 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."
|
"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": {
|
"dhcp_auth": {
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
"password": "[%key:common::config_flow::data::password%]"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"username": "The username to log into the gateway. This is `admin` in most cases.",
|
"username": "[%key:component::incomfort::config::step::user::data_description::username%]",
|
||||||
"password": "The password to log into the gateway, is printed at the bottom of the Gateway or is `intergas` for some older devices."
|
"password": "[%key:component::incomfort::config::step::user::data_description::password%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dhcp_confirm": {
|
"dhcp_confirm": {
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from inkbird_ble import INKBIRDBluetoothDeviceData, SensorUpdate
|
from inkbird_ble import INKBIRDBluetoothDeviceData, SensorUpdate
|
||||||
@@ -9,13 +10,16 @@ from inkbird_ble import INKBIRDBluetoothDeviceData, SensorUpdate
|
|||||||
from homeassistant.components.bluetooth import (
|
from homeassistant.components.bluetooth import (
|
||||||
BluetoothScanningMode,
|
BluetoothScanningMode,
|
||||||
BluetoothServiceInfo,
|
BluetoothServiceInfo,
|
||||||
|
BluetoothServiceInfoBleak,
|
||||||
|
async_ble_device_from_address,
|
||||||
)
|
)
|
||||||
from homeassistant.components.bluetooth.passive_update_processor import (
|
from homeassistant.components.bluetooth.active_update_processor import (
|
||||||
PassiveBluetoothProcessorCoordinator,
|
ActiveBluetoothProcessorCoordinator,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
|
|
||||||
from .const import CONF_DEVICE_TYPE, DOMAIN
|
from .const import CONF_DEVICE_TYPE, DOMAIN
|
||||||
|
|
||||||
@@ -23,34 +27,87 @@ PLATFORMS: list[Platform] = [Platform.SENSOR]
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up INKBIRD BLE device from a config entry."""
|
"""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)
|
device_type: str | None = entry.data.get(CONF_DEVICE_TYPE)
|
||||||
data = INKBIRDBluetoothDeviceData(device_type)
|
data = INKBIRDBluetoothDeviceData(device_type)
|
||||||
|
coordinator = INKBIRDActiveBluetoothProcessorCoordinator(hass, entry, data)
|
||||||
@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,
|
|
||||||
)
|
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
# only start after all platforms have had a chance to subscribe
|
# only start after all platforms have had a chance to subscribe
|
||||||
|
@@ -40,5 +40,5 @@
|
|||||||
"dependencies": ["bluetooth_adapters"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/inkbird",
|
"documentation": "https://www.home-assistant.io/integrations/inkbird",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"requirements": ["inkbird-ble==0.9.0"]
|
"requirements": ["inkbird-ble==0.10.1"]
|
||||||
}
|
}
|
||||||
|
@@ -123,7 +123,7 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"off": "[%key:common::state::off%]",
|
"off": "[%key:common::state::off%]",
|
||||||
"slow": "[%key:component::iron_os::common::slow%]",
|
"slow": "[%key:component::iron_os::common::slow%]",
|
||||||
"medium": "Medium",
|
"medium": "[%key:common::state::medium%]",
|
||||||
"fast": "[%key:component::iron_os::common::fast%]"
|
"fast": "[%key:component::iron_os::common::fast%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/kulersky",
|
"documentation": "https://www.home-assistant.io/integrations/kulersky",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["bleak", "pykulersky"],
|
"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.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
CONF_DOMAIN,
|
CONF_DOMAIN,
|
||||||
CONF_ENTITIES,
|
CONF_ENTITIES,
|
||||||
CONF_SOURCE,
|
CONF_SOURCE,
|
||||||
@@ -49,6 +50,7 @@ DEVICE_CLASS_MAPPING = {
|
|||||||
pypck.lcn_defs.VarUnit.METERPERSECOND: SensorDeviceClass.SPEED,
|
pypck.lcn_defs.VarUnit.METERPERSECOND: SensorDeviceClass.SPEED,
|
||||||
pypck.lcn_defs.VarUnit.VOLT: SensorDeviceClass.VOLTAGE,
|
pypck.lcn_defs.VarUnit.VOLT: SensorDeviceClass.VOLTAGE,
|
||||||
pypck.lcn_defs.VarUnit.AMPERE: SensorDeviceClass.CURRENT,
|
pypck.lcn_defs.VarUnit.AMPERE: SensorDeviceClass.CURRENT,
|
||||||
|
pypck.lcn_defs.VarUnit.PPM: SensorDeviceClass.CO2,
|
||||||
}
|
}
|
||||||
|
|
||||||
UNIT_OF_MEASUREMENT_MAPPING = {
|
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.METERPERSECOND: UnitOfSpeed.METERS_PER_SECOND,
|
||||||
pypck.lcn_defs.VarUnit.VOLT: UnitOfElectricPotential.VOLT,
|
pypck.lcn_defs.VarUnit.VOLT: UnitOfElectricPotential.VOLT,
|
||||||
pypck.lcn_defs.VarUnit.AMPERE: UnitOfElectricCurrent.AMPERE,
|
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",
|
"documentation": "https://www.home-assistant.io/integrations/ld2410_ble",
|
||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_push",
|
"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"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/led_ble",
|
"documentation": "https://www.home-assistant.io/integrations/led_ble",
|
||||||
"iot_class": "local_polling",
|
"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": {
|
"fan_mode": {
|
||||||
"state": {
|
"state": {
|
||||||
"slow": "Slow",
|
"slow": "Slow",
|
||||||
"low": "Low",
|
"low": "[%key:common::state::low%]",
|
||||||
"mid": "Medium",
|
"mid": "[%key:common::state::medium%]",
|
||||||
"high": "High",
|
"high": "[%key:common::state::high%]",
|
||||||
"power": "[%key:component::lg_thinq::entity::sensor::current_job_mode::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%]"
|
"auto": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]"
|
||||||
}
|
}
|
||||||
@@ -303,7 +303,7 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"invalid": "Invalid",
|
"invalid": "Invalid",
|
||||||
"weak": "Weak",
|
"weak": "Weak",
|
||||||
"normal": "Normal",
|
"normal": "[%key:common::state::normal%]",
|
||||||
"strong": "Strong",
|
"strong": "Strong",
|
||||||
"very_strong": "Very strong"
|
"very_strong": "Very strong"
|
||||||
}
|
}
|
||||||
@@ -390,17 +390,17 @@
|
|||||||
"temperature_state": {
|
"temperature_state": {
|
||||||
"name": "[%key:component::sensor::entity_component::temperature::name%]",
|
"name": "[%key:component::sensor::entity_component::temperature::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"high": "High",
|
"high": "[%key:common::state::high%]",
|
||||||
"normal": "Good",
|
"normal": "Good",
|
||||||
"low": "Low"
|
"low": "[%key:common::state::low%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"temperature_state_for_location": {
|
"temperature_state_for_location": {
|
||||||
"name": "[%key:component::lg_thinq::entity::number::target_temperature_for_location::name%]",
|
"name": "[%key:component::lg_thinq::entity::number::target_temperature_for_location::name%]",
|
||||||
"state": {
|
"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%]",
|
"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": {
|
"current_state": {
|
||||||
@@ -607,7 +607,7 @@
|
|||||||
"intensive_dry": "Spot",
|
"intensive_dry": "Spot",
|
||||||
"macro": "Custom mode",
|
"macro": "Custom mode",
|
||||||
"mop": "Mop",
|
"mop": "Mop",
|
||||||
"normal": "Normal",
|
"normal": "[%key:common::state::normal%]",
|
||||||
"off": "[%key:common::state::off%]",
|
"off": "[%key:common::state::off%]",
|
||||||
"quiet_humidity": "Silent",
|
"quiet_humidity": "Silent",
|
||||||
"rapid_humidity": "Jet",
|
"rapid_humidity": "Jet",
|
||||||
@@ -626,7 +626,7 @@
|
|||||||
"auto": "Low power",
|
"auto": "Low power",
|
||||||
"high": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::high%]",
|
"high": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::high%]",
|
||||||
"mop": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::mop%]",
|
"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%]",
|
"off": "[%key:common::state::off%]",
|
||||||
"turbo": "[%key:component::lg_thinq::entity::select::wind_strength::state::power%]"
|
"turbo": "[%key:component::lg_thinq::entity::select::wind_strength::state::power%]"
|
||||||
}
|
}
|
||||||
@@ -653,7 +653,7 @@
|
|||||||
"heavy": "Intensive",
|
"heavy": "Intensive",
|
||||||
"delicate": "Delicate",
|
"delicate": "Delicate",
|
||||||
"turbo": "[%key:component::lg_thinq::entity::select::wind_strength::state::power%]",
|
"turbo": "[%key:component::lg_thinq::entity::select::wind_strength::state::power%]",
|
||||||
"normal": "Normal",
|
"normal": "[%key:common::state::normal%]",
|
||||||
"rinse": "Rinse",
|
"rinse": "Rinse",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
"express": "Express",
|
"express": "Express",
|
||||||
@@ -781,8 +781,8 @@
|
|||||||
"name": "Battery",
|
"name": "Battery",
|
||||||
"state": {
|
"state": {
|
||||||
"high": "Full",
|
"high": "Full",
|
||||||
"mid": "Medium",
|
"mid": "[%key:common::state::medium%]",
|
||||||
"low": "Low",
|
"low": "[%key:common::state::low%]",
|
||||||
"warning": "Empty"
|
"warning": "Empty"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -876,9 +876,9 @@
|
|||||||
"name": "Speed",
|
"name": "Speed",
|
||||||
"state": {
|
"state": {
|
||||||
"slow": "[%key:component::lg_thinq::entity::climate::climate_air_conditioner::state_attributes::fan_mode::state::slow%]",
|
"slow": "[%key:component::lg_thinq::entity::climate::climate_air_conditioner::state_attributes::fan_mode::state::slow%]",
|
||||||
"low": "Low",
|
"low": "[%key:common::state::low%]",
|
||||||
"mid": "Medium",
|
"mid": "[%key:common::state::medium%]",
|
||||||
"high": "High",
|
"high": "[%key:common::state::high%]",
|
||||||
"power": "Turbo",
|
"power": "Turbo",
|
||||||
"turbo": "[%key:component::lg_thinq::entity::select::wind_strength::state::power%]",
|
"turbo": "[%key:component::lg_thinq::entity::select::wind_strength::state::power%]",
|
||||||
"auto": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]",
|
"auto": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]",
|
||||||
|
@@ -118,9 +118,9 @@
|
|||||||
"brightness_level": {
|
"brightness_level": {
|
||||||
"name": "Panel brightness",
|
"name": "Panel brightness",
|
||||||
"state": {
|
"state": {
|
||||||
"low": "Low",
|
"low": "[%key:common::state::low%]",
|
||||||
"medium": "Medium",
|
"medium": "[%key:common::state::medium%]",
|
||||||
"high": "High"
|
"high": "[%key:common::state::high%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
|
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["ical"],
|
"loggers": ["ical"],
|
||||||
"requirements": ["ical==9.0.3"]
|
"requirements": ["ical==9.1.0"]
|
||||||
}
|
}
|
||||||
|
@@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/local_todo",
|
"documentation": "https://www.home-assistant.io/integrations/local_todo",
|
||||||
"iot_class": "local_polling",
|
"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 collections.abc import Callable, Mapping
|
||||||
from dataclasses import dataclass
|
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 propcache.api import cached_property
|
||||||
from sqlalchemy.engine.row import Row
|
from sqlalchemy.engine.row import Row
|
||||||
@@ -114,6 +114,7 @@ DATA_POS: Final = 11
|
|||||||
CONTEXT_POS: Final = 12
|
CONTEXT_POS: Final = 12
|
||||||
|
|
||||||
|
|
||||||
|
@final # Final to allow direct checking of the type instead of using isinstance
|
||||||
class EventAsRow(NamedTuple):
|
class EventAsRow(NamedTuple):
|
||||||
"""Convert an event to a row.
|
"""Convert an event to a row.
|
||||||
|
|
||||||
|
@@ -265,4 +265,61 @@ DISCOVERY_SCHEMAS = [
|
|||||||
entity_class=MatterBinarySensor,
|
entity_class=MatterBinarySensor,
|
||||||
required_attributes=(clusters.SmokeCoAlarm.Attributes.InterconnectCOAlarm,),
|
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
|
# convert the value from the primary attribute to the value used by HA
|
||||||
measurement_to_ha: Callable[[Any], Any] | None = None
|
measurement_to_ha: Callable[[Any], Any] | None = None
|
||||||
ha_to_native_value: Callable[[Any], Any] | None = None
|
ha_to_native_value: Callable[[Any], Any] | None = None
|
||||||
|
command_timeout: int | None = None
|
||||||
|
|
||||||
|
|
||||||
class MatterEntity(Entity):
|
class MatterEntity(Entity):
|
||||||
|
@@ -71,6 +71,15 @@
|
|||||||
},
|
},
|
||||||
"battery_replacement_description": {
|
"battery_replacement_description": {
|
||||||
"default": "mdi:battery-sync-outline"
|
"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": {
|
"switch": {
|
||||||
@@ -80,6 +89,9 @@
|
|||||||
"on": "mdi:lock",
|
"on": "mdi:lock",
|
||||||
"off": "mdi:lock-off"
|
"off": "mdi:lock-off"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"evse_charging_switch": {
|
||||||
|
"default": "mdi:ev-station"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -77,6 +77,25 @@ OPERATIONAL_STATE_MAP = {
|
|||||||
clusters.RvcOperationalState.Enums.OperationalStateEnum.kDocked: "docked",
|
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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@@ -904,4 +923,77 @@ DISCOVERY_SCHEMAS = [
|
|||||||
# don't discover this entry if the supported state list is empty
|
# don't discover this entry if the supported state list is empty
|
||||||
secondary_value_is_not=[],
|
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": {
|
"muted": {
|
||||||
"name": "Muted"
|
"name": "Muted"
|
||||||
|
},
|
||||||
|
"evse_charging_status": {
|
||||||
|
"name": "Charging status"
|
||||||
|
},
|
||||||
|
"evse_plug": {
|
||||||
|
"name": "Plug state"
|
||||||
|
},
|
||||||
|
"evse_supply_charging_state": {
|
||||||
|
"name": "Supply charging state"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
@@ -135,9 +144,9 @@
|
|||||||
"state_attributes": {
|
"state_attributes": {
|
||||||
"preset_mode": {
|
"preset_mode": {
|
||||||
"state": {
|
"state": {
|
||||||
"low": "Low",
|
"low": "[%key:common::state::low%]",
|
||||||
"medium": "Medium",
|
"medium": "[%key:common::state::medium%]",
|
||||||
"high": "High",
|
"high": "[%key:common::state::high%]",
|
||||||
"auto": "Auto",
|
"auto": "Auto",
|
||||||
"natural_wind": "Natural wind",
|
"natural_wind": "Natural wind",
|
||||||
"sleep_wind": "Sleep wind"
|
"sleep_wind": "Sleep wind"
|
||||||
@@ -189,9 +198,9 @@
|
|||||||
"sensitivity_level": {
|
"sensitivity_level": {
|
||||||
"name": "Sensitivity",
|
"name": "Sensitivity",
|
||||||
"state": {
|
"state": {
|
||||||
"low": "[%key:component::matter::entity::fan::fan::state_attributes::preset_mode::state::low%]",
|
"low": "[%key:common::state::low%]",
|
||||||
"standard": "Standard",
|
"standard": "Standard",
|
||||||
"high": "[%key:component::matter::entity::fan::fan::state_attributes::preset_mode::state::high%]"
|
"high": "[%key:common::state::high%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"startup_on_off": {
|
"startup_on_off": {
|
||||||
@@ -213,7 +222,7 @@
|
|||||||
"name": "Number of rinses",
|
"name": "Number of rinses",
|
||||||
"state": {
|
"state": {
|
||||||
"off": "[%key:common::state::off%]",
|
"off": "[%key:common::state::off%]",
|
||||||
"normal": "Normal",
|
"normal": "[%key:common::state::normal%]",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"max": "Max"
|
"max": "Max"
|
||||||
}
|
}
|
||||||
@@ -229,8 +238,8 @@
|
|||||||
"contamination_state": {
|
"contamination_state": {
|
||||||
"name": "Contamination state",
|
"name": "Contamination state",
|
||||||
"state": {
|
"state": {
|
||||||
"normal": "Normal",
|
"normal": "[%key:common::state::normal%]",
|
||||||
"low": "[%key:component::matter::entity::fan::fan::state_attributes::preset_mode::state::low%]",
|
"low": "[%key:common::state::low%]",
|
||||||
"warning": "Warning",
|
"warning": "Warning",
|
||||||
"critical": "Critical"
|
"critical": "Critical"
|
||||||
}
|
}
|
||||||
@@ -278,6 +287,42 @@
|
|||||||
},
|
},
|
||||||
"current_phase": {
|
"current_phase": {
|
||||||
"name": "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": {
|
"switch": {
|
||||||
@@ -289,6 +334,9 @@
|
|||||||
},
|
},
|
||||||
"child_lock": {
|
"child_lock": {
|
||||||
"name": "Child lock"
|
"name": "Child lock"
|
||||||
|
},
|
||||||
|
"evse_charging_switch": {
|
||||||
|
"name": "Enable charging"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vacuum": {
|
"vacuum": {
|
||||||
|
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from chip.clusters import Objects as clusters
|
from chip.clusters import Objects as clusters
|
||||||
|
from chip.clusters.Objects import ClusterCommand, NullValue
|
||||||
from matter_server.client.models import device_types
|
from matter_server.client.models import device_types
|
||||||
|
|
||||||
from homeassistant.components.switch import (
|
from homeassistant.components.switch import (
|
||||||
@@ -22,6 +24,13 @@ from .entity import MatterEntity, MatterEntityDescription
|
|||||||
from .helpers import get_matter
|
from .helpers import get_matter
|
||||||
from .models import MatterDiscoverySchema
|
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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
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)
|
@dataclass(frozen=True)
|
||||||
class MatterNumericSwitchEntityDescription(
|
class MatterNumericSwitchEntityDescription(
|
||||||
SwitchEntityDescription, MatterEntityDescription
|
SwitchEntityDescription, MatterEntityDescription
|
||||||
@@ -194,4 +263,26 @@ DISCOVERY_SCHEMAS = [
|
|||||||
),
|
),
|
||||||
vendor_id=(4874,),
|
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