aiohomematic 2026.1.29__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aiohomematic/__init__.py +110 -0
- aiohomematic/_log_context_protocol.py +29 -0
- aiohomematic/api.py +410 -0
- aiohomematic/async_support.py +250 -0
- aiohomematic/backend_detection.py +462 -0
- aiohomematic/central/__init__.py +103 -0
- aiohomematic/central/async_rpc_server.py +760 -0
- aiohomematic/central/central_unit.py +1152 -0
- aiohomematic/central/config.py +463 -0
- aiohomematic/central/config_builder.py +772 -0
- aiohomematic/central/connection_state.py +160 -0
- aiohomematic/central/coordinators/__init__.py +38 -0
- aiohomematic/central/coordinators/cache.py +414 -0
- aiohomematic/central/coordinators/client.py +480 -0
- aiohomematic/central/coordinators/connection_recovery.py +1141 -0
- aiohomematic/central/coordinators/device.py +1166 -0
- aiohomematic/central/coordinators/event.py +514 -0
- aiohomematic/central/coordinators/hub.py +532 -0
- aiohomematic/central/decorators.py +184 -0
- aiohomematic/central/device_registry.py +229 -0
- aiohomematic/central/events/__init__.py +104 -0
- aiohomematic/central/events/bus.py +1392 -0
- aiohomematic/central/events/integration.py +424 -0
- aiohomematic/central/events/types.py +194 -0
- aiohomematic/central/health.py +762 -0
- aiohomematic/central/rpc_server.py +353 -0
- aiohomematic/central/scheduler.py +794 -0
- aiohomematic/central/state_machine.py +391 -0
- aiohomematic/client/__init__.py +203 -0
- aiohomematic/client/_rpc_errors.py +187 -0
- aiohomematic/client/backends/__init__.py +48 -0
- aiohomematic/client/backends/base.py +335 -0
- aiohomematic/client/backends/capabilities.py +138 -0
- aiohomematic/client/backends/ccu.py +487 -0
- aiohomematic/client/backends/factory.py +116 -0
- aiohomematic/client/backends/homegear.py +294 -0
- aiohomematic/client/backends/json_ccu.py +252 -0
- aiohomematic/client/backends/protocol.py +316 -0
- aiohomematic/client/ccu.py +1857 -0
- aiohomematic/client/circuit_breaker.py +459 -0
- aiohomematic/client/config.py +64 -0
- aiohomematic/client/handlers/__init__.py +40 -0
- aiohomematic/client/handlers/backup.py +157 -0
- aiohomematic/client/handlers/base.py +79 -0
- aiohomematic/client/handlers/device_ops.py +1085 -0
- aiohomematic/client/handlers/firmware.py +144 -0
- aiohomematic/client/handlers/link_mgmt.py +199 -0
- aiohomematic/client/handlers/metadata.py +436 -0
- aiohomematic/client/handlers/programs.py +144 -0
- aiohomematic/client/handlers/sysvars.py +100 -0
- aiohomematic/client/interface_client.py +1304 -0
- aiohomematic/client/json_rpc.py +2068 -0
- aiohomematic/client/request_coalescer.py +282 -0
- aiohomematic/client/rpc_proxy.py +629 -0
- aiohomematic/client/state_machine.py +324 -0
- aiohomematic/const.py +2207 -0
- aiohomematic/context.py +275 -0
- aiohomematic/converter.py +270 -0
- aiohomematic/decorators.py +390 -0
- aiohomematic/exceptions.py +185 -0
- aiohomematic/hmcli.py +997 -0
- aiohomematic/i18n.py +193 -0
- aiohomematic/interfaces/__init__.py +407 -0
- aiohomematic/interfaces/central.py +1067 -0
- aiohomematic/interfaces/client.py +1096 -0
- aiohomematic/interfaces/coordinators.py +63 -0
- aiohomematic/interfaces/model.py +1921 -0
- aiohomematic/interfaces/operations.py +217 -0
- aiohomematic/logging_context.py +134 -0
- aiohomematic/metrics/__init__.py +125 -0
- aiohomematic/metrics/_protocols.py +140 -0
- aiohomematic/metrics/aggregator.py +534 -0
- aiohomematic/metrics/dataclasses.py +489 -0
- aiohomematic/metrics/emitter.py +292 -0
- aiohomematic/metrics/events.py +183 -0
- aiohomematic/metrics/keys.py +300 -0
- aiohomematic/metrics/observer.py +563 -0
- aiohomematic/metrics/stats.py +172 -0
- aiohomematic/model/__init__.py +189 -0
- aiohomematic/model/availability.py +65 -0
- aiohomematic/model/calculated/__init__.py +89 -0
- aiohomematic/model/calculated/climate.py +276 -0
- aiohomematic/model/calculated/data_point.py +315 -0
- aiohomematic/model/calculated/field.py +147 -0
- aiohomematic/model/calculated/operating_voltage_level.py +286 -0
- aiohomematic/model/calculated/support.py +232 -0
- aiohomematic/model/custom/__init__.py +214 -0
- aiohomematic/model/custom/capabilities/__init__.py +67 -0
- aiohomematic/model/custom/capabilities/climate.py +41 -0
- aiohomematic/model/custom/capabilities/light.py +87 -0
- aiohomematic/model/custom/capabilities/lock.py +44 -0
- aiohomematic/model/custom/capabilities/siren.py +63 -0
- aiohomematic/model/custom/climate.py +1130 -0
- aiohomematic/model/custom/cover.py +722 -0
- aiohomematic/model/custom/data_point.py +360 -0
- aiohomematic/model/custom/definition.py +300 -0
- aiohomematic/model/custom/field.py +89 -0
- aiohomematic/model/custom/light.py +1174 -0
- aiohomematic/model/custom/lock.py +322 -0
- aiohomematic/model/custom/mixins.py +445 -0
- aiohomematic/model/custom/profile.py +945 -0
- aiohomematic/model/custom/registry.py +251 -0
- aiohomematic/model/custom/siren.py +462 -0
- aiohomematic/model/custom/switch.py +195 -0
- aiohomematic/model/custom/text_display.py +289 -0
- aiohomematic/model/custom/valve.py +78 -0
- aiohomematic/model/data_point.py +1416 -0
- aiohomematic/model/device.py +1840 -0
- aiohomematic/model/event.py +216 -0
- aiohomematic/model/generic/__init__.py +327 -0
- aiohomematic/model/generic/action.py +40 -0
- aiohomematic/model/generic/action_select.py +62 -0
- aiohomematic/model/generic/binary_sensor.py +30 -0
- aiohomematic/model/generic/button.py +31 -0
- aiohomematic/model/generic/data_point.py +177 -0
- aiohomematic/model/generic/dummy.py +150 -0
- aiohomematic/model/generic/number.py +76 -0
- aiohomematic/model/generic/select.py +56 -0
- aiohomematic/model/generic/sensor.py +76 -0
- aiohomematic/model/generic/switch.py +54 -0
- aiohomematic/model/generic/text.py +33 -0
- aiohomematic/model/hub/__init__.py +100 -0
- aiohomematic/model/hub/binary_sensor.py +24 -0
- aiohomematic/model/hub/button.py +28 -0
- aiohomematic/model/hub/connectivity.py +190 -0
- aiohomematic/model/hub/data_point.py +342 -0
- aiohomematic/model/hub/hub.py +864 -0
- aiohomematic/model/hub/inbox.py +135 -0
- aiohomematic/model/hub/install_mode.py +393 -0
- aiohomematic/model/hub/metrics.py +208 -0
- aiohomematic/model/hub/number.py +42 -0
- aiohomematic/model/hub/select.py +52 -0
- aiohomematic/model/hub/sensor.py +37 -0
- aiohomematic/model/hub/switch.py +43 -0
- aiohomematic/model/hub/text.py +30 -0
- aiohomematic/model/hub/update.py +221 -0
- aiohomematic/model/support.py +592 -0
- aiohomematic/model/update.py +140 -0
- aiohomematic/model/week_profile.py +1827 -0
- aiohomematic/property_decorators.py +719 -0
- aiohomematic/py.typed +0 -0
- aiohomematic/rega_scripts/accept_device_in_inbox.fn +51 -0
- aiohomematic/rega_scripts/create_backup_start.fn +28 -0
- aiohomematic/rega_scripts/create_backup_status.fn +89 -0
- aiohomematic/rega_scripts/fetch_all_device_data.fn +97 -0
- aiohomematic/rega_scripts/get_backend_info.fn +25 -0
- aiohomematic/rega_scripts/get_inbox_devices.fn +61 -0
- aiohomematic/rega_scripts/get_program_descriptions.fn +31 -0
- aiohomematic/rega_scripts/get_serial.fn +44 -0
- aiohomematic/rega_scripts/get_service_messages.fn +83 -0
- aiohomematic/rega_scripts/get_system_update_info.fn +39 -0
- aiohomematic/rega_scripts/get_system_variable_descriptions.fn +31 -0
- aiohomematic/rega_scripts/set_program_state.fn +17 -0
- aiohomematic/rega_scripts/set_system_variable.fn +19 -0
- aiohomematic/rega_scripts/trigger_firmware_update.fn +67 -0
- aiohomematic/schemas.py +256 -0
- aiohomematic/store/__init__.py +55 -0
- aiohomematic/store/dynamic/__init__.py +43 -0
- aiohomematic/store/dynamic/command.py +250 -0
- aiohomematic/store/dynamic/data.py +175 -0
- aiohomematic/store/dynamic/details.py +187 -0
- aiohomematic/store/dynamic/ping_pong.py +416 -0
- aiohomematic/store/persistent/__init__.py +71 -0
- aiohomematic/store/persistent/base.py +285 -0
- aiohomematic/store/persistent/device.py +233 -0
- aiohomematic/store/persistent/incident.py +380 -0
- aiohomematic/store/persistent/paramset.py +241 -0
- aiohomematic/store/persistent/session.py +556 -0
- aiohomematic/store/serialization.py +150 -0
- aiohomematic/store/storage.py +689 -0
- aiohomematic/store/types.py +526 -0
- aiohomematic/store/visibility/__init__.py +40 -0
- aiohomematic/store/visibility/parser.py +141 -0
- aiohomematic/store/visibility/registry.py +722 -0
- aiohomematic/store/visibility/rules.py +307 -0
- aiohomematic/strings.json +237 -0
- aiohomematic/support.py +706 -0
- aiohomematic/tracing.py +236 -0
- aiohomematic/translations/de.json +237 -0
- aiohomematic/translations/en.json +237 -0
- aiohomematic/type_aliases.py +51 -0
- aiohomematic/validator.py +128 -0
- aiohomematic-2026.1.29.dist-info/METADATA +296 -0
- aiohomematic-2026.1.29.dist-info/RECORD +188 -0
- aiohomematic-2026.1.29.dist-info/WHEEL +5 -0
- aiohomematic-2026.1.29.dist-info/entry_points.txt +2 -0
- aiohomematic-2026.1.29.dist-info/licenses/LICENSE +21 -0
- aiohomematic-2026.1.29.dist-info/top_level.txt +1 -0
aiohomematic/__init__.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
AioHomematic: async Python library for Homematic and HomematicIP backends.
|
|
5
|
+
|
|
6
|
+
Overview
|
|
7
|
+
--------
|
|
8
|
+
This package provides a comprehensive async library for interacting with Homematic CCU
|
|
9
|
+
and HomematicIP systems. It enables device discovery, data point manipulation, event
|
|
10
|
+
handling, and management of programs and system variables.
|
|
11
|
+
|
|
12
|
+
The library is designed for integration with Home Assistant but can be used standalone.
|
|
13
|
+
It features automatic data point discovery, flexible customization through device-specific
|
|
14
|
+
implementations, and fast startup through intelligent caching.
|
|
15
|
+
|
|
16
|
+
Architecture
|
|
17
|
+
------------
|
|
18
|
+
The library is organized into four main layers:
|
|
19
|
+
|
|
20
|
+
- **aiohomematic.central**: Central orchestration managing client lifecycles, device
|
|
21
|
+
creation, event handling, and background tasks.
|
|
22
|
+
- **aiohomematic.client**: Protocol adapters (JSON-RPC/XML-RPC) for backend communication.
|
|
23
|
+
- **aiohomematic.model**: Runtime representation of devices, channels, and data points
|
|
24
|
+
with generic, custom, calculated, and hub data point types.
|
|
25
|
+
- **aiohomematic.store**: Persistence layer for device descriptions, paramsets, and
|
|
26
|
+
runtime caches.
|
|
27
|
+
|
|
28
|
+
Public API
|
|
29
|
+
----------
|
|
30
|
+
- `__version__`: Library version string.
|
|
31
|
+
|
|
32
|
+
The primary entry point is `CentralConfig` and `CentralUnit` from `aiohomematic.central`.
|
|
33
|
+
|
|
34
|
+
Quick start
|
|
35
|
+
-----------
|
|
36
|
+
Typical usage pattern:
|
|
37
|
+
|
|
38
|
+
from aiohomematic.central import CentralConfig
|
|
39
|
+
from aiohomematic.client import InterfaceConfig, Interface
|
|
40
|
+
|
|
41
|
+
config = CentralConfig(
|
|
42
|
+
name="ccu-main",
|
|
43
|
+
host="192.168.1.100",
|
|
44
|
+
username="admin",
|
|
45
|
+
password="secret",
|
|
46
|
+
central_id="unique-id",
|
|
47
|
+
interface_configs={
|
|
48
|
+
InterfaceConfig(
|
|
49
|
+
central_name="ccu-main",
|
|
50
|
+
interface=Interface.HMIP_RF,
|
|
51
|
+
port=2010,
|
|
52
|
+
),
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
central = await config.create_central()
|
|
57
|
+
await central.start()
|
|
58
|
+
|
|
59
|
+
# Access devices and data points
|
|
60
|
+
device = central.device_coordinator.get_device_by_address("VCU0000001")
|
|
61
|
+
|
|
62
|
+
await central.stop()
|
|
63
|
+
|
|
64
|
+
Notes
|
|
65
|
+
-----
|
|
66
|
+
Public API at the top-level package is defined by `__all__`.
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
from __future__ import annotations
|
|
71
|
+
|
|
72
|
+
import asyncio
|
|
73
|
+
import logging
|
|
74
|
+
import signal
|
|
75
|
+
import sys
|
|
76
|
+
import threading
|
|
77
|
+
from typing import Final
|
|
78
|
+
|
|
79
|
+
from aiohomematic import central as hmcu, i18n, validator as _ahm_validator
|
|
80
|
+
from aiohomematic.const import VERSION
|
|
81
|
+
|
|
82
|
+
if sys.stdout.isatty():
|
|
83
|
+
logging.basicConfig(level=logging.INFO)
|
|
84
|
+
|
|
85
|
+
__version__: Final = VERSION
|
|
86
|
+
_LOGGER: Final = logging.getLogger(__name__)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# pylint: disable=unused-argument
|
|
90
|
+
# noinspection PyUnusedLocal
|
|
91
|
+
def signal_handler(sig, frame): # type: ignore[no-untyped-def] # kwonly: disable
|
|
92
|
+
"""Handle signal to shut down central."""
|
|
93
|
+
_LOGGER.info(i18n.tr(key="log.core.signal.shutdown", sig=str(sig)))
|
|
94
|
+
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
95
|
+
for central in hmcu.CENTRAL_INSTANCES.values():
|
|
96
|
+
asyncio.run_coroutine_threadsafe(central.stop(), asyncio.get_running_loop())
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Perform lightweight startup validation once on import
|
|
100
|
+
try:
|
|
101
|
+
_ahm_validator.validate_startup()
|
|
102
|
+
except Exception as _exc: # pragma: no cover
|
|
103
|
+
# Fail-fast with a clear message if validation fails during import
|
|
104
|
+
raise RuntimeError(i18n.tr(key="exception.startup.validation_failed", reason=_exc)) from _exc
|
|
105
|
+
|
|
106
|
+
if threading.current_thread() is threading.main_thread() and sys.stdout.isatty():
|
|
107
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
108
|
+
|
|
109
|
+
# Define public API for the top-level package
|
|
110
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Log context protocol interface.
|
|
5
|
+
|
|
6
|
+
This module is intentionally minimal to avoid circular imports.
|
|
7
|
+
It's imported by property_decorators.py which is near the bottom of the import chain.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from abc import abstractmethod
|
|
13
|
+
from collections.abc import Mapping
|
|
14
|
+
from typing import Any, Protocol, runtime_checkable
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class LogContextProtocol(Protocol):
|
|
19
|
+
"""
|
|
20
|
+
Protocol for objects providing log context.
|
|
21
|
+
|
|
22
|
+
Implemented by LogContextMixin and used by property_decorators.py
|
|
23
|
+
to avoid circular imports.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def log_context(self) -> Mapping[str, Any]:
|
|
29
|
+
"""Return the log context for this object."""
|
aiohomematic/api.py
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Simplified facade API for common Homematic operations.
|
|
5
|
+
|
|
6
|
+
This module provides `HomematicAPI`, a high-level facade that wraps the most
|
|
7
|
+
commonly used operations from `CentralUnit`. It offers a streamlined interface
|
|
8
|
+
for typical use cases without requiring deep knowledge of the internal architecture.
|
|
9
|
+
|
|
10
|
+
Quick start
|
|
11
|
+
-----------
|
|
12
|
+
Using the async context manager (recommended)::
|
|
13
|
+
|
|
14
|
+
from aiohomematic.api import HomematicAPI
|
|
15
|
+
|
|
16
|
+
async with HomematicAPI.connect(
|
|
17
|
+
host="192.168.1.100",
|
|
18
|
+
username="Admin",
|
|
19
|
+
password="secret",
|
|
20
|
+
) as api:
|
|
21
|
+
# List all devices
|
|
22
|
+
for device in api.list_devices():
|
|
23
|
+
print(f"{device.address}: {device.name}")
|
|
24
|
+
|
|
25
|
+
# Read and write values
|
|
26
|
+
value = await api.read_value(channel_address="VCU0000001:1", parameter="STATE")
|
|
27
|
+
await api.write_value(channel_address="VCU0000001:1", parameter="STATE", value=True)
|
|
28
|
+
|
|
29
|
+
# Connection is automatically closed when exiting the context
|
|
30
|
+
|
|
31
|
+
Manual lifecycle management::
|
|
32
|
+
|
|
33
|
+
from aiohomematic.api import HomematicAPI
|
|
34
|
+
from aiohomematic.central import CentralConfig
|
|
35
|
+
|
|
36
|
+
config = CentralConfig.for_ccu(
|
|
37
|
+
host="192.168.1.100",
|
|
38
|
+
username="Admin",
|
|
39
|
+
password="secret",
|
|
40
|
+
)
|
|
41
|
+
api = HomematicAPI(config=config)
|
|
42
|
+
await api.start()
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
for device in api.list_devices():
|
|
46
|
+
print(f"{device.address}: {device.name}")
|
|
47
|
+
finally:
|
|
48
|
+
await api.stop()
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
from __future__ import annotations
|
|
53
|
+
|
|
54
|
+
from collections.abc import Callable, Iterable
|
|
55
|
+
from types import TracebackType
|
|
56
|
+
from typing import Any, Final, Self
|
|
57
|
+
|
|
58
|
+
from aiohomematic.central import CentralConfig, CentralUnit
|
|
59
|
+
from aiohomematic.central.events import DataPointValueReceivedEvent
|
|
60
|
+
from aiohomematic.const import ParamsetKey
|
|
61
|
+
from aiohomematic.interfaces import DeviceProtocol
|
|
62
|
+
from aiohomematic.property_decorators import DelegatedProperty
|
|
63
|
+
from aiohomematic.support import get_device_address
|
|
64
|
+
from aiohomematic.type_aliases import UnsubscribeCallback
|
|
65
|
+
|
|
66
|
+
# Type alias for update callback
|
|
67
|
+
UpdateCallback = Callable[[str, str, Any], None]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class HomematicAPI:
|
|
71
|
+
"""
|
|
72
|
+
Simplified facade for common Homematic operations.
|
|
73
|
+
|
|
74
|
+
This class provides a high-level interface for interacting with Homematic
|
|
75
|
+
devices without requiring deep knowledge of the internal architecture.
|
|
76
|
+
It wraps the most commonly used operations from CentralUnit.
|
|
77
|
+
|
|
78
|
+
Attributes:
|
|
79
|
+
central: The underlying CentralUnit instance.
|
|
80
|
+
config: The configuration used to create this API instance.
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(self, *, config: CentralConfig) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Initialize the HomematicAPI.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
config: Configuration for the central unit. Use CentralConfig.for_ccu()
|
|
90
|
+
or CentralConfig.for_homegear() for simplified setup.
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
self._config: Final = config
|
|
94
|
+
self._central: CentralUnit | None = None
|
|
95
|
+
|
|
96
|
+
async def __aenter__(self) -> Self:
|
|
97
|
+
"""Enter the async context manager and start the API."""
|
|
98
|
+
await self.start()
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
async def __aexit__( # kwonly: disable
|
|
102
|
+
self,
|
|
103
|
+
exc_type: type[BaseException] | None,
|
|
104
|
+
exc_val: BaseException | None,
|
|
105
|
+
exc_tb: TracebackType | None,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Exit the async context manager and stop the API."""
|
|
108
|
+
await self.stop()
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def connect(
|
|
112
|
+
cls,
|
|
113
|
+
*,
|
|
114
|
+
host: str,
|
|
115
|
+
username: str,
|
|
116
|
+
password: str,
|
|
117
|
+
central_id: str | None = None,
|
|
118
|
+
tls: bool = False,
|
|
119
|
+
verify_tls: bool = True,
|
|
120
|
+
backend: str = "ccu",
|
|
121
|
+
) -> Self:
|
|
122
|
+
"""
|
|
123
|
+
Create a HomematicAPI instance for use as an async context manager.
|
|
124
|
+
|
|
125
|
+
This is the recommended way to use the API, as it ensures proper
|
|
126
|
+
cleanup even when exceptions occur.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
host: The hostname or IP address of the Homematic backend.
|
|
130
|
+
username: The username for authentication.
|
|
131
|
+
password: The password for authentication.
|
|
132
|
+
central_id: Optional unique identifier for this central (defaults to host).
|
|
133
|
+
tls: Whether to use TLS encryption (default: False).
|
|
134
|
+
verify_tls: Whether to verify TLS certificates (default: True).
|
|
135
|
+
backend: The backend type, either "ccu" or "homegear" (default: "ccu").
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
A HomematicAPI instance that can be used as an async context manager.
|
|
139
|
+
|
|
140
|
+
Example:
|
|
141
|
+
async with HomematicAPI.connect(
|
|
142
|
+
host="192.168.1.100",
|
|
143
|
+
username="Admin",
|
|
144
|
+
password="secret",
|
|
145
|
+
) as api:
|
|
146
|
+
for device in api.list_devices():
|
|
147
|
+
print(device.address)
|
|
148
|
+
|
|
149
|
+
# Connection is automatically closed
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
ValueError: If backend is not "ccu" or "homegear".
|
|
153
|
+
|
|
154
|
+
"""
|
|
155
|
+
if backend == "ccu":
|
|
156
|
+
config = CentralConfig.for_ccu(
|
|
157
|
+
name=central_id or host,
|
|
158
|
+
host=host,
|
|
159
|
+
username=username,
|
|
160
|
+
password=password,
|
|
161
|
+
central_id=central_id or host,
|
|
162
|
+
tls=tls,
|
|
163
|
+
verify_tls=verify_tls,
|
|
164
|
+
)
|
|
165
|
+
elif backend == "homegear":
|
|
166
|
+
config = CentralConfig.for_homegear(
|
|
167
|
+
name=central_id or host,
|
|
168
|
+
host=host,
|
|
169
|
+
username=username,
|
|
170
|
+
password=password,
|
|
171
|
+
central_id=central_id or host,
|
|
172
|
+
tls=tls,
|
|
173
|
+
verify_tls=verify_tls,
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
msg = f"Unknown backend: {backend}. Use 'ccu' or 'homegear'."
|
|
177
|
+
raise ValueError(msg)
|
|
178
|
+
|
|
179
|
+
return cls(config=config)
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
async def _do_fetch_all_device_data(*, client: Any) -> None:
|
|
183
|
+
"""Fetch all device data for a single client."""
|
|
184
|
+
await client.fetch_all_device_data()
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
async def _do_get_value(
|
|
188
|
+
*,
|
|
189
|
+
client: Any,
|
|
190
|
+
channel_address: str,
|
|
191
|
+
paramset_key: ParamsetKey,
|
|
192
|
+
parameter: str,
|
|
193
|
+
) -> Any:
|
|
194
|
+
"""Get a value from a client."""
|
|
195
|
+
return await client.get_value(
|
|
196
|
+
channel_address=channel_address,
|
|
197
|
+
paramset_key=paramset_key,
|
|
198
|
+
parameter=parameter,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
@staticmethod
|
|
202
|
+
async def _do_set_value(
|
|
203
|
+
*,
|
|
204
|
+
client: Any,
|
|
205
|
+
channel_address: str,
|
|
206
|
+
paramset_key: ParamsetKey,
|
|
207
|
+
parameter: str,
|
|
208
|
+
value: Any,
|
|
209
|
+
) -> None:
|
|
210
|
+
"""Set a value on a client."""
|
|
211
|
+
await client.set_value(
|
|
212
|
+
channel_address=channel_address,
|
|
213
|
+
paramset_key=paramset_key,
|
|
214
|
+
parameter=parameter,
|
|
215
|
+
value=value,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
config: Final = DelegatedProperty[CentralConfig](path="_config")
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def central(self) -> CentralUnit:
|
|
222
|
+
"""Return the underlying CentralUnit instance."""
|
|
223
|
+
if self._central is None:
|
|
224
|
+
msg = "API not started. Call start() first."
|
|
225
|
+
raise RuntimeError(msg)
|
|
226
|
+
return self._central
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def is_connected(self) -> bool:
|
|
230
|
+
"""Return True if connected to the backend."""
|
|
231
|
+
return (
|
|
232
|
+
self._central is not None
|
|
233
|
+
and self._central.client_coordinator.has_clients
|
|
234
|
+
and not self._central.connection_state.has_any_issue
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
def get_device(self, *, address: str) -> DeviceProtocol | None:
|
|
238
|
+
"""
|
|
239
|
+
Get a device by its address.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
address: The device address (e.g., "VCU0000001").
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
The Device object, or None if not found.
|
|
246
|
+
|
|
247
|
+
Example:
|
|
248
|
+
device = api.get_device(address="VCU0000001")
|
|
249
|
+
if device:
|
|
250
|
+
print(f"Found: {device.name}")
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
return self.central.device_coordinator.get_device(address=address)
|
|
254
|
+
|
|
255
|
+
def list_devices(self) -> Iterable[DeviceProtocol]:
|
|
256
|
+
"""
|
|
257
|
+
List all known devices.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Iterable of Device objects.
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
for device in api.list_devices():
|
|
264
|
+
print(f"{device.address}: {device.name} ({device.model})")
|
|
265
|
+
|
|
266
|
+
"""
|
|
267
|
+
return self.central.device_registry.devices
|
|
268
|
+
|
|
269
|
+
async def read_value(
|
|
270
|
+
self,
|
|
271
|
+
*,
|
|
272
|
+
channel_address: str,
|
|
273
|
+
parameter: str,
|
|
274
|
+
paramset_key: ParamsetKey = ParamsetKey.VALUES,
|
|
275
|
+
) -> Any:
|
|
276
|
+
"""
|
|
277
|
+
Read a parameter value from a device channel.
|
|
278
|
+
|
|
279
|
+
This method automatically retries on transient network errors.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
channel_address: The channel address (e.g., "VCU0000001:1").
|
|
283
|
+
parameter: The parameter name (e.g., "STATE", "LEVEL").
|
|
284
|
+
paramset_key: The paramset key (default: VALUES).
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
The current parameter value.
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
# Read switch state
|
|
291
|
+
state = await api.read_value(channel_address="VCU0000001:1", parameter="STATE")
|
|
292
|
+
|
|
293
|
+
# Read dimmer level
|
|
294
|
+
level = await api.read_value(channel_address="VCU0000002:1", parameter="LEVEL")
|
|
295
|
+
|
|
296
|
+
"""
|
|
297
|
+
device_address = get_device_address(address=channel_address)
|
|
298
|
+
if (device := self.central.device_coordinator.get_device(address=device_address)) is None:
|
|
299
|
+
msg = f"Device not found for address: {device_address}"
|
|
300
|
+
raise ValueError(msg)
|
|
301
|
+
client = self.central.client_coordinator.get_client(interface_id=device.interface_id)
|
|
302
|
+
return await self._do_get_value(
|
|
303
|
+
client=client,
|
|
304
|
+
channel_address=channel_address,
|
|
305
|
+
paramset_key=paramset_key,
|
|
306
|
+
parameter=parameter,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
async def refresh_data(self) -> None:
|
|
310
|
+
"""
|
|
311
|
+
Refresh data from all devices.
|
|
312
|
+
|
|
313
|
+
This fetches the latest values from all connected devices.
|
|
314
|
+
Each client fetch automatically retries on transient network errors.
|
|
315
|
+
"""
|
|
316
|
+
for client in self.central.client_coordinator.clients:
|
|
317
|
+
await self._do_fetch_all_device_data(client=client)
|
|
318
|
+
|
|
319
|
+
async def start(self) -> None:
|
|
320
|
+
"""
|
|
321
|
+
Start the API and connect to the Homematic backend.
|
|
322
|
+
|
|
323
|
+
This creates the central unit, initializes clients, and starts
|
|
324
|
+
the background scheduler for connection health checks.
|
|
325
|
+
"""
|
|
326
|
+
self._central = await self._config.create_central()
|
|
327
|
+
await self._central.start()
|
|
328
|
+
|
|
329
|
+
async def stop(self) -> None:
|
|
330
|
+
"""
|
|
331
|
+
Stop the API and disconnect from the backend.
|
|
332
|
+
|
|
333
|
+
This stops all clients, the XML-RPC server, and the background scheduler.
|
|
334
|
+
"""
|
|
335
|
+
if self._central is not None:
|
|
336
|
+
await self._central.stop()
|
|
337
|
+
self._central = None
|
|
338
|
+
|
|
339
|
+
def subscribe_to_updates(self, *, callback: UpdateCallback) -> UnsubscribeCallback:
|
|
340
|
+
"""
|
|
341
|
+
Subscribe to data point value updates.
|
|
342
|
+
|
|
343
|
+
The callback is invoked whenever a data point value changes.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
callback: Function called with (channel_address, parameter, value)
|
|
347
|
+
when a data point is updated.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
An unsubscribe function to remove the callback.
|
|
351
|
+
|
|
352
|
+
Example:
|
|
353
|
+
def on_update(address: str, parameter: str, value: Any) -> None:
|
|
354
|
+
print(f"{address}.{parameter} = {value}")
|
|
355
|
+
|
|
356
|
+
unsubscribe = api.subscribe_to_updates(callback=on_update)
|
|
357
|
+
|
|
358
|
+
# Later, to stop receiving updates:
|
|
359
|
+
unsubscribe()
|
|
360
|
+
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
async def event_handler(*, event: DataPointValueReceivedEvent) -> None:
|
|
364
|
+
callback(event.dpk.channel_address, event.dpk.parameter, event.value)
|
|
365
|
+
|
|
366
|
+
return self.central.event_bus.subscribe(
|
|
367
|
+
event_type=DataPointValueReceivedEvent,
|
|
368
|
+
event_key=None,
|
|
369
|
+
handler=event_handler,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
async def write_value(
|
|
373
|
+
self,
|
|
374
|
+
*,
|
|
375
|
+
channel_address: str,
|
|
376
|
+
parameter: str,
|
|
377
|
+
value: Any,
|
|
378
|
+
paramset_key: ParamsetKey = ParamsetKey.VALUES,
|
|
379
|
+
) -> None:
|
|
380
|
+
"""
|
|
381
|
+
Write a parameter value to a device channel.
|
|
382
|
+
|
|
383
|
+
This method automatically retries on transient network errors.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
channel_address: The channel address (e.g., "VCU0000001:1").
|
|
387
|
+
parameter: The parameter name (e.g., "STATE", "LEVEL").
|
|
388
|
+
value: The value to write.
|
|
389
|
+
paramset_key: The paramset key (default: VALUES).
|
|
390
|
+
|
|
391
|
+
Example:
|
|
392
|
+
# Turn on a switch
|
|
393
|
+
await api.write_value(channel_address="VCU0000001:1", parameter="STATE", value=True)
|
|
394
|
+
|
|
395
|
+
# Set dimmer to 50%
|
|
396
|
+
await api.write_value(channel_address="VCU0000002:1", parameter="LEVEL", value=0.5)
|
|
397
|
+
|
|
398
|
+
"""
|
|
399
|
+
device_address = get_device_address(address=channel_address)
|
|
400
|
+
if (device := self.central.device_coordinator.get_device(address=device_address)) is None:
|
|
401
|
+
msg = f"Device not found for address: {device_address}"
|
|
402
|
+
raise ValueError(msg)
|
|
403
|
+
client = self.central.client_coordinator.get_client(interface_id=device.interface_id)
|
|
404
|
+
await self._do_set_value(
|
|
405
|
+
client=client,
|
|
406
|
+
channel_address=channel_address,
|
|
407
|
+
paramset_key=paramset_key,
|
|
408
|
+
parameter=parameter,
|
|
409
|
+
value=value,
|
|
410
|
+
)
|