mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Move KNXModule class to separate module (#146100)
This commit is contained in:
parent
a4b9efa1b1
commit
24a7ebd2bb
@ -1,34 +1,17 @@
|
||||
"""Support KNX devices."""
|
||||
"""The KNX integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
import voluptuous as vol
|
||||
from xknx import XKNX
|
||||
from xknx.core import XknxConnectionState
|
||||
from xknx.core.state_updater import StateTrackerType, TrackerOptions
|
||||
from xknx.core.telegram_queue import TelegramQueue
|
||||
from xknx.dpt import DPTBase
|
||||
from xknx.exceptions import ConversionError, CouldNotParseTelegram, XKNXException
|
||||
from xknx.io import ConnectionConfig, ConnectionType, SecureConfig
|
||||
from xknx.telegram import AddressFilter, Telegram
|
||||
from xknx.telegram.address import DeviceGroupAddress, GroupAddress, InternalGroupAddress
|
||||
from xknx.telegram.apci import GroupValueResponse, GroupValueWrite
|
||||
from xknx.exceptions import XKNXException
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_EVENT,
|
||||
CONF_HOST,
|
||||
CONF_PORT,
|
||||
CONF_TYPE,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from homeassistant.helpers.reload import async_integration_yaml_config
|
||||
@ -36,40 +19,17 @@ from homeassistant.helpers.storage import STORAGE_DIR
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_KNX_CONNECTION_TYPE,
|
||||
CONF_KNX_EXPOSE,
|
||||
CONF_KNX_INDIVIDUAL_ADDRESS,
|
||||
CONF_KNX_KNXKEY_FILENAME,
|
||||
CONF_KNX_KNXKEY_PASSWORD,
|
||||
CONF_KNX_LOCAL_IP,
|
||||
CONF_KNX_MCAST_GRP,
|
||||
CONF_KNX_MCAST_PORT,
|
||||
CONF_KNX_RATE_LIMIT,
|
||||
CONF_KNX_ROUTE_BACK,
|
||||
CONF_KNX_ROUTING,
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY,
|
||||
CONF_KNX_ROUTING_SECURE,
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE,
|
||||
CONF_KNX_SECURE_DEVICE_AUTHENTICATION,
|
||||
CONF_KNX_SECURE_USER_ID,
|
||||
CONF_KNX_SECURE_USER_PASSWORD,
|
||||
CONF_KNX_STATE_UPDATER,
|
||||
CONF_KNX_TELEGRAM_LOG_SIZE,
|
||||
CONF_KNX_TUNNEL_ENDPOINT_IA,
|
||||
CONF_KNX_TUNNELING,
|
||||
CONF_KNX_TUNNELING_TCP,
|
||||
CONF_KNX_TUNNELING_TCP_SECURE,
|
||||
DATA_HASS_CONFIG,
|
||||
DOMAIN,
|
||||
KNX_ADDRESS,
|
||||
KNX_MODULE_KEY,
|
||||
SUPPORTED_PLATFORMS_UI,
|
||||
SUPPORTED_PLATFORMS_YAML,
|
||||
TELEGRAM_LOG_DEFAULT,
|
||||
)
|
||||
from .device import KNXInterfaceDevice
|
||||
from .expose import KNXExposeSensor, KNXExposeTime, create_knx_exposure
|
||||
from .project import STORAGE_KEY as PROJECT_STORAGE_KEY, KNXProject
|
||||
from .expose import create_knx_exposure
|
||||
from .knx_module import KNXModule
|
||||
from .project import STORAGE_KEY as PROJECT_STORAGE_KEY
|
||||
from .schema import (
|
||||
BinarySensorSchema,
|
||||
ButtonSchema,
|
||||
@ -92,12 +52,10 @@ from .schema import (
|
||||
WeatherSchema,
|
||||
)
|
||||
from .services import async_setup_services
|
||||
from .storage.config_store import STORAGE_KEY as CONFIG_STORAGE_KEY, KNXConfigStore
|
||||
from .telegrams import STORAGE_KEY as TELEGRAMS_STORAGE_KEY, Telegrams
|
||||
from .storage.config_store import STORAGE_KEY as CONFIG_STORAGE_KEY
|
||||
from .telegrams import STORAGE_KEY as TELEGRAMS_STORAGE_KEY
|
||||
from .websocket import register_panel
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_KNX_YAML_CONFIG: Final = "knx_yaml_config"
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
@ -162,6 +120,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
hass.data[KNX_MODULE_KEY] = knx_module
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(async_update_entry))
|
||||
|
||||
if CONF_KNX_EXPOSE in config:
|
||||
for expose_config in config[CONF_KNX_EXPOSE]:
|
||||
knx_module.exposures.append(
|
||||
@ -255,243 +215,3 @@ async def async_remove_config_entry_device(
|
||||
if entity.device_id == device_entry.id:
|
||||
await knx_module.config_store.delete_entity(entity.entity_id)
|
||||
return True
|
||||
|
||||
|
||||
class KNXModule:
|
||||
"""Representation of KNX Object."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, config: ConfigType, entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Initialize KNX module."""
|
||||
self.hass = hass
|
||||
self.config_yaml = config
|
||||
self.connected = False
|
||||
self.exposures: list[KNXExposeSensor | KNXExposeTime] = []
|
||||
self.service_exposures: dict[str, KNXExposeSensor | KNXExposeTime] = {}
|
||||
self.entry = entry
|
||||
|
||||
self.project = KNXProject(hass=hass, entry=entry)
|
||||
self.config_store = KNXConfigStore(hass=hass, config_entry=entry)
|
||||
|
||||
default_state_updater = (
|
||||
TrackerOptions(tracker_type=StateTrackerType.EXPIRE, update_interval_min=60)
|
||||
if self.entry.data[CONF_KNX_STATE_UPDATER]
|
||||
else TrackerOptions(
|
||||
tracker_type=StateTrackerType.INIT, update_interval_min=60
|
||||
)
|
||||
)
|
||||
self.xknx = XKNX(
|
||||
address_format=self.project.get_address_format(),
|
||||
connection_config=self.connection_config(),
|
||||
rate_limit=self.entry.data[CONF_KNX_RATE_LIMIT],
|
||||
state_updater=default_state_updater,
|
||||
)
|
||||
self.xknx.connection_manager.register_connection_state_changed_cb(
|
||||
self.connection_state_changed_cb
|
||||
)
|
||||
self.telegrams = Telegrams(
|
||||
hass=hass,
|
||||
xknx=self.xknx,
|
||||
project=self.project,
|
||||
log_size=entry.data.get(CONF_KNX_TELEGRAM_LOG_SIZE, TELEGRAM_LOG_DEFAULT),
|
||||
)
|
||||
self.interface_device = KNXInterfaceDevice(
|
||||
hass=hass, entry=entry, xknx=self.xknx
|
||||
)
|
||||
|
||||
self._address_filter_transcoder: dict[AddressFilter, type[DPTBase]] = {}
|
||||
self.group_address_transcoder: dict[DeviceGroupAddress, type[DPTBase]] = {}
|
||||
self.knx_event_callback: TelegramQueue.Callback = self.register_event_callback()
|
||||
|
||||
self.entry.async_on_unload(
|
||||
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop)
|
||||
)
|
||||
self.entry.async_on_unload(self.entry.add_update_listener(async_update_entry))
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Start XKNX object. Connect to tunneling or Routing device."""
|
||||
await self.project.load_project(self.xknx)
|
||||
await self.config_store.load_data()
|
||||
await self.telegrams.load_history()
|
||||
await self.xknx.start()
|
||||
|
||||
async def stop(self, event: Event | None = None) -> None:
|
||||
"""Stop XKNX object. Disconnect from tunneling or Routing device."""
|
||||
await self.xknx.stop()
|
||||
await self.telegrams.save_history()
|
||||
|
||||
def connection_config(self) -> ConnectionConfig:
|
||||
"""Return the connection_config."""
|
||||
_conn_type: str = self.entry.data[CONF_KNX_CONNECTION_TYPE]
|
||||
_knxkeys_file: str | None = (
|
||||
self.hass.config.path(
|
||||
STORAGE_DIR,
|
||||
self.entry.data[CONF_KNX_KNXKEY_FILENAME],
|
||||
)
|
||||
if self.entry.data.get(CONF_KNX_KNXKEY_FILENAME) is not None
|
||||
else None
|
||||
)
|
||||
if _conn_type == CONF_KNX_ROUTING:
|
||||
return ConnectionConfig(
|
||||
connection_type=ConnectionType.ROUTING,
|
||||
individual_address=self.entry.data[CONF_KNX_INDIVIDUAL_ADDRESS],
|
||||
multicast_group=self.entry.data[CONF_KNX_MCAST_GRP],
|
||||
multicast_port=self.entry.data[CONF_KNX_MCAST_PORT],
|
||||
local_ip=self.entry.data.get(CONF_KNX_LOCAL_IP),
|
||||
auto_reconnect=True,
|
||||
secure_config=SecureConfig(
|
||||
knxkeys_password=self.entry.data.get(CONF_KNX_KNXKEY_PASSWORD),
|
||||
knxkeys_file_path=_knxkeys_file,
|
||||
),
|
||||
threaded=True,
|
||||
)
|
||||
if _conn_type == CONF_KNX_TUNNELING:
|
||||
return ConnectionConfig(
|
||||
connection_type=ConnectionType.TUNNELING,
|
||||
gateway_ip=self.entry.data[CONF_HOST],
|
||||
gateway_port=self.entry.data[CONF_PORT],
|
||||
local_ip=self.entry.data.get(CONF_KNX_LOCAL_IP),
|
||||
route_back=self.entry.data.get(CONF_KNX_ROUTE_BACK, False),
|
||||
auto_reconnect=True,
|
||||
secure_config=SecureConfig(
|
||||
knxkeys_password=self.entry.data.get(CONF_KNX_KNXKEY_PASSWORD),
|
||||
knxkeys_file_path=_knxkeys_file,
|
||||
),
|
||||
threaded=True,
|
||||
)
|
||||
if _conn_type == CONF_KNX_TUNNELING_TCP:
|
||||
return ConnectionConfig(
|
||||
connection_type=ConnectionType.TUNNELING_TCP,
|
||||
individual_address=self.entry.data.get(CONF_KNX_TUNNEL_ENDPOINT_IA),
|
||||
gateway_ip=self.entry.data[CONF_HOST],
|
||||
gateway_port=self.entry.data[CONF_PORT],
|
||||
auto_reconnect=True,
|
||||
secure_config=SecureConfig(
|
||||
knxkeys_password=self.entry.data.get(CONF_KNX_KNXKEY_PASSWORD),
|
||||
knxkeys_file_path=_knxkeys_file,
|
||||
),
|
||||
threaded=True,
|
||||
)
|
||||
if _conn_type == CONF_KNX_TUNNELING_TCP_SECURE:
|
||||
return ConnectionConfig(
|
||||
connection_type=ConnectionType.TUNNELING_TCP_SECURE,
|
||||
individual_address=self.entry.data.get(CONF_KNX_TUNNEL_ENDPOINT_IA),
|
||||
gateway_ip=self.entry.data[CONF_HOST],
|
||||
gateway_port=self.entry.data[CONF_PORT],
|
||||
secure_config=SecureConfig(
|
||||
user_id=self.entry.data.get(CONF_KNX_SECURE_USER_ID),
|
||||
user_password=self.entry.data.get(CONF_KNX_SECURE_USER_PASSWORD),
|
||||
device_authentication_password=self.entry.data.get(
|
||||
CONF_KNX_SECURE_DEVICE_AUTHENTICATION
|
||||
),
|
||||
knxkeys_password=self.entry.data.get(CONF_KNX_KNXKEY_PASSWORD),
|
||||
knxkeys_file_path=_knxkeys_file,
|
||||
),
|
||||
auto_reconnect=True,
|
||||
threaded=True,
|
||||
)
|
||||
if _conn_type == CONF_KNX_ROUTING_SECURE:
|
||||
return ConnectionConfig(
|
||||
connection_type=ConnectionType.ROUTING_SECURE,
|
||||
individual_address=self.entry.data[CONF_KNX_INDIVIDUAL_ADDRESS],
|
||||
multicast_group=self.entry.data[CONF_KNX_MCAST_GRP],
|
||||
multicast_port=self.entry.data[CONF_KNX_MCAST_PORT],
|
||||
local_ip=self.entry.data.get(CONF_KNX_LOCAL_IP),
|
||||
secure_config=SecureConfig(
|
||||
backbone_key=self.entry.data.get(CONF_KNX_ROUTING_BACKBONE_KEY),
|
||||
latency_ms=self.entry.data.get(
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE
|
||||
),
|
||||
knxkeys_password=self.entry.data.get(CONF_KNX_KNXKEY_PASSWORD),
|
||||
knxkeys_file_path=_knxkeys_file,
|
||||
),
|
||||
auto_reconnect=True,
|
||||
threaded=True,
|
||||
)
|
||||
return ConnectionConfig(
|
||||
auto_reconnect=True,
|
||||
individual_address=self.entry.data.get(
|
||||
CONF_KNX_TUNNEL_ENDPOINT_IA, # may be configured at knxkey upload
|
||||
),
|
||||
secure_config=SecureConfig(
|
||||
knxkeys_password=self.entry.data.get(CONF_KNX_KNXKEY_PASSWORD),
|
||||
knxkeys_file_path=_knxkeys_file,
|
||||
),
|
||||
threaded=True,
|
||||
)
|
||||
|
||||
def connection_state_changed_cb(self, state: XknxConnectionState) -> None:
|
||||
"""Call invoked after a KNX connection state change was received."""
|
||||
self.connected = state == XknxConnectionState.CONNECTED
|
||||
for device in self.xknx.devices:
|
||||
device.after_update()
|
||||
|
||||
def telegram_received_cb(self, telegram: Telegram) -> None:
|
||||
"""Call invoked after a KNX telegram was received."""
|
||||
# Not all telegrams have serializable data.
|
||||
data: int | tuple[int, ...] | None = None
|
||||
value = None
|
||||
if (
|
||||
isinstance(telegram.payload, (GroupValueWrite, GroupValueResponse))
|
||||
and telegram.payload.value is not None
|
||||
and isinstance(
|
||||
telegram.destination_address, (GroupAddress, InternalGroupAddress)
|
||||
)
|
||||
):
|
||||
data = telegram.payload.value.value
|
||||
if transcoder := (
|
||||
self.group_address_transcoder.get(telegram.destination_address)
|
||||
or next(
|
||||
(
|
||||
_transcoder
|
||||
for _filter, _transcoder in self._address_filter_transcoder.items()
|
||||
if _filter.match(telegram.destination_address)
|
||||
),
|
||||
None,
|
||||
)
|
||||
):
|
||||
try:
|
||||
value = transcoder.from_knx(telegram.payload.value)
|
||||
except (ConversionError, CouldNotParseTelegram) as err:
|
||||
_LOGGER.warning(
|
||||
(
|
||||
"Error in `knx_event` at decoding type '%s' from"
|
||||
" telegram %s\n%s"
|
||||
),
|
||||
transcoder.__name__,
|
||||
telegram,
|
||||
err,
|
||||
)
|
||||
|
||||
self.hass.bus.async_fire(
|
||||
"knx_event",
|
||||
{
|
||||
"data": data,
|
||||
"destination": str(telegram.destination_address),
|
||||
"direction": telegram.direction.value,
|
||||
"value": value,
|
||||
"source": str(telegram.source_address),
|
||||
"telegramtype": telegram.payload.__class__.__name__,
|
||||
},
|
||||
)
|
||||
|
||||
def register_event_callback(self) -> TelegramQueue.Callback:
|
||||
"""Register callback for knx_event within XKNX TelegramQueue."""
|
||||
address_filters = []
|
||||
for filter_set in self.config_yaml[CONF_EVENT]:
|
||||
_filters = list(map(AddressFilter, filter_set[KNX_ADDRESS]))
|
||||
address_filters.extend(_filters)
|
||||
if (dpt := filter_set.get(CONF_TYPE)) and (
|
||||
transcoder := DPTBase.parse_transcoder(dpt)
|
||||
):
|
||||
self._address_filter_transcoder.update(
|
||||
dict.fromkeys(_filters, transcoder)
|
||||
)
|
||||
|
||||
return self.xknx.telegram_queue.register_telegram_received_cb(
|
||||
self.telegram_received_cb,
|
||||
address_filters=address_filters,
|
||||
group_addresses=[],
|
||||
match_for_outgoing=True,
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP binary sensors."""
|
||||
"""Support for KNX binary sensor entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -25,7 +25,6 @@ from homeassistant.helpers.entity_platform import (
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import (
|
||||
ATTR_COUNTER,
|
||||
ATTR_SOURCE,
|
||||
@ -39,6 +38,7 @@ from .const import (
|
||||
KNX_MODULE_KEY,
|
||||
)
|
||||
from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
from .storage.const import CONF_ENTITY, CONF_GA_SENSOR
|
||||
from .storage.util import ConfigExtractor
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP buttons."""
|
||||
"""Support for KNX button entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -11,9 +11,9 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import CONF_PAYLOAD_LENGTH, KNX_ADDRESS, KNX_MODULE_KEY
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP climate devices."""
|
||||
"""Support for KNX climate entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -37,9 +37,9 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import CONTROLLER_MODES, CURRENT_HVAC_ACTIONS, KNX_MODULE_KEY
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
from .schema import ClimateSchema
|
||||
|
||||
ATTR_COMMAND_VALUE = "command_value"
|
||||
|
@ -14,7 +14,7 @@ from homeassistant.const import Platform
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import KNXModule
|
||||
from .knx_module import KNXModule
|
||||
|
||||
DOMAIN: Final = "knx"
|
||||
KNX_MODULE_KEY: HassKey[KNXModule] = HassKey(DOMAIN)
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP covers."""
|
||||
"""Support for KNX cover entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -28,9 +28,9 @@ from homeassistant.helpers.entity_platform import (
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import CONF_SYNC_STATE, DOMAIN, KNX_MODULE_KEY, CoverConf
|
||||
from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
from .schema import CoverSchema
|
||||
from .storage.const import (
|
||||
CONF_ENTITY,
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP date."""
|
||||
"""Support for KNX date entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -22,7 +22,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import (
|
||||
CONF_RESPOND_TO_READ,
|
||||
CONF_STATE_ADDRESS,
|
||||
@ -31,6 +30,7 @@ from .const import (
|
||||
KNX_MODULE_KEY,
|
||||
)
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP datetime."""
|
||||
"""Support for KNX datetime entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -23,7 +23,6 @@ from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import KNXModule
|
||||
from .const import (
|
||||
CONF_RESPOND_TO_READ,
|
||||
CONF_STATE_ADDRESS,
|
||||
@ -32,6 +31,7 @@ from .const import (
|
||||
KNX_MODULE_KEY,
|
||||
)
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Handle KNX Devices."""
|
||||
"""Handle Home Assistant Devices for the KNX integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Provides device triggers for KNX."""
|
||||
"""Provide device triggers for KNX."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Diagnostics support for KNX."""
|
||||
"""Diagnostics support for the KNX integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Base class for KNX devices."""
|
||||
"""Base classes for KNX entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -17,7 +17,7 @@ from .storage.config_store import PlatformControllerBase
|
||||
from .storage.const import CONF_DEVICE_INFO
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import KNXModule
|
||||
from .knx_module import KNXModule
|
||||
|
||||
|
||||
class KnxUiEntityPlatformController(PlatformControllerBase):
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Exposures to KNX bus."""
|
||||
"""Expose Home Assistant entity states to KNX."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP fans."""
|
||||
"""Support for KNX fan entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -19,9 +19,9 @@ from homeassistant.util.percentage import (
|
||||
)
|
||||
from homeassistant.util.scaling import int_states_in_range
|
||||
|
||||
from . import KNXModule
|
||||
from .const import KNX_ADDRESS, KNX_MODULE_KEY
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
from .schema import FanSchema
|
||||
|
||||
DEFAULT_PERCENTAGE: Final = 50
|
||||
|
301
homeassistant/components/knx/knx_module.py
Normal file
301
homeassistant/components/knx/knx_module.py
Normal file
@ -0,0 +1,301 @@
|
||||
"""Base module for the KNX integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from xknx import XKNX
|
||||
from xknx.core import XknxConnectionState
|
||||
from xknx.core.state_updater import StateTrackerType, TrackerOptions
|
||||
from xknx.core.telegram_queue import TelegramQueue
|
||||
from xknx.dpt import DPTBase
|
||||
from xknx.exceptions import ConversionError, CouldNotParseTelegram
|
||||
from xknx.io import ConnectionConfig, ConnectionType, SecureConfig
|
||||
from xknx.telegram import AddressFilter, Telegram
|
||||
from xknx.telegram.address import DeviceGroupAddress, GroupAddress, InternalGroupAddress
|
||||
from xknx.telegram.apci import GroupValueResponse, GroupValueWrite
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_EVENT,
|
||||
CONF_HOST,
|
||||
CONF_PORT,
|
||||
CONF_TYPE,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.helpers.storage import STORAGE_DIR
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_KNX_CONNECTION_TYPE,
|
||||
CONF_KNX_INDIVIDUAL_ADDRESS,
|
||||
CONF_KNX_KNXKEY_FILENAME,
|
||||
CONF_KNX_KNXKEY_PASSWORD,
|
||||
CONF_KNX_LOCAL_IP,
|
||||
CONF_KNX_MCAST_GRP,
|
||||
CONF_KNX_MCAST_PORT,
|
||||
CONF_KNX_RATE_LIMIT,
|
||||
CONF_KNX_ROUTE_BACK,
|
||||
CONF_KNX_ROUTING,
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY,
|
||||
CONF_KNX_ROUTING_SECURE,
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE,
|
||||
CONF_KNX_SECURE_DEVICE_AUTHENTICATION,
|
||||
CONF_KNX_SECURE_USER_ID,
|
||||
CONF_KNX_SECURE_USER_PASSWORD,
|
||||
CONF_KNX_STATE_UPDATER,
|
||||
CONF_KNX_TELEGRAM_LOG_SIZE,
|
||||
CONF_KNX_TUNNEL_ENDPOINT_IA,
|
||||
CONF_KNX_TUNNELING,
|
||||
CONF_KNX_TUNNELING_TCP,
|
||||
CONF_KNX_TUNNELING_TCP_SECURE,
|
||||
KNX_ADDRESS,
|
||||
TELEGRAM_LOG_DEFAULT,
|
||||
)
|
||||
from .device import KNXInterfaceDevice
|
||||
from .expose import KNXExposeSensor, KNXExposeTime
|
||||
from .project import KNXProject
|
||||
from .storage.config_store import KNXConfigStore
|
||||
from .telegrams import Telegrams
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class KNXModule:
|
||||
"""Representation of KNX Object."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, config: ConfigType, entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Initialize KNX module."""
|
||||
self.hass = hass
|
||||
self.config_yaml = config
|
||||
self.connected = False
|
||||
self.exposures: list[KNXExposeSensor | KNXExposeTime] = []
|
||||
self.service_exposures: dict[str, KNXExposeSensor | KNXExposeTime] = {}
|
||||
self.entry = entry
|
||||
|
||||
self.project = KNXProject(hass=hass, entry=entry)
|
||||
self.config_store = KNXConfigStore(hass=hass, config_entry=entry)
|
||||
|
||||
default_state_updater = (
|
||||
TrackerOptions(tracker_type=StateTrackerType.EXPIRE, update_interval_min=60)
|
||||
if self.entry.data[CONF_KNX_STATE_UPDATER]
|
||||
else TrackerOptions(
|
||||
tracker_type=StateTrackerType.INIT, update_interval_min=60
|
||||
)
|
||||
)
|
||||
self.xknx = XKNX(
|
||||
address_format=self.project.get_address_format(),
|
||||
connection_config=self.connection_config(),
|
||||
rate_limit=self.entry.data[CONF_KNX_RATE_LIMIT],
|
||||
state_updater=default_state_updater,
|
||||
)
|
||||
self.xknx.connection_manager.register_connection_state_changed_cb(
|
||||
self.connection_state_changed_cb
|
||||
)
|
||||
self.telegrams = Telegrams(
|
||||
hass=hass,
|
||||
xknx=self.xknx,
|
||||
project=self.project,
|
||||
log_size=entry.data.get(CONF_KNX_TELEGRAM_LOG_SIZE, TELEGRAM_LOG_DEFAULT),
|
||||
)
|
||||
self.interface_device = KNXInterfaceDevice(
|
||||
hass=hass, entry=entry, xknx=self.xknx
|
||||
)
|
||||
|
||||
self._address_filter_transcoder: dict[AddressFilter, type[DPTBase]] = {}
|
||||
self.group_address_transcoder: dict[DeviceGroupAddress, type[DPTBase]] = {}
|
||||
self.knx_event_callback: TelegramQueue.Callback = self.register_event_callback()
|
||||
|
||||
self.entry.async_on_unload(
|
||||
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop)
|
||||
)
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Start XKNX object. Connect to tunneling or Routing device."""
|
||||
await self.project.load_project(self.xknx)
|
||||
await self.config_store.load_data()
|
||||
await self.telegrams.load_history()
|
||||
await self.xknx.start()
|
||||
|
||||
async def stop(self, event: Event | None = None) -> None:
|
||||
"""Stop XKNX object. Disconnect from tunneling or Routing device."""
|
||||
await self.xknx.stop()
|
||||
await self.telegrams.save_history()
|
||||
|
||||
def connection_config(self) -> ConnectionConfig:
|
||||
"""Return the connection_config."""
|
||||
_conn_type: str = self.entry.data[CONF_KNX_CONNECTION_TYPE]
|
||||
_knxkeys_file: str | None = (
|
||||
self.hass.config.path(
|
||||
STORAGE_DIR,
|
||||
self.entry.data[CONF_KNX_KNXKEY_FILENAME],
|
||||
)
|
||||
if self.entry.data.get(CONF_KNX_KNXKEY_FILENAME) is not None
|
||||
else None
|
||||
)
|
||||
if _conn_type == CONF_KNX_ROUTING:
|
||||
return ConnectionConfig(
|
||||
connection_type=ConnectionType.ROUTING,
|
||||
individual_address=self.entry.data[CONF_KNX_INDIVIDUAL_ADDRESS],
|
||||
multicast_group=self.entry.data[CONF_KNX_MCAST_GRP],
|
||||
multicast_port=self.entry.data[CONF_KNX_MCAST_PORT],
|
||||
local_ip=self.entry.data.get(CONF_KNX_LOCAL_IP),
|
||||
auto_reconnect=True,
|
||||
secure_config=SecureConfig(
|
||||
knxkeys_password=self.entry.data.get(CONF_KNX_KNXKEY_PASSWORD),
|
||||
knxkeys_file_path=_knxkeys_file,
|
||||
),
|
||||
threaded=True,
|
||||
)
|
||||
if _conn_type == CONF_KNX_TUNNELING:
|
||||
return ConnectionConfig(
|
||||
connection_type=ConnectionType.TUNNELING,
|
||||
gateway_ip=self.entry.data[CONF_HOST],
|
||||
gateway_port=self.entry.data[CONF_PORT],
|
||||
local_ip=self.entry.data.get(CONF_KNX_LOCAL_IP),
|
||||
route_back=self.entry.data.get(CONF_KNX_ROUTE_BACK, False),
|
||||
auto_reconnect=True,
|
||||
secure_config=SecureConfig(
|
||||
knxkeys_password=self.entry.data.get(CONF_KNX_KNXKEY_PASSWORD),
|
||||
knxkeys_file_path=_knxkeys_file,
|
||||
),
|
||||
threaded=True,
|
||||
)
|
||||
if _conn_type == CONF_KNX_TUNNELING_TCP:
|
||||
return ConnectionConfig(
|
||||
connection_type=ConnectionType.TUNNELING_TCP,
|
||||
individual_address=self.entry.data.get(CONF_KNX_TUNNEL_ENDPOINT_IA),
|
||||
gateway_ip=self.entry.data[CONF_HOST],
|
||||
gateway_port=self.entry.data[CONF_PORT],
|
||||
auto_reconnect=True,
|
||||
secure_config=SecureConfig(
|
||||
knxkeys_password=self.entry.data.get(CONF_KNX_KNXKEY_PASSWORD),
|
||||
knxkeys_file_path=_knxkeys_file,
|
||||
),
|
||||
threaded=True,
|
||||
)
|
||||
if _conn_type == CONF_KNX_TUNNELING_TCP_SECURE:
|
||||
return ConnectionConfig(
|
||||
connection_type=ConnectionType.TUNNELING_TCP_SECURE,
|
||||
individual_address=self.entry.data.get(CONF_KNX_TUNNEL_ENDPOINT_IA),
|
||||
gateway_ip=self.entry.data[CONF_HOST],
|
||||
gateway_port=self.entry.data[CONF_PORT],
|
||||
secure_config=SecureConfig(
|
||||
user_id=self.entry.data.get(CONF_KNX_SECURE_USER_ID),
|
||||
user_password=self.entry.data.get(CONF_KNX_SECURE_USER_PASSWORD),
|
||||
device_authentication_password=self.entry.data.get(
|
||||
CONF_KNX_SECURE_DEVICE_AUTHENTICATION
|
||||
),
|
||||
knxkeys_password=self.entry.data.get(CONF_KNX_KNXKEY_PASSWORD),
|
||||
knxkeys_file_path=_knxkeys_file,
|
||||
),
|
||||
auto_reconnect=True,
|
||||
threaded=True,
|
||||
)
|
||||
if _conn_type == CONF_KNX_ROUTING_SECURE:
|
||||
return ConnectionConfig(
|
||||
connection_type=ConnectionType.ROUTING_SECURE,
|
||||
individual_address=self.entry.data[CONF_KNX_INDIVIDUAL_ADDRESS],
|
||||
multicast_group=self.entry.data[CONF_KNX_MCAST_GRP],
|
||||
multicast_port=self.entry.data[CONF_KNX_MCAST_PORT],
|
||||
local_ip=self.entry.data.get(CONF_KNX_LOCAL_IP),
|
||||
secure_config=SecureConfig(
|
||||
backbone_key=self.entry.data.get(CONF_KNX_ROUTING_BACKBONE_KEY),
|
||||
latency_ms=self.entry.data.get(
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE
|
||||
),
|
||||
knxkeys_password=self.entry.data.get(CONF_KNX_KNXKEY_PASSWORD),
|
||||
knxkeys_file_path=_knxkeys_file,
|
||||
),
|
||||
auto_reconnect=True,
|
||||
threaded=True,
|
||||
)
|
||||
return ConnectionConfig(
|
||||
auto_reconnect=True,
|
||||
individual_address=self.entry.data.get(
|
||||
CONF_KNX_TUNNEL_ENDPOINT_IA, # may be configured at knxkey upload
|
||||
),
|
||||
secure_config=SecureConfig(
|
||||
knxkeys_password=self.entry.data.get(CONF_KNX_KNXKEY_PASSWORD),
|
||||
knxkeys_file_path=_knxkeys_file,
|
||||
),
|
||||
threaded=True,
|
||||
)
|
||||
|
||||
def connection_state_changed_cb(self, state: XknxConnectionState) -> None:
|
||||
"""Call invoked after a KNX connection state change was received."""
|
||||
self.connected = state == XknxConnectionState.CONNECTED
|
||||
for device in self.xknx.devices:
|
||||
device.after_update()
|
||||
|
||||
def telegram_received_cb(self, telegram: Telegram) -> None:
|
||||
"""Call invoked after a KNX telegram was received."""
|
||||
# Not all telegrams have serializable data.
|
||||
data: int | tuple[int, ...] | None = None
|
||||
value = None
|
||||
if (
|
||||
isinstance(telegram.payload, (GroupValueWrite, GroupValueResponse))
|
||||
and telegram.payload.value is not None
|
||||
and isinstance(
|
||||
telegram.destination_address, (GroupAddress, InternalGroupAddress)
|
||||
)
|
||||
):
|
||||
data = telegram.payload.value.value
|
||||
if transcoder := (
|
||||
self.group_address_transcoder.get(telegram.destination_address)
|
||||
or next(
|
||||
(
|
||||
_transcoder
|
||||
for _filter, _transcoder in self._address_filter_transcoder.items()
|
||||
if _filter.match(telegram.destination_address)
|
||||
),
|
||||
None,
|
||||
)
|
||||
):
|
||||
try:
|
||||
value = transcoder.from_knx(telegram.payload.value)
|
||||
except (ConversionError, CouldNotParseTelegram) as err:
|
||||
_LOGGER.warning(
|
||||
(
|
||||
"Error in `knx_event` at decoding type '%s' from"
|
||||
" telegram %s\n%s"
|
||||
),
|
||||
transcoder.__name__,
|
||||
telegram,
|
||||
err,
|
||||
)
|
||||
|
||||
self.hass.bus.async_fire(
|
||||
"knx_event",
|
||||
{
|
||||
"data": data,
|
||||
"destination": str(telegram.destination_address),
|
||||
"direction": telegram.direction.value,
|
||||
"value": value,
|
||||
"source": str(telegram.source_address),
|
||||
"telegramtype": telegram.payload.__class__.__name__,
|
||||
},
|
||||
)
|
||||
|
||||
def register_event_callback(self) -> TelegramQueue.Callback:
|
||||
"""Register callback for knx_event within XKNX TelegramQueue."""
|
||||
address_filters = []
|
||||
for filter_set in self.config_yaml[CONF_EVENT]:
|
||||
_filters = list(map(AddressFilter, filter_set[KNX_ADDRESS]))
|
||||
address_filters.extend(_filters)
|
||||
if (dpt := filter_set.get(CONF_TYPE)) and (
|
||||
transcoder := DPTBase.parse_transcoder(dpt)
|
||||
):
|
||||
self._address_filter_transcoder.update(
|
||||
dict.fromkeys(_filters, transcoder)
|
||||
)
|
||||
|
||||
return self.xknx.telegram_queue.register_telegram_received_cb(
|
||||
self.telegram_received_cb,
|
||||
address_filters=address_filters,
|
||||
group_addresses=[],
|
||||
match_for_outgoing=True,
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP lights."""
|
||||
"""Support for KNX light entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -28,9 +28,9 @@ from homeassistant.helpers.entity_platform import (
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import color as color_util
|
||||
|
||||
from . import KNXModule
|
||||
from .const import CONF_SYNC_STATE, DOMAIN, KNX_ADDRESS, KNX_MODULE_KEY, ColorTempModes
|
||||
from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
from .schema import LightSchema
|
||||
from .storage.const import (
|
||||
CONF_COLOR_TEMP_MAX,
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP notifications."""
|
||||
"""Support for KNX notify entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -12,9 +12,9 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import KNX_ADDRESS, KNX_MODULE_KEY
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP numeric values."""
|
||||
"""Support for KNX number entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -22,9 +22,9 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import CONF_RESPOND_TO_READ, CONF_STATE_ADDRESS, KNX_ADDRESS, KNX_MODULE_KEY
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
from .schema import NumberSchema
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX scenes."""
|
||||
"""Support for KNX scene entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -13,9 +13,9 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import KNX_ADDRESS, KNX_MODULE_KEY
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
from .schema import SceneSchema
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP select entities."""
|
||||
"""Support for KNX select entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -20,7 +20,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import (
|
||||
CONF_PAYLOAD_LENGTH,
|
||||
CONF_RESPOND_TO_READ,
|
||||
@ -30,6 +29,7 @@ from .const import (
|
||||
KNX_MODULE_KEY,
|
||||
)
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
from .schema import SelectSchema
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP sensors."""
|
||||
"""Support for KNX sensor entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -33,9 +33,9 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, StateType
|
||||
from homeassistant.util.enum import try_parse_enum
|
||||
|
||||
from . import KNXModule
|
||||
from .const import ATTR_SOURCE, KNX_MODULE_KEY
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
from .schema import SensorSchema
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
|
@ -35,7 +35,7 @@ from .expose import create_knx_exposure
|
||||
from .schema import ExposeSchema, dpt_base_type_validator, ga_validator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import KNXModule
|
||||
from .knx_module import KNXModule
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -1 +1 @@
|
||||
"""Helpers for KNX."""
|
||||
"""Handle persistent storage for the KNX integration."""
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""KNX Entity Store Validation."""
|
||||
"""KNX entity store validation."""
|
||||
|
||||
from typing import Literal, TypedDict
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP switches."""
|
||||
"""Support for KNX switch entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -25,7 +25,6 @@ from homeassistant.helpers.entity_platform import (
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import (
|
||||
CONF_INVERT,
|
||||
CONF_RESPOND_TO_READ,
|
||||
@ -35,6 +34,7 @@ from .const import (
|
||||
KNX_MODULE_KEY,
|
||||
)
|
||||
from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
from .schema import SwitchSchema
|
||||
from .storage.const import CONF_ENTITY, CONF_GA_SWITCH
|
||||
from .storage.util import ConfigExtractor
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP text."""
|
||||
"""Support for KNX text entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -22,9 +22,9 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import CONF_RESPOND_TO_READ, CONF_STATE_ADDRESS, KNX_ADDRESS, KNX_MODULE_KEY
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP time."""
|
||||
"""Support for KNX time entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -22,7 +22,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import (
|
||||
CONF_RESPOND_TO_READ,
|
||||
CONF_STATE_ADDRESS,
|
||||
@ -31,6 +30,7 @@ from .const import (
|
||||
KNX_MODULE_KEY,
|
||||
)
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Offer knx telegram automation triggers."""
|
||||
"""Provide KNX automation triggers."""
|
||||
|
||||
from typing import Final
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Support for KNX/IP weather station."""
|
||||
"""Support for KNX weather entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -19,9 +19,9 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import KNX_MODULE_KEY
|
||||
from .entity import KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
from .schema import WeatherSchema
|
||||
|
||||
|
||||
|
@ -36,7 +36,7 @@ from .storage.entity_store_validation import (
|
||||
from .telegrams import SIGNAL_KNX_TELEGRAM, TelegramDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import KNXModule
|
||||
from .knx_module import KNXModule
|
||||
|
||||
URL_BASE: Final = "/knx_static"
|
||||
|
||||
|
@ -4,7 +4,8 @@ import logging
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.knx import CONF_EVENT, CONF_TYPE, KNX_ADDRESS
|
||||
from homeassistant.components.knx.const import KNX_ADDRESS
|
||||
from homeassistant.const import CONF_EVENT, CONF_TYPE
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .conftest import KNXTestKit
|
||||
|
@ -6,7 +6,7 @@ from freezegun import freeze_time
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.knx import CONF_KNX_EXPOSE, DOMAIN, KNX_ADDRESS
|
||||
from homeassistant.components.knx.const import CONF_KNX_EXPOSE, DOMAIN, KNX_ADDRESS
|
||||
from homeassistant.components.knx.schema import ExposeSchema
|
||||
from homeassistant.const import (
|
||||
CONF_ATTRIBUTE,
|
||||
|
Loading…
x
Reference in New Issue
Block a user