Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions homeassistant/components/gatus/sensor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,6 +33,16 @@ class GatusSensorEntityDescription(SensorEntityDescription):
]


DNS_RCODE_MAP = {
"NOERROR": "no_error",
"FORMERR": "format_error",
"SERVFAIL": "server_failure",
"NXDOMAIN": "non_existent_domain",
"NOTIMP": "not_implemented",
"REFUSED": "refused",
}


SENSOR_TYPES: tuple[GatusSensorEntityDescription, ...] = (
GatusSensorEntityDescription(
key="response_time",
Expand DownExpand Up@@ -79,6 +89,19 @@ class GatusSensorEntityDescription(SensorEntityDescription):
else None
),
),
GatusSensorEntityDescription(
key="dns_rcode",
translation_key="dns_rcode",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda coordinator, endpoint: (
DNS_RCODE_MAP.get(
endpoint.results[-1].dns_rcode,
endpoint.results[-1].dns_rcode.lower(),
)
if endpoint.results and endpoint.results[-1].dns_rcode is not None
else None
),
),
)


Expand All@@ -94,9 +117,16 @@ async def async_setup_entry(
GatusEndpointSensor(coordinator, entry, endpoint_key, description)
for endpoint_key, endpoint in coordinator.data.items()
for description in SENSOR_TYPES
if description.key != "certificate_expiration"
or (
endpoint.results and endpoint.results[-1].certificate_expiration is not None
if (
description.key != "certificate_expiration"
or (
endpoint.results
and endpoint.results[-1].certificate_expiration is not None
)
)
and (
description.key != "dns_rcode"
or (endpoint.results and endpoint.results[-1].dns_rcode is not None)
)
)

Expand Down
11 changes: 11 additions & 0 deletions homeassistant/components/gatus/strings.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,17 @@
"certificate_expiration": {
"name": "Certificate expiration"
},
"dns_rcode": {
"name": "DNS response code",
"state": {
"format_error": "Format error",
"no_error": "No error",
"non_existent_domain": "Non-existent domain",
"not_implemented": "Not implemented",
"refused": "Refused",
"server_failure": "Server failure"
}
},
"last_event": {
"name": "Last event",
"state": {
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/hue/strings.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -146,6 +146,9 @@
"light_sensor_enabled": {
"name": "Light sensor enabled"
},
"motion_aware": {
"name": "MotionAware"
},
"motion_sensor_enabled": {
"name": "Motion sensor enabled"
}
Expand Down
56 changes: 51 additions & 5 deletions homeassistant/components/hue/switch.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,12 @@
from typing import Any, override

from aiohue.v2 import HueBridgeV2
from aiohue.v2.controllers.config import BehaviorInstance, BehaviorInstanceController
from aiohue.v2.controllers.config import (
BehaviorInstance,
BehaviorInstanceController,
MotionAreaConfiguration,
MotionAreaConfigurationController,
)
from aiohue.v2.controllers.events import EventType
from aiohue.v2.controllers.sensors import (
LightLevel,
Expand All@@ -23,9 +28,10 @@
from homeassistant.const import EntityCategory, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .bridge import HueConfigEntry
from .bridge import HueBridge, HueConfigEntry
from .const import DOMAIN
from .v2.entity import HueBaseEntity

Expand All@@ -47,17 +53,20 @@ async def async_setup_entry(
def register_items(
controller: BehaviorInstanceController
| LightLevelController
| MotionAreaConfigurationController
| MotionController,
switch_class: type[
HueBehaviorInstanceEnabledEntity
| HueLightSensorEnabledEntity
| HueMotionAreaConfigurationEnabledEntity
| HueMotionSensorEnabledEntity
],
resource_filter: Callable[[Any], bool] | None = None,
):
@callback
def async_add_entity(
event_type: EventType, resource: BehaviorInstance | LightLevel | Motion
event_type: EventType,
resource: BehaviorInstance | LightLevel | MotionAreaConfiguration | Motion,
) -> None:
"""Add entity from Hue resource."""
if resource_filter is not None and not resource_filter(resource):
Expand DownExpand Up@@ -107,13 +116,21 @@ def is_user_automation(resource: BehaviorInstance) -> bool:
HueBehaviorInstanceEnabledEntity,
is_user_automation,
)
register_items(
api.config.motion_area_configuration, HueMotionAreaConfigurationEnabledEntity
)


class HueResourceEnabledEntity(HueBaseEntity, SwitchEntity):
"""Represent a Switch entity from a Hue resource that toggles."""

controller: BehaviorInstanceController | LightLevelController | MotionController
resource: BehaviorInstance | LightLevel | Motion
controller: (
BehaviorInstanceController
| LightLevelController
| MotionAreaConfigurationController
| MotionController
)
resource: BehaviorInstance | LightLevel | MotionAreaConfiguration | Motion

entity_description = SwitchEntityDescription(
key="sensing_service_enabled",
Expand DownExpand Up@@ -190,6 +207,35 @@ async def async_turn_off(self, **kwargs: Any) -> None:
await self.bridge.async_request_call(self.controller.stop, self.resource.id)


class HueMotionAreaConfigurationEnabledEntity(HueResourceEnabledEntity):
"""Representation of a Switch entity to enable/disable a Hue MotionAware zone."""

controller: MotionAreaConfigurationController
resource: MotionAreaConfiguration

entity_description = SwitchEntityDescription(
key="motion_area_configuration",
device_class=SwitchDeviceClass.SWITCH,
entity_category=EntityCategory.CONFIG,
has_entity_name=True,
translation_key="motion_aware",
)

def __init__(
self,
bridge: HueBridge,
controller: MotionAreaConfigurationController,
resource: MotionAreaConfiguration,
) -> None:
"""Initialize the switch."""
super().__init__(bridge, controller, resource)
# link the switch to the group the MotionAware zone is associated with
self.hue_group = controller.get_group(resource.id)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.hue_group.id)},
)


class HueMotionSensorEnabledEntity(HueResourceEnabledEntity):
"""Representation of a Switch entity to enable/disable a Hue motion sensor."""

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/infrared/manifest.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/infrared",
"integration_type": "entity",
"quality_scale": "internal",
"requirements": ["infrared-protocols==9.0.0"]
"requirements": ["infrared-protocols==9.1.0"]
}
4 changes: 1 addition & 3 deletions homeassistant/components/knx/binary_sensor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,9 +68,7 @@ async def async_setup_entry(
KnxYamlBinarySensor(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(
Platform.BINARY_SENSOR
):
if ui_config := knx_module.config_store.get_entity_configs(Platform.BINARY_SENSOR):
entities.extend(
KnxUiBinarySensor(knx_module, unique_id, config)
for unique_id, config in ui_config.items()
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/knx/button.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,7 +49,7 @@ async def async_setup_entry(
KnxYamlButton(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.BUTTON):
if ui_config := knx_module.config_store.get_entity_configs(Platform.BUTTON):
entities.extend(
KnxUiButton(knx_module, unique_id, config)
for unique_id, config in ui_config.items()
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/knx/climate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -103,7 +103,7 @@ async def async_setup_entry(
KnxYamlClimate(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.CLIMATE):
if ui_config := knx_module.config_store.get_entity_configs(Platform.CLIMATE):
entities.extend(
KnxUiClimate(knx_module, unique_id, config)
for unique_id, config in ui_config.items()
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/knx/const.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,6 +124,7 @@
SERVICE_KNX_READ: Final = "read"

REPAIR_ISSUE_DATA_SECURE_GROUP_KEY: Final = "data_secure_group_key_issue"
REPAIR_ISSUE_ENTITY_VALIDATION_ERROR: Final = "entity_validation_error"
REPAIR_ISSUE_TELEGRAM_BACKEND_ERROR: Final = "telegram_backend_error"


Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/knx/cover.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -74,7 +74,7 @@ async def async_setup_entry(
KnxYamlCover(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.COVER):
if ui_config := knx_module.config_store.get_entity_configs(Platform.COVER):
entities.extend(
KnxUiCover(knx_module, unique_id, config)
for unique_id, config in ui_config.items()
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/knx/date.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,7 @@ async def async_setup_entry(
KnxYamlDate(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.DATE):
if ui_config := knx_module.config_store.get_entity_configs(Platform.DATE):
entities.extend(
KnxUiDate(knx_module, unique_id, config)
for unique_id, config in ui_config.items()
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/knx/datetime.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,7 +60,7 @@ async def async_setup_entry(
KnxYamlDateTime(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.DATETIME):
if ui_config := knx_module.config_store.get_entity_configs(Platform.DATETIME):
entities.extend(
KnxUiDateTime(knx_module, unique_id, config)
for unique_id, config in ui_config.items()
Expand Down
6 changes: 2 additions & 4 deletions homeassistant/components/knx/entity.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,7 +14,6 @@
CONF_ID,
CONF_NAME,
CONF_UNIQUE_ID,
EntityCategory,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
Expand DownExpand Up@@ -267,7 +266,6 @@ def __init__(

self._attr_name = entity_config[CONF_NAME]
self._attr_unique_id = unique_id
if entity_category := entity_config.get(CONF_ENTITY_CATEGORY):
self._attr_entity_category = EntityCategory(entity_category)
if device_info := entity_config.get(CONF_DEVICE_INFO):
self._attr_entity_category = entity_config[CONF_ENTITY_CATEGORY]
if device_info := entity_config[CONF_DEVICE_INFO]:
self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, device_info)})
2 changes: 1 addition & 1 deletion homeassistant/components/knx/fan.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -122,7 +122,7 @@ async def async_setup_entry(
KnxYamlFan(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.FAN):
if ui_config := knx_module.config_store.get_entity_configs(Platform.FAN):
entities.extend(
KnxUiFan(knx_module, unique_id, config)
for unique_id, config in ui_config.items()
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/knx/light.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,7 +83,7 @@ async def async_setup_entry(
KnxYamlLight(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.LIGHT):
if ui_config := knx_module.config_store.get_entity_configs(Platform.LIGHT):
entities.extend(
KnxUiLight(knx_module, unique_id, config)
for unique_id, config in ui_config.items()
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/knx/notify.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,7 +49,7 @@ async def async_setup_entry(
KnxYamlNotify(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.NOTIFY):
if ui_config := knx_module.config_store.get_entity_configs(Platform.NOTIFY):
entities.extend(
KnxUiNotify(knx_module, unique_id, config)
for unique_id, config in ui_config.items()
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/knx/number.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,7 +68,7 @@ async def async_setup_entry(
KnxYamlNumber(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.NUMBER):
if ui_config := knx_module.config_store.get_entity_configs(Platform.NUMBER):
entities.extend(
KnxUiNumber(knx_module, unique_id, config)
for unique_id, config in ui_config.items()
Expand Down
34 changes: 34 additions & 0 deletions homeassistant/components/knx/repairs.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,13 +2,15 @@

from collections.abc import Callable
from functools import partial
import logging
from typing import TYPE_CHECKING, Any, Final

import voluptuous as vol
from xknx.exceptions.exception import InvalidSecureConfiguration
from xknx.telegram import GroupAddress, IndividualAddress, Telegram

from homeassistant.components.repairs import RepairsFlow, RepairsFlowResult
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import issue_registry as ir, selector
from homeassistant.helpers.dispatcher import async_dispatcher_connect
Expand All@@ -21,12 +23,15 @@
CONF_KNX_KNXKEY_PASSWORD,
DOMAIN,
REPAIR_ISSUE_DATA_SECURE_GROUP_KEY,
REPAIR_ISSUE_ENTITY_VALIDATION_ERROR,
REPAIR_ISSUE_TELEGRAM_BACKEND_ERROR,
SIGNAL_KNX_DATA_SECURE_ISSUE_TELEGRAM,
KNXConfigEntryData,
)
from .storage.keyring import DEFAULT_KNX_KEYRING_FILENAME, save_uploaded_knxkeys_file

_LOGGER = logging.getLogger(__name__)

CONF_KEYRING_FILE: Final = "knxkeys_file"


Expand All@@ -43,6 +48,35 @@ async def async_create_fix_flow(
raise ValueError(f"unknown repair {issue_id}")


###########################
# Entity store schema issue
###########################


@callback
def async_create_entity_validation_issue(
hass: HomeAssistant, platform: Platform, unique_ids: list[str]
) -> None:
"""Create a repair issue for invalid entity configurations in the config store."""
_LOGGER.error(
"Invalid KNX %s configuration in storage. These entities were not set up: %s",
platform,
", ".join(unique_ids),
)
ir.async_create_issue(
hass,
DOMAIN,
f"{REPAIR_ISSUE_ENTITY_VALIDATION_ERROR}_{platform}",
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
translation_key=REPAIR_ISSUE_ENTITY_VALIDATION_ERROR,
translation_placeholders={
"platform": platform,
"entities": "\n".join(f"- {unique_id}" for unique_id in unique_ids),
},
)


######################
# DataSecure key issue
######################
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/knx/scene.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,7 +51,7 @@ async def async_setup_entry(
KnxYamlScene(knx_module, entity_config)
for entity_config in yaml_platform_config
)
if ui_config := knx_module.config_store.data["entities"].get(Platform.SCENE):
if ui_config := knx_module.config_store.get_entity_configs(Platform.SCENE):
entities.extend(
KnxUiScene(knx_module, unique_id, config)
for unique_id, config in ui_config.items()
Expand Down
Loading
Loading