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/const.py
ADDED
|
@@ -0,0 +1,2207 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2021-2026
|
|
3
|
+
"""
|
|
4
|
+
Constants used by aiohomematic.
|
|
5
|
+
|
|
6
|
+
Public API of this module is defined by __all__.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Iterable, Mapping
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from enum import Enum, IntEnum, StrEnum
|
|
15
|
+
import inspect
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
from types import MappingProxyType
|
|
20
|
+
from typing import Any, Final, NamedTuple, Required, TypedDict
|
|
21
|
+
|
|
22
|
+
VERSION: Final = "2026.1.29"
|
|
23
|
+
|
|
24
|
+
# Detect test speedup mode via environment
|
|
25
|
+
_TEST_SPEEDUP: Final = (
|
|
26
|
+
bool(os.getenv("AIOHOMEMATIC_TEST_SPEEDUP")) or ("PYTEST_CURRENT_TEST" in os.environ) or ("pytest" in sys.modules)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# default
|
|
30
|
+
DEFAULT_DELAY_NEW_DEVICE_CREATION: Final = False
|
|
31
|
+
DEFAULT_ENABLE_DEVICE_FIRMWARE_CHECK: Final = False
|
|
32
|
+
DEFAULT_ENABLE_PROGRAM_SCAN: Final = True
|
|
33
|
+
DEFAULT_ENABLE_SYSVAR_SCAN: Final = True
|
|
34
|
+
DEFAULT_IGNORE_CUSTOM_DEVICE_DEFINITION_MODELS: Final[frozenset[str]] = frozenset()
|
|
35
|
+
DEFAULT_INCLUDE_INTERNAL_PROGRAMS: Final = False
|
|
36
|
+
DEFAULT_INCLUDE_INTERNAL_SYSVARS: Final = True
|
|
37
|
+
DEFAULT_LOCALE: Final = "en"
|
|
38
|
+
DEFAULT_MAX_READ_WORKERS: Final = 1
|
|
39
|
+
DEFAULT_MAX_WORKERS: Final = 1
|
|
40
|
+
DEFAULT_MULTIPLIER: Final = 1.0
|
|
41
|
+
DEFAULT_OPTIONAL_SETTINGS: Final[tuple[OptionalSettings | str, ...]] = ()
|
|
42
|
+
DEFAULT_PROGRAM_MARKERS: Final[tuple[DescriptionMarker | str, ...]] = ()
|
|
43
|
+
DEFAULT_SESSION_RECORDER_START_FOR_SECONDS: Final = 180
|
|
44
|
+
DEFAULT_STORAGE_DIRECTORY: Final = "aiohomematic_storage"
|
|
45
|
+
DEFAULT_SYSVAR_MARKERS: Final[tuple[DescriptionMarker | str, ...]] = ()
|
|
46
|
+
DEFAULT_TLS: Final = False
|
|
47
|
+
DEFAULT_UN_IGNORES: Final[frozenset[str]] = frozenset()
|
|
48
|
+
DEFAULT_USE_GROUP_CHANNEL_FOR_COVER_STATE: Final = True
|
|
49
|
+
DEFAULT_VERIFY_TLS: Final = False
|
|
50
|
+
DEFAULT_INCLUDE_DEFAULT_DPS: Final = True
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TimeoutConfig(NamedTuple):
|
|
54
|
+
"""
|
|
55
|
+
Configuration for various timeout and interval settings.
|
|
56
|
+
|
|
57
|
+
All values are in seconds unless otherwise noted.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
reconnect_initial_delay: float = 0.5 if _TEST_SPEEDUP else 2
|
|
61
|
+
"""Initial delay before first reconnect attempt (default: 2s)."""
|
|
62
|
+
|
|
63
|
+
reconnect_max_delay: float = 1 if _TEST_SPEEDUP else 120
|
|
64
|
+
"""Maximum delay between reconnect attempts after exponential backoff (default: 120s)."""
|
|
65
|
+
|
|
66
|
+
reconnect_backoff_factor: float = 2
|
|
67
|
+
"""Multiplier for exponential backoff on reconnect attempts (default: 2)."""
|
|
68
|
+
|
|
69
|
+
reconnect_initial_cooldown: float = 0.5 if _TEST_SPEEDUP else 30
|
|
70
|
+
"""Initial cool-down period after connection loss before starting TCP checks (default: 30s)."""
|
|
71
|
+
|
|
72
|
+
reconnect_tcp_check_timeout: float = 1 if _TEST_SPEEDUP else 60
|
|
73
|
+
"""Maximum time to wait for TCP port to become available before giving up (default: 60s)."""
|
|
74
|
+
|
|
75
|
+
reconnect_tcp_check_interval: float = 0.5 if _TEST_SPEEDUP else 5
|
|
76
|
+
"""Interval between TCP port checks during reconnection (default: 5s)."""
|
|
77
|
+
|
|
78
|
+
reconnect_warmup_delay: float = 0.5 if _TEST_SPEEDUP else 15
|
|
79
|
+
"""
|
|
80
|
+
Warmup delay after first successful RPC check before attempting init (default: 15s).
|
|
81
|
+
|
|
82
|
+
After TCP port becomes available and first listMethods succeeds, this delay allows
|
|
83
|
+
CCU services to fully stabilize. A second listMethods call verifies stability before
|
|
84
|
+
init() is attempted.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
callback_warn_interval: float = (1 if _TEST_SPEEDUP else 15) * 12
|
|
88
|
+
"""Interval before warning about missing callback events (default: 180s = 3min)."""
|
|
89
|
+
|
|
90
|
+
rpc_timeout: float = 5 if _TEST_SPEEDUP else 60
|
|
91
|
+
"""Default timeout for RPC calls (default: 60s)."""
|
|
92
|
+
|
|
93
|
+
ping_timeout: float = 2 if _TEST_SPEEDUP else 10
|
|
94
|
+
"""Timeout for ping/connectivity check operations (default: 10s)."""
|
|
95
|
+
|
|
96
|
+
connectivity_error_threshold: int = 1
|
|
97
|
+
"""Number of consecutive connectivity failures before marking devices unavailable (default: 1)."""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
DEFAULT_TIMEOUT_CONFIG: Final = TimeoutConfig()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ScheduleTimerConfig(NamedTuple):
|
|
104
|
+
"""
|
|
105
|
+
Configuration for scheduler intervals and timeouts.
|
|
106
|
+
|
|
107
|
+
All values are in seconds.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
connection_checker_interval: int = 1 if _TEST_SPEEDUP else 15
|
|
111
|
+
"""Interval between connection health checks (default: 15s)."""
|
|
112
|
+
|
|
113
|
+
device_firmware_check_interval: int = 21600 # 6h
|
|
114
|
+
"""Interval for periodic device firmware update checks (default: 6h)."""
|
|
115
|
+
|
|
116
|
+
device_firmware_delivering_check_interval: int = 3600 # 1h
|
|
117
|
+
"""Interval for checking firmware delivery progress (default: 1h)."""
|
|
118
|
+
|
|
119
|
+
device_firmware_updating_check_interval: int = 300 # 5m
|
|
120
|
+
"""Interval for checking firmware update progress (default: 5m)."""
|
|
121
|
+
|
|
122
|
+
master_poll_after_send_intervals: tuple[int, ...] = (5,)
|
|
123
|
+
"""Interval for polling HM master after sending commands (default: 5s)."""
|
|
124
|
+
|
|
125
|
+
metrics_refresh_interval: int = 60
|
|
126
|
+
"""Interval for refreshing metrics hub sensors (default: 60s)."""
|
|
127
|
+
|
|
128
|
+
periodic_refresh_interval: int = 15
|
|
129
|
+
"""Interval for periodic data refresh (default: 15s)."""
|
|
130
|
+
|
|
131
|
+
sys_scan_interval: int = 30
|
|
132
|
+
"""Interval for system variable and program scans (default: 30s)."""
|
|
133
|
+
|
|
134
|
+
system_update_check_interval: int = 14400 # 4h
|
|
135
|
+
"""Interval for periodic system update checks (default: 4h)."""
|
|
136
|
+
|
|
137
|
+
system_update_progress_check_interval: int = 30 # 30s
|
|
138
|
+
"""Interval for checking system update progress during active update (default: 30s)."""
|
|
139
|
+
|
|
140
|
+
system_update_progress_timeout: int = 1800 # 30min
|
|
141
|
+
"""Timeout for system update monitoring (default: 30min)."""
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
DEFAULT_SCHEDULE_TIMER_CONFIG: Final = ScheduleTimerConfig()
|
|
145
|
+
|
|
146
|
+
# Default encoding for json service calls, persistent cache
|
|
147
|
+
UTF_8: Final = "utf-8"
|
|
148
|
+
# Default encoding for xmlrpc service calls and script files
|
|
149
|
+
ISO_8859_1: Final = "iso-8859-1"
|
|
150
|
+
|
|
151
|
+
BIDCOS_DEVICE_CHANNEL_DUMMY: Final = 999
|
|
152
|
+
|
|
153
|
+
# Password can be empty.
|
|
154
|
+
# Allowed characters: A-Z, a-z, 0-9, .!$():;#-
|
|
155
|
+
# The CCU WebUI also supports ÄäÖöÜüß, but these characters are not supported by the XmlRPC servers
|
|
156
|
+
CCU_PASSWORD_PATTERN: Final = re.compile(r"[A-Za-z0-9.!$():;#-]{0,}")
|
|
157
|
+
# Pattern is bigger than needed
|
|
158
|
+
CHANNEL_ADDRESS_PATTERN: Final = re.compile(r"^[0-9a-zA-Z-]{5,20}:[0-9]{1,3}$")
|
|
159
|
+
DEVICE_ADDRESS_PATTERN: Final = re.compile(r"^[0-9a-zA-Z-]{5,20}$")
|
|
160
|
+
|
|
161
|
+
HOSTNAME_PATTERN: Final = re.compile(
|
|
162
|
+
r"^(?=.{1,253}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)(?:\.(?!-)[A-Za-z0-9-]{1,63}(?<!-))*$"
|
|
163
|
+
)
|
|
164
|
+
IPV4_PATTERN: Final = re.compile(
|
|
165
|
+
r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
|
|
166
|
+
)
|
|
167
|
+
IPV6_PATTERN: Final = re.compile(r"^\[?[0-9a-fA-F:]+\]?$")
|
|
168
|
+
|
|
169
|
+
HTMLTAG_PATTERN: Final = re.compile(r"<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});")
|
|
170
|
+
SCHEDULER_PROFILE_PATTERN: Final = re.compile(
|
|
171
|
+
r"^P[1-6]_(ENDTIME|TEMPERATURE)_(MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY|SATURDAY|SUNDAY)_([1-9]|1[0-3])$"
|
|
172
|
+
)
|
|
173
|
+
SCHEDULER_TIME_PATTERN: Final = re.compile(r"^(([0-1]{0,1}[0-9])|(2[0-4])):[0-5][0-9]")
|
|
174
|
+
WEEK_PROFILE_PATTERN: Final = re.compile(r".*WEEK_PROFILE$")
|
|
175
|
+
|
|
176
|
+
ALWAYS_ENABLE_SYSVARS_BY_ID: Final[frozenset[str]] = frozenset({"40", "41"})
|
|
177
|
+
RENAME_SYSVAR_BY_NAME: Final[Mapping[str, str]] = MappingProxyType(
|
|
178
|
+
{
|
|
179
|
+
"${sysVarAlarmMessages}": "ALARM_MESSAGES",
|
|
180
|
+
"${sysVarPresence}": "PRESENCE",
|
|
181
|
+
"${sysVarServiceMessages}": "SERVICE_MESSAGES",
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
ADDRESS_SEPARATOR: Final = ":"
|
|
186
|
+
BLOCK_LOG_TIMEOUT: Final = 60
|
|
187
|
+
CONTENT_PATH: Final = "cache"
|
|
188
|
+
CONF_PASSWORD: Final = "password"
|
|
189
|
+
CONF_USERNAME: Final = "username"
|
|
190
|
+
|
|
191
|
+
DATETIME_FORMAT: Final = "%d.%m.%Y %H:%M:%S"
|
|
192
|
+
DATETIME_FORMAT_MILLIS: Final = "%d.%m.%Y %H:%M:%S.%f'"
|
|
193
|
+
DUMMY_SERIAL: Final = "SN0815"
|
|
194
|
+
FILE_DEVICES: Final = "homematic_devices"
|
|
195
|
+
FILE_INCIDENTS: Final = "homematic_incidents"
|
|
196
|
+
FILE_PARAMSETS: Final = "homematic_paramsets"
|
|
197
|
+
FILE_SESSION_RECORDER: Final = "homematic_session_recorder"
|
|
198
|
+
FILE_NAME_TS_PATTERN: Final = "%Y%m%d_%H%M%S"
|
|
199
|
+
INCIDENT_STORE_MAX_PER_TYPE: Final = 50
|
|
200
|
+
SUB_DIRECTORY_CACHE: Final = "cache"
|
|
201
|
+
SUB_DIRECTORY_SESSION: Final = "session"
|
|
202
|
+
HUB_PATH: Final = "hub"
|
|
203
|
+
IDENTIFIER_SEPARATOR: Final = "@"
|
|
204
|
+
INIT_DATETIME: Final = datetime.strptime("01.01.1970 00:00:00", DATETIME_FORMAT)
|
|
205
|
+
IP_ANY_V4: Final = "0.0.0.0"
|
|
206
|
+
JSON_SESSION_AGE: Final = 90
|
|
207
|
+
|
|
208
|
+
# Login rate limiting constants
|
|
209
|
+
LOGIN_MAX_FAILED_ATTEMPTS: Final = 10
|
|
210
|
+
LOGIN_INITIAL_BACKOFF_SECONDS: Final = 1.0
|
|
211
|
+
LOGIN_MAX_BACKOFF_SECONDS: Final = 60.0
|
|
212
|
+
LOGIN_BACKOFF_MULTIPLIER: Final = 2.0
|
|
213
|
+
|
|
214
|
+
KWARGS_ARG_CUSTOM_ID: Final = "custom_id"
|
|
215
|
+
KWARGS_ARG_DATA_POINT: Final = "data_point"
|
|
216
|
+
LAST_COMMAND_SEND_TRACKER_CLEANUP_THRESHOLD: Final = 100 # Cleanup when tracker size exceeds this
|
|
217
|
+
LAST_COMMAND_SEND_STORE_TIMEOUT: Final = 60
|
|
218
|
+
|
|
219
|
+
# Resource limits for internal collections
|
|
220
|
+
COMMAND_TRACKER_MAX_SIZE: Final = 500 # Maximum entries in command tracker
|
|
221
|
+
COMMAND_TRACKER_WARNING_THRESHOLD: Final = 400 # Log warning when approaching limit
|
|
222
|
+
PING_PONG_CACHE_MAX_SIZE: Final = 100 # Maximum entries in ping/pong cache per interface
|
|
223
|
+
LOCAL_HOST: Final = "127.0.0.1"
|
|
224
|
+
MAX_CACHE_AGE: Final = 10
|
|
225
|
+
MAX_CONCURRENT_HTTP_SESSIONS: Final = 3
|
|
226
|
+
MAX_WAIT_FOR_CALLBACK: Final = 60
|
|
227
|
+
NO_CACHE_ENTRY: Final = "NO_CACHE_ENTRY"
|
|
228
|
+
DEVICE_DESCRIPTIONS_ZIP_DIR: Final = "device_descriptions"
|
|
229
|
+
PARAMSET_DESCRIPTIONS_ZIP_DIR: Final = "paramset_descriptions"
|
|
230
|
+
PATH_JSON_RPC: Final = "/api/homematic.cgi"
|
|
231
|
+
PING_PONG_MISMATCH_COUNT: Final = 15
|
|
232
|
+
PING_PONG_MISMATCH_COUNT_TTL: Final = 300
|
|
233
|
+
PORT_ANY: Final = 0
|
|
234
|
+
|
|
235
|
+
# Backend detection ports
|
|
236
|
+
# Format: (non-TLS port, TLS port)
|
|
237
|
+
DETECTION_PORT_BIDCOS_RF: Final = (2001, 42001)
|
|
238
|
+
DETECTION_PORT_HMIP_RF: Final = (2010, 42010)
|
|
239
|
+
DETECTION_PORT_BIDCOS_WIRED: Final = (2000, 42000)
|
|
240
|
+
DETECTION_PORT_VIRTUAL_DEVICES: Final = (9292, 49292)
|
|
241
|
+
DETECTION_PORT_JSON_RPC: Final = ((80, False), (443, True)) # (port, tls)
|
|
242
|
+
|
|
243
|
+
# Default JSON-RPC ports
|
|
244
|
+
DEFAULT_JSON_RPC_PORT: Final = 80
|
|
245
|
+
DEFAULT_JSON_RPC_TLS_PORT: Final = 443
|
|
246
|
+
|
|
247
|
+
HUB_ADDRESS: Final = "hub"
|
|
248
|
+
INSTALL_MODE_ADDRESS: Final = "install_mode"
|
|
249
|
+
PROGRAM_ADDRESS: Final = "program"
|
|
250
|
+
REGA_SCRIPT_PATH: Final = "../rega_scripts"
|
|
251
|
+
REPORT_VALUE_USAGE_DATA: Final = "reportValueUsageData"
|
|
252
|
+
REPORT_VALUE_USAGE_VALUE_ID: Final = "PRESS_SHORT"
|
|
253
|
+
SYSVAR_ADDRESS: Final = "sysvar"
|
|
254
|
+
TIMEOUT: Final = 5 if _TEST_SPEEDUP else 60 # default timeout for a connection
|
|
255
|
+
UN_IGNORE_WILDCARD: Final = "all"
|
|
256
|
+
WAIT_FOR_CALLBACK: Final[int | None] = None
|
|
257
|
+
|
|
258
|
+
# Scheduler sleep durations (used by central scheduler loop)
|
|
259
|
+
SCHEDULER_NOT_STARTED_SLEEP: Final = 0.2 if _TEST_SPEEDUP else 10
|
|
260
|
+
SCHEDULER_LOOP_SLEEP: Final = 0.2 if _TEST_SPEEDUP else 5
|
|
261
|
+
|
|
262
|
+
# Path
|
|
263
|
+
HUB_SET_PATH_ROOT: Final = "hub/set"
|
|
264
|
+
HUB_STATE_PATH_ROOT: Final = "hub/status"
|
|
265
|
+
PROGRAM_SET_PATH_ROOT: Final = "program/set"
|
|
266
|
+
PROGRAM_STATE_PATH_ROOT: Final = "program/status"
|
|
267
|
+
SET_PATH_ROOT: Final = "device/set"
|
|
268
|
+
STATE_PATH_ROOT: Final = "device/status"
|
|
269
|
+
SYSVAR_SET_PATH_ROOT: Final = "sysvar/set"
|
|
270
|
+
SYSVAR_STATE_PATH_ROOT: Final = "sysvar/status"
|
|
271
|
+
VIRTDEV_SET_PATH_ROOT: Final = "virtdev/set"
|
|
272
|
+
VIRTDEV_STATE_PATH_ROOT: Final = "virtdev/status"
|
|
273
|
+
|
|
274
|
+
# Metric sensor names
|
|
275
|
+
METRICS_SENSOR_SYSTEM_HEALTH_NAME: Final = "system_health"
|
|
276
|
+
METRICS_SENSOR_CONNECTION_LATENCY_NAME: Final = "connection_latency"
|
|
277
|
+
METRICS_SENSOR_LAST_EVENT_AGE_NAME: Final = "last_event_age"
|
|
278
|
+
|
|
279
|
+
CONNECTIVITY_SENSOR_PREFIX: Final = "Connectivity"
|
|
280
|
+
INBOX_SENSOR_NAME: Final = "inbox"
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class Backend(StrEnum):
|
|
284
|
+
"""Enum with supported aiohomematic backends."""
|
|
285
|
+
|
|
286
|
+
CCU = "CCU"
|
|
287
|
+
HOMEGEAR = "Homegear"
|
|
288
|
+
PYDEVCCU = "PyDevCCU"
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class CCUType(StrEnum):
|
|
292
|
+
"""
|
|
293
|
+
Enum with CCU types.
|
|
294
|
+
|
|
295
|
+
CCU: Original CCU2/CCU3 hardware and debmatic (CCU clone).
|
|
296
|
+
OPENCCU: OpenCCU - modern variants with online update check.
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
CCU = "CCU"
|
|
300
|
+
OPENCCU = "OpenCCU"
|
|
301
|
+
UNKNOWN = "Unknown"
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class SystemEventType(StrEnum):
|
|
305
|
+
"""Enum with aiohomematic system events."""
|
|
306
|
+
|
|
307
|
+
DELETE_DEVICES = "deleteDevices"
|
|
308
|
+
DEVICES_CREATED = "devicesCreated"
|
|
309
|
+
DEVICES_DELAYED = "devicesDelayed"
|
|
310
|
+
ERROR = "error"
|
|
311
|
+
HUB_REFRESHED = "hubDataPointRefreshed"
|
|
312
|
+
LIST_DEVICES = "listDevices"
|
|
313
|
+
NEW_DEVICES = "newDevices"
|
|
314
|
+
REPLACE_DEVICE = "replaceDevice"
|
|
315
|
+
RE_ADDED_DEVICE = "readdedDevice"
|
|
316
|
+
UPDATE_DEVICE = "updateDevice"
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class CallSource(StrEnum):
|
|
320
|
+
"""Enum with sources for calls."""
|
|
321
|
+
|
|
322
|
+
HA_INIT = "ha_init"
|
|
323
|
+
HM_INIT = "hm_init"
|
|
324
|
+
MANUAL_OR_SCHEDULED = "manual_or_scheduled"
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class ServiceScope(StrEnum):
|
|
328
|
+
"""
|
|
329
|
+
Enum defining the scope of service methods.
|
|
330
|
+
|
|
331
|
+
Used by @inspector and @bind_collector decorators to control whether
|
|
332
|
+
a method is exposed as a service method (lib_service attribute).
|
|
333
|
+
|
|
334
|
+
Values:
|
|
335
|
+
EXTERNAL: Methods intended for external consumers (e.g., Home Assistant).
|
|
336
|
+
These are user-invokable commands like turn_on, turn_off, set_temperature.
|
|
337
|
+
Methods with this scope appear in service_method_names.
|
|
338
|
+
INTERNAL: Infrastructure methods for library operation.
|
|
339
|
+
These are internal methods like load_data_point_value, fetch_*_data.
|
|
340
|
+
Methods with this scope do NOT appear in service_method_names.
|
|
341
|
+
"""
|
|
342
|
+
|
|
343
|
+
EXTERNAL = "external"
|
|
344
|
+
INTERNAL = "internal"
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class CalculatedParameter(StrEnum):
|
|
348
|
+
"""Enum with calculated Homematic parameters."""
|
|
349
|
+
|
|
350
|
+
APPARENT_TEMPERATURE = "APPARENT_TEMPERATURE"
|
|
351
|
+
DEW_POINT = "DEW_POINT"
|
|
352
|
+
DEW_POINT_SPREAD = "DEW_POINT_SPREAD"
|
|
353
|
+
ENTHALPY = "ENTHALPY"
|
|
354
|
+
FROST_POINT = "FROST_POINT"
|
|
355
|
+
OPERATING_VOLTAGE_LEVEL = "OPERATING_VOLTAGE_LEVEL"
|
|
356
|
+
VAPOR_CONCENTRATION = "VAPOR_CONCENTRATION"
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class ProfileKey(StrEnum):
|
|
360
|
+
"""Enum for custom data point definitions."""
|
|
361
|
+
|
|
362
|
+
ADDITIONAL_DPS = "additional_dps"
|
|
363
|
+
ALLOW_UNDEFINED_GENERIC_DPS = "allow_undefined_generic_dps"
|
|
364
|
+
DEFAULT_DPS = "default_dps"
|
|
365
|
+
DEVICE_DEFINITIONS = "device_definitions"
|
|
366
|
+
DEVICE_GROUP = "device_group"
|
|
367
|
+
FIELDS = "fields"
|
|
368
|
+
INCLUDE_DEFAULT_DPS = "include_default_dps"
|
|
369
|
+
PRIMARY_CHANNEL = "primary_channel"
|
|
370
|
+
REPEATABLE_FIELDS = "repeatable_fields"
|
|
371
|
+
SECONDARY_CHANNELS = "secondary_channels"
|
|
372
|
+
STATE_CHANNEL = "state_channel"
|
|
373
|
+
VISIBLE_FIELDS = "visible_fields"
|
|
374
|
+
VISIBLE_REPEATABLE_FIELDS = "visible_repeatable_fields"
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class ChannelOffset(IntEnum):
|
|
378
|
+
"""
|
|
379
|
+
Semantic channel offsets relative to the primary channel.
|
|
380
|
+
|
|
381
|
+
Used in profile definitions to reference channels by their semantic role
|
|
382
|
+
rather than magic numbers.
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
STATE = -1
|
|
386
|
+
"""State channel offset (e.g., ACTIVITY_STATE for covers)."""
|
|
387
|
+
|
|
388
|
+
SENSOR = -2
|
|
389
|
+
"""Sensor channel offset (e.g., WATER_FLOW for irrigation)."""
|
|
390
|
+
|
|
391
|
+
CONFIG = -5
|
|
392
|
+
"""Configuration channel offset (e.g., for WGTC thermostat)."""
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class CacheInvalidationReason(StrEnum):
|
|
396
|
+
"""Reason for cache invalidation."""
|
|
397
|
+
|
|
398
|
+
DEVICE_ADDED = "device_added"
|
|
399
|
+
"""Cache invalidated due to device being added."""
|
|
400
|
+
|
|
401
|
+
DEVICE_REMOVED = "device_removed"
|
|
402
|
+
"""Cache invalidated due to device being removed."""
|
|
403
|
+
|
|
404
|
+
DEVICE_UPDATED = "device_updated"
|
|
405
|
+
"""Cache invalidated due to device being updated."""
|
|
406
|
+
|
|
407
|
+
REFRESH = "refresh"
|
|
408
|
+
"""Cache invalidated due to scheduled refresh."""
|
|
409
|
+
|
|
410
|
+
MANUAL = "manual"
|
|
411
|
+
"""Cache invalidated manually."""
|
|
412
|
+
|
|
413
|
+
STARTUP = "startup"
|
|
414
|
+
"""Cache invalidated during startup."""
|
|
415
|
+
|
|
416
|
+
SHUTDOWN = "shutdown"
|
|
417
|
+
"""Cache invalidated during shutdown."""
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
class CacheType(StrEnum):
|
|
421
|
+
"""Cache type identifiers."""
|
|
422
|
+
|
|
423
|
+
DEVICE_DESCRIPTION = "device_description"
|
|
424
|
+
"""Device description cache."""
|
|
425
|
+
|
|
426
|
+
PARAMSET_DESCRIPTION = "paramset_description"
|
|
427
|
+
"""Paramset description cache."""
|
|
428
|
+
|
|
429
|
+
DATA = "data"
|
|
430
|
+
"""Data cache."""
|
|
431
|
+
|
|
432
|
+
DETAILS = "details"
|
|
433
|
+
"""Device details cache."""
|
|
434
|
+
|
|
435
|
+
VISIBILITY = "visibility"
|
|
436
|
+
"""Parameter visibility cache."""
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
class CentralState(StrEnum):
|
|
440
|
+
"""
|
|
441
|
+
Central State Machine states for overall system health orchestration.
|
|
442
|
+
|
|
443
|
+
This enum defines the states for the Central State Machine which
|
|
444
|
+
orchestrates the overall system state based on individual client states.
|
|
445
|
+
|
|
446
|
+
State Machine
|
|
447
|
+
-------------
|
|
448
|
+
```
|
|
449
|
+
STARTING ──► INITIALIZING ──► RUNNING ◄──► DEGRADED
|
|
450
|
+
│ │ │
|
|
451
|
+
│ ▼ ▼
|
|
452
|
+
│ RECOVERING ◄────┘
|
|
453
|
+
│ │
|
|
454
|
+
│ ├──► RUNNING (all recovered)
|
|
455
|
+
│ ├──► DEGRADED (partial recovery)
|
|
456
|
+
│ └──► FAILED (max retries)
|
|
457
|
+
│
|
|
458
|
+
└──► FAILED (critical init error)
|
|
459
|
+
|
|
460
|
+
STOPPED ◄── (from any state via stop())
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Valid Transitions
|
|
464
|
+
-----------------
|
|
465
|
+
- STARTING → INITIALIZING, STOPPED
|
|
466
|
+
- INITIALIZING → RUNNING, DEGRADED, FAILED, STOPPED
|
|
467
|
+
- RUNNING → DEGRADED, RECOVERING, STOPPED
|
|
468
|
+
- DEGRADED → RUNNING, RECOVERING, FAILED, STOPPED
|
|
469
|
+
- RECOVERING → RUNNING, DEGRADED, FAILED, STOPPED
|
|
470
|
+
- FAILED → RECOVERING, STOPPED
|
|
471
|
+
- STOPPED → (terminal)
|
|
472
|
+
"""
|
|
473
|
+
|
|
474
|
+
STARTING = "starting"
|
|
475
|
+
"""Central is being created."""
|
|
476
|
+
|
|
477
|
+
INITIALIZING = "initializing"
|
|
478
|
+
"""Clients are being initialized."""
|
|
479
|
+
|
|
480
|
+
RUNNING = "running"
|
|
481
|
+
"""Normal operation - all clients connected."""
|
|
482
|
+
|
|
483
|
+
DEGRADED = "degraded"
|
|
484
|
+
"""Limited operation - at least one client not connected."""
|
|
485
|
+
|
|
486
|
+
RECOVERING = "recovering"
|
|
487
|
+
"""Active recovery in progress."""
|
|
488
|
+
|
|
489
|
+
FAILED = "failed"
|
|
490
|
+
"""Critical error - manual intervention required."""
|
|
491
|
+
|
|
492
|
+
STOPPED = "stopped"
|
|
493
|
+
"""Central has been stopped."""
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
class FailureReason(StrEnum):
|
|
497
|
+
"""
|
|
498
|
+
Reason for a failure state in state machines.
|
|
499
|
+
|
|
500
|
+
This enum provides detailed failure categorization for FAILED states
|
|
501
|
+
in both ClientStateMachine and CentralStateMachine, enabling integrations
|
|
502
|
+
to distinguish between different error types and show appropriate messages.
|
|
503
|
+
|
|
504
|
+
Usage
|
|
505
|
+
-----
|
|
506
|
+
When transitioning to a FAILED state, specify the failure reason:
|
|
507
|
+
|
|
508
|
+
```python
|
|
509
|
+
state_machine.transition_to(
|
|
510
|
+
target=ClientState.FAILED,
|
|
511
|
+
reason="Authentication failed",
|
|
512
|
+
failure_reason=FailureReason.AUTH,
|
|
513
|
+
)
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
The integration can then check the failure reason:
|
|
517
|
+
|
|
518
|
+
```python
|
|
519
|
+
if central.state_machine.failure_reason == FailureReason.AUTH:
|
|
520
|
+
show_auth_error_dialog()
|
|
521
|
+
elif central.state_machine.failure_reason == FailureReason.NETWORK:
|
|
522
|
+
show_connection_error_dialog()
|
|
523
|
+
```
|
|
524
|
+
"""
|
|
525
|
+
|
|
526
|
+
NONE = "none"
|
|
527
|
+
"""No failure - normal operation."""
|
|
528
|
+
|
|
529
|
+
AUTH = "auth"
|
|
530
|
+
"""Authentication/authorization failure (wrong credentials)."""
|
|
531
|
+
|
|
532
|
+
NETWORK = "network"
|
|
533
|
+
"""Network connectivity issue (host unreachable, connection refused)."""
|
|
534
|
+
|
|
535
|
+
INTERNAL = "internal"
|
|
536
|
+
"""Internal backend error (CCU internal error)."""
|
|
537
|
+
|
|
538
|
+
TIMEOUT = "timeout"
|
|
539
|
+
"""Operation timed out."""
|
|
540
|
+
|
|
541
|
+
CIRCUIT_BREAKER = "circuit_breaker"
|
|
542
|
+
"""Circuit breaker is open due to repeated failures."""
|
|
543
|
+
|
|
544
|
+
UNKNOWN = "unknown"
|
|
545
|
+
"""Unknown or unclassified error."""
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
class UpdateDeviceHint(IntEnum):
|
|
549
|
+
"""
|
|
550
|
+
Hint values for updateDevice callback from Homematic backend.
|
|
551
|
+
|
|
552
|
+
The CCU sends these hint values to indicate the type of device update:
|
|
553
|
+
- FIRMWARE: Device firmware was updated, requires cache invalidation
|
|
554
|
+
- LINKS: Only link partners changed, no cache invalidation needed
|
|
555
|
+
"""
|
|
556
|
+
|
|
557
|
+
FIRMWARE = 0
|
|
558
|
+
"""Device firmware was updated - requires cache invalidation and reload."""
|
|
559
|
+
|
|
560
|
+
LINKS = 1
|
|
561
|
+
"""Link partners changed - no cache invalidation needed."""
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
class ConnectionStage(IntEnum):
|
|
565
|
+
"""
|
|
566
|
+
Reconnection stage progression.
|
|
567
|
+
|
|
568
|
+
Stages during reconnection after connection loss:
|
|
569
|
+
- LOST: Connection was lost, initiating reconnection
|
|
570
|
+
- TCP_AVAILABLE: TCP port is responding
|
|
571
|
+
- RPC_AVAILABLE: RPC service is responding (listMethods)
|
|
572
|
+
- WARMUP: Waiting for services to stabilize
|
|
573
|
+
- ESTABLISHED: Connection fully established
|
|
574
|
+
"""
|
|
575
|
+
|
|
576
|
+
LOST = 0
|
|
577
|
+
"""Connection lost, initiating reconnection."""
|
|
578
|
+
|
|
579
|
+
TCP_AVAILABLE = 1
|
|
580
|
+
"""TCP port is responding."""
|
|
581
|
+
|
|
582
|
+
RPC_AVAILABLE = 2
|
|
583
|
+
"""RPC service is responding (listMethods passed)."""
|
|
584
|
+
|
|
585
|
+
WARMUP = 3
|
|
586
|
+
"""Warmup period - waiting for services to stabilize."""
|
|
587
|
+
|
|
588
|
+
ESTABLISHED = 4
|
|
589
|
+
"""Connection fully re-established."""
|
|
590
|
+
|
|
591
|
+
@property
|
|
592
|
+
def display_name(self) -> str:
|
|
593
|
+
"""Return human-readable stage name."""
|
|
594
|
+
names: dict[int, str] = {
|
|
595
|
+
0: "Connection Lost",
|
|
596
|
+
1: "TCP Port Available",
|
|
597
|
+
2: "RPC Responding",
|
|
598
|
+
3: "Warmup Period",
|
|
599
|
+
4: "Connection Established",
|
|
600
|
+
}
|
|
601
|
+
return names.get(self.value, "Unknown")
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
class RecoveryStage(StrEnum):
|
|
605
|
+
"""
|
|
606
|
+
Stages of the unified connection recovery process.
|
|
607
|
+
|
|
608
|
+
The ConnectionRecoveryCoordinator progresses through these stages
|
|
609
|
+
when recovering a failed connection. Each stage emits a
|
|
610
|
+
RecoveryStageChangedEvent for observability.
|
|
611
|
+
|
|
612
|
+
Stage Progression
|
|
613
|
+
-----------------
|
|
614
|
+
Normal recovery: IDLE → DETECTING → COOLDOWN → TCP_CHECKING → RPC_CHECKING
|
|
615
|
+
→ WARMING_UP → STABILITY_CHECK → RECONNECTING → DATA_LOADING
|
|
616
|
+
→ RECOVERED
|
|
617
|
+
|
|
618
|
+
Failed recovery: Any stage → FAILED → HEARTBEAT (periodic retry)
|
|
619
|
+
|
|
620
|
+
Retry from FAILED: HEARTBEAT → TCP_CHECKING → ... → RECOVERED (or back to FAILED)
|
|
621
|
+
"""
|
|
622
|
+
|
|
623
|
+
IDLE = "idle"
|
|
624
|
+
"""No recovery in progress."""
|
|
625
|
+
|
|
626
|
+
DETECTING = "detecting"
|
|
627
|
+
"""Connection loss detected, preparing recovery."""
|
|
628
|
+
|
|
629
|
+
COOLDOWN = "cooldown"
|
|
630
|
+
"""Initial cool-down period before recovery attempt."""
|
|
631
|
+
|
|
632
|
+
TCP_CHECKING = "tcp_checking"
|
|
633
|
+
"""Checking TCP port availability (non-invasive)."""
|
|
634
|
+
|
|
635
|
+
RPC_CHECKING = "rpc_checking"
|
|
636
|
+
"""Checking RPC service responds (listMethods)."""
|
|
637
|
+
|
|
638
|
+
WARMING_UP = "warming_up"
|
|
639
|
+
"""Waiting for services to stabilize after RPC responds."""
|
|
640
|
+
|
|
641
|
+
STABILITY_CHECK = "stability_check"
|
|
642
|
+
"""Confirming RPC stability before reconnection."""
|
|
643
|
+
|
|
644
|
+
RECONNECTING = "reconnecting"
|
|
645
|
+
"""Performing full client reconnection (init call)."""
|
|
646
|
+
|
|
647
|
+
DATA_LOADING = "data_loading"
|
|
648
|
+
"""Loading device and paramset data post-reconnect."""
|
|
649
|
+
|
|
650
|
+
RECOVERED = "recovered"
|
|
651
|
+
"""Recovery completed successfully."""
|
|
652
|
+
|
|
653
|
+
FAILED = "failed"
|
|
654
|
+
"""Recovery failed after max retries."""
|
|
655
|
+
|
|
656
|
+
HEARTBEAT = "heartbeat"
|
|
657
|
+
"""Periodic retry attempt in FAILED state."""
|
|
658
|
+
|
|
659
|
+
@property
|
|
660
|
+
def display_name(self) -> str:
|
|
661
|
+
"""Return human-readable stage name."""
|
|
662
|
+
names: dict[str, str] = {
|
|
663
|
+
"idle": "Idle",
|
|
664
|
+
"detecting": "Detecting Loss",
|
|
665
|
+
"cooldown": "Cool-down",
|
|
666
|
+
"tcp_checking": "TCP Check",
|
|
667
|
+
"rpc_checking": "RPC Check",
|
|
668
|
+
"warming_up": "Warming Up",
|
|
669
|
+
"stability_check": "Stability Check",
|
|
670
|
+
"reconnecting": "Reconnecting",
|
|
671
|
+
"data_loading": "Loading Data",
|
|
672
|
+
"recovered": "Recovered",
|
|
673
|
+
"failed": "Failed",
|
|
674
|
+
"heartbeat": "Heartbeat Retry",
|
|
675
|
+
}
|
|
676
|
+
return names.get(self.value, "Unknown")
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
class RecoveryResult(StrEnum):
|
|
680
|
+
"""Result of a recovery attempt."""
|
|
681
|
+
|
|
682
|
+
SUCCESS = "success"
|
|
683
|
+
"""Recovery was fully successful."""
|
|
684
|
+
|
|
685
|
+
PARTIAL = "partial"
|
|
686
|
+
"""Some clients recovered, others still failed."""
|
|
687
|
+
|
|
688
|
+
FAILED = "failed"
|
|
689
|
+
"""Recovery failed for all clients."""
|
|
690
|
+
|
|
691
|
+
MAX_RETRIES = "max_retries"
|
|
692
|
+
"""Maximum retry attempts reached."""
|
|
693
|
+
|
|
694
|
+
CANCELLED = "cancelled"
|
|
695
|
+
"""Recovery was cancelled (e.g., during shutdown)."""
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
class CommandRxMode(StrEnum):
|
|
699
|
+
"""Enum for Homematic rx modes for commands."""
|
|
700
|
+
|
|
701
|
+
BURST = "BURST"
|
|
702
|
+
WAKEUP = "WAKEUP"
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
class DataOperationResult(Enum):
|
|
706
|
+
"""Enum with data operation results."""
|
|
707
|
+
|
|
708
|
+
LOAD_FAIL = 0
|
|
709
|
+
LOAD_SUCCESS = 1
|
|
710
|
+
SAVE_FAIL = 10
|
|
711
|
+
SAVE_SUCCESS = 11
|
|
712
|
+
NO_LOAD = 20
|
|
713
|
+
NO_SAVE = 21
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
class DataPointCategory(StrEnum):
|
|
717
|
+
"""Enum with data point types."""
|
|
718
|
+
|
|
719
|
+
ACTION = "action"
|
|
720
|
+
ACTION_SELECT = "action_select"
|
|
721
|
+
BINARY_SENSOR = "binary_sensor"
|
|
722
|
+
BUTTON = "button"
|
|
723
|
+
CLIMATE = "climate"
|
|
724
|
+
COVER = "cover"
|
|
725
|
+
EVENT = "event"
|
|
726
|
+
HUB_BINARY_SENSOR = "hub_binary_sensor"
|
|
727
|
+
HUB_BUTTON = "hub_button"
|
|
728
|
+
HUB_NUMBER = "hub_number"
|
|
729
|
+
HUB_SELECT = "hub_select"
|
|
730
|
+
HUB_SENSOR = "hub_sensor"
|
|
731
|
+
HUB_SWITCH = "hub_switch"
|
|
732
|
+
HUB_TEXT = "hub_text"
|
|
733
|
+
HUB_UPDATE = "hub_update"
|
|
734
|
+
LIGHT = "light"
|
|
735
|
+
LOCK = "lock"
|
|
736
|
+
NUMBER = "number"
|
|
737
|
+
SELECT = "select"
|
|
738
|
+
SENSOR = "sensor"
|
|
739
|
+
SIREN = "siren"
|
|
740
|
+
SWITCH = "switch"
|
|
741
|
+
TEXT = "text"
|
|
742
|
+
TEXT_DISPLAY = "text_display"
|
|
743
|
+
UNDEFINED = "undefined"
|
|
744
|
+
UPDATE = "update"
|
|
745
|
+
VALVE = "valve"
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
class DataPointKey(NamedTuple):
|
|
749
|
+
"""Key for data points."""
|
|
750
|
+
|
|
751
|
+
interface_id: str
|
|
752
|
+
channel_address: str
|
|
753
|
+
paramset_key: ParamsetKey
|
|
754
|
+
parameter: str
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
class DataPointUsage(StrEnum):
|
|
758
|
+
"""Enum with usage information."""
|
|
759
|
+
|
|
760
|
+
CDP_PRIMARY = "ce_primary"
|
|
761
|
+
CDP_SECONDARY = "ce_secondary"
|
|
762
|
+
CDP_VISIBLE = "ce_visible"
|
|
763
|
+
DATA_POINT = "data_point"
|
|
764
|
+
EVENT = "event"
|
|
765
|
+
NO_CREATE = "no_create"
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
class ParameterStatus(StrEnum):
|
|
769
|
+
"""
|
|
770
|
+
Status values for paired *_STATUS parameters.
|
|
771
|
+
|
|
772
|
+
These indicate the validity/quality of the associated parameter value.
|
|
773
|
+
HmIP devices use string-based ENUMs for status parameters.
|
|
774
|
+
Note: Some *_STATUS parameters (LED_STATUS, BACKLIGHT_AT_STATUS, etc.)
|
|
775
|
+
have different value semantics and should not use this enum.
|
|
776
|
+
"""
|
|
777
|
+
|
|
778
|
+
NORMAL = "NORMAL"
|
|
779
|
+
"""Value is valid and within expected range."""
|
|
780
|
+
|
|
781
|
+
UNKNOWN = "UNKNOWN"
|
|
782
|
+
"""Value is unknown (device hasn't reported yet, or communication issue)."""
|
|
783
|
+
|
|
784
|
+
OVERFLOW = "OVERFLOW"
|
|
785
|
+
"""Value exceeds the maximum expected range."""
|
|
786
|
+
|
|
787
|
+
UNDERFLOW = "UNDERFLOW"
|
|
788
|
+
"""Value is below the minimum expected range."""
|
|
789
|
+
|
|
790
|
+
ERROR = "ERROR"
|
|
791
|
+
"""Measurement error occurred."""
|
|
792
|
+
|
|
793
|
+
INVALID = "INVALID"
|
|
794
|
+
"""Value is invalid."""
|
|
795
|
+
|
|
796
|
+
UNUSED = "UNUSED"
|
|
797
|
+
"""Parameter is not used."""
|
|
798
|
+
|
|
799
|
+
EXTERNAL = "EXTERNAL"
|
|
800
|
+
"""Value is from an external source."""
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
class DescriptionMarker(StrEnum):
|
|
804
|
+
"""Enum with default description markers."""
|
|
805
|
+
|
|
806
|
+
HAHM = "HAHM"
|
|
807
|
+
HX = "HX"
|
|
808
|
+
INTERNAL = "INTERNAL"
|
|
809
|
+
MQTT = "MQTT"
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
class DeviceFirmwareState(StrEnum):
|
|
813
|
+
"""Enum with Homematic device firmware states."""
|
|
814
|
+
|
|
815
|
+
UNKNOWN = "UNKNOWN"
|
|
816
|
+
UP_TO_DATE = "UP_TO_DATE"
|
|
817
|
+
LIVE_UP_TO_DATE = "LIVE_UP_TO_DATE"
|
|
818
|
+
NEW_FIRMWARE_AVAILABLE = "NEW_FIRMWARE_AVAILABLE"
|
|
819
|
+
LIVE_NEW_FIRMWARE_AVAILABLE = "LIVE_NEW_FIRMWARE_AVAILABLE"
|
|
820
|
+
DELIVER_FIRMWARE_IMAGE = "DELIVER_FIRMWARE_IMAGE"
|
|
821
|
+
LIVE_DELIVER_FIRMWARE_IMAGE = "LIVE_DELIVER_FIRMWARE_IMAGE"
|
|
822
|
+
READY_FOR_UPDATE = "READY_FOR_UPDATE"
|
|
823
|
+
DO_UPDATE_PENDING = "DO_UPDATE_PENDING"
|
|
824
|
+
PERFORMING_UPDATE = "PERFORMING_UPDATE"
|
|
825
|
+
BACKGROUND_UPDATE_NOT_SUPPORTED = "BACKGROUND_UPDATE_NOT_SUPPORTED"
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
class DeviceProfile(StrEnum):
|
|
829
|
+
"""Enum for device profiles."""
|
|
830
|
+
|
|
831
|
+
IP_BUTTON_LOCK = "IPButtonLock"
|
|
832
|
+
IP_COVER = "IPCover"
|
|
833
|
+
IP_DIMMER = "IPDimmer"
|
|
834
|
+
IP_DRG_DALI = "IPDRGDALI"
|
|
835
|
+
IP_FIXED_COLOR_LIGHT = "IPFixedColorLight"
|
|
836
|
+
IP_GARAGE = "IPGarage"
|
|
837
|
+
IP_HDM = "IPHdm"
|
|
838
|
+
IP_IRRIGATION_VALVE = "IPIrrigationValve"
|
|
839
|
+
IP_LOCK = "IPLock"
|
|
840
|
+
IP_RGBW_LIGHT = "IPRGBW"
|
|
841
|
+
IP_SIMPLE_FIXED_COLOR_LIGHT = "IPSimpleFixedColorLight"
|
|
842
|
+
IP_SIMPLE_FIXED_COLOR_LIGHT_WIRED = "IPSimpleFixedColorLightWired"
|
|
843
|
+
IP_SIREN = "IPSiren"
|
|
844
|
+
IP_SIREN_SMOKE = "IPSirenSmoke"
|
|
845
|
+
IP_SOUND_PLAYER = "IPSoundPlayer"
|
|
846
|
+
IP_SOUND_PLAYER_LED = "IPSoundPlayerLed"
|
|
847
|
+
IP_SWITCH = "IPSwitch"
|
|
848
|
+
IP_TEXT_DISPLAY = "IPTextDisplay"
|
|
849
|
+
IP_THERMOSTAT = "IPThermostat"
|
|
850
|
+
IP_THERMOSTAT_GROUP = "IPThermostatGroup"
|
|
851
|
+
RF_BUTTON_LOCK = "RFButtonLock"
|
|
852
|
+
RF_COVER = "RfCover"
|
|
853
|
+
RF_DIMMER = "RfDimmer"
|
|
854
|
+
RF_DIMMER_COLOR = "RfDimmer_Color"
|
|
855
|
+
RF_DIMMER_COLOR_FIXED = "RfDimmer_Color_Fixed"
|
|
856
|
+
RF_DIMMER_COLOR_TEMP = "RfDimmer_Color_Temp"
|
|
857
|
+
RF_DIMMER_WITH_VIRT_CHANNEL = "RfDimmerWithVirtChannel"
|
|
858
|
+
RF_LOCK = "RfLock"
|
|
859
|
+
RF_SIREN = "RfSiren"
|
|
860
|
+
RF_SWITCH = "RfSwitch"
|
|
861
|
+
RF_THERMOSTAT = "RfThermostat"
|
|
862
|
+
RF_THERMOSTAT_GROUP = "RfThermostatGroup"
|
|
863
|
+
SIMPLE_RF_THERMOSTAT = "SimpleRfThermostat"
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
class DeviceTriggerEventType(StrEnum):
|
|
867
|
+
"""Enum with aiohomematic event types."""
|
|
868
|
+
|
|
869
|
+
DEVICE_ERROR = "homematic.device_error"
|
|
870
|
+
IMPULSE = "homematic.impulse"
|
|
871
|
+
KEYPRESS = "homematic.keypress"
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
875
|
+
class EventData:
|
|
876
|
+
"""Data for device trigger events."""
|
|
877
|
+
|
|
878
|
+
interface_id: str
|
|
879
|
+
model: str
|
|
880
|
+
device_address: str
|
|
881
|
+
channel_no: int | None
|
|
882
|
+
parameter: str
|
|
883
|
+
value: Any = None
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
class Field(Enum):
|
|
887
|
+
"""Enum for fields."""
|
|
888
|
+
|
|
889
|
+
ACOUSTIC_ALARM_ACTIVE = "acoustic_alarm_active"
|
|
890
|
+
ACOUSTIC_ALARM_SELECTION = "acoustic_alarm_selection"
|
|
891
|
+
ACOUSTIC_NOTIFICATION_SELECTION = "acoustic_notification_selection"
|
|
892
|
+
ACTIVE_PROFILE = "active_profile"
|
|
893
|
+
ACTIVITY_STATE = "activity_state"
|
|
894
|
+
AUTO_MODE = "auto_mode"
|
|
895
|
+
BOOST_MODE = "boost_mode"
|
|
896
|
+
BURST_LIMIT_WARNING = "burst_limit_warning"
|
|
897
|
+
BUTTON_LOCK = "button_lock"
|
|
898
|
+
CHANNEL_COLOR = "channel_color"
|
|
899
|
+
COLOR = "color"
|
|
900
|
+
COLOR_BEHAVIOUR = "color_behaviour"
|
|
901
|
+
COLOR_LEVEL = "color_temp"
|
|
902
|
+
COLOR_TEMPERATURE = "color_temperature"
|
|
903
|
+
COMBINED_PARAMETER = "combined_parameter"
|
|
904
|
+
COMFORT_MODE = "comfort_mode"
|
|
905
|
+
CONCENTRATION = "concentration"
|
|
906
|
+
CONTROL_MODE = "control_mode"
|
|
907
|
+
CURRENT = "current"
|
|
908
|
+
DEVICE_OPERATION_MODE = "device_operation_mode"
|
|
909
|
+
DIRECTION = "direction"
|
|
910
|
+
DISPLAY_DATA_ALIGNMENT = "display_data_alignment"
|
|
911
|
+
DISPLAY_DATA_BACKGROUND_COLOR = "display_data_background_color"
|
|
912
|
+
DISPLAY_DATA_ICON = "display_data_icon"
|
|
913
|
+
DISPLAY_DATA_TEXT_COLOR = "display_data_text_color"
|
|
914
|
+
DISPLAY_DATA_COMMIT = "display_data_commit"
|
|
915
|
+
DISPLAY_DATA_ID = "display_data_id"
|
|
916
|
+
DISPLAY_DATA_STRING = "display_data_string"
|
|
917
|
+
DOOR_COMMAND = "door_command"
|
|
918
|
+
DOOR_STATE = "door_state"
|
|
919
|
+
DURATION = "duration"
|
|
920
|
+
DURATION_UNIT = "duration_unit"
|
|
921
|
+
DURATION_VALUE = "duration_value"
|
|
922
|
+
DUTYCYCLE = "dutycycle"
|
|
923
|
+
DUTY_CYCLE = "duty_cycle"
|
|
924
|
+
EFFECT = "effect"
|
|
925
|
+
ENERGY_COUNTER = "energy_counter"
|
|
926
|
+
ERROR = "error"
|
|
927
|
+
FREQUENCY = "frequency"
|
|
928
|
+
GROUP_LEVEL = "group_level"
|
|
929
|
+
GROUP_LEVEL_2 = "group_level_2"
|
|
930
|
+
GROUP_STATE = "group_state"
|
|
931
|
+
HEATING_COOLING = "heating_cooling"
|
|
932
|
+
HEATING_VALVE_TYPE = "heating_valve_type"
|
|
933
|
+
HUE = "hue"
|
|
934
|
+
HUMIDITY = "humidity"
|
|
935
|
+
INHIBIT = "inhibit"
|
|
936
|
+
INTERVAL = "interval"
|
|
937
|
+
LEVEL = "level"
|
|
938
|
+
LEVEL_2 = "level_2"
|
|
939
|
+
LEVEL_COMBINED = "level_combined"
|
|
940
|
+
LOCK_STATE = "lock_state"
|
|
941
|
+
LOCK_TARGET_LEVEL = "lock_target_level"
|
|
942
|
+
LOWBAT = "lowbat"
|
|
943
|
+
LOWERING_MODE = "lowering_mode"
|
|
944
|
+
LOW_BAT = "low_bat"
|
|
945
|
+
LOW_BAT_LIMIT = "low_bat_limit"
|
|
946
|
+
MANU_MODE = "manu_mode"
|
|
947
|
+
MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE = "min_max_value_not_relevant_for_manu_mode"
|
|
948
|
+
ON_TIME_UNIT = "on_time_unit"
|
|
949
|
+
ON_TIME_VALUE = "on_time_value"
|
|
950
|
+
ON_TIME_LIST = "on_time_list_1"
|
|
951
|
+
OPEN = "open"
|
|
952
|
+
OPERATING_VOLTAGE = "operating_voltage"
|
|
953
|
+
OPERATION_MODE = "channel_operation_mode"
|
|
954
|
+
OPTICAL_ALARM_ACTIVE = "optical_alarm_active"
|
|
955
|
+
OPTICAL_ALARM_SELECTION = "optical_alarm_selection"
|
|
956
|
+
OPTIMUM_START_STOP = "optimum_start_stop"
|
|
957
|
+
PARTY_MODE = "party_mode"
|
|
958
|
+
POWER = "power"
|
|
959
|
+
PROGRAM = "program"
|
|
960
|
+
RAMP_TIME_TO_OFF_UNIT = "ramp_time_to_off_unit"
|
|
961
|
+
RAMP_TIME_TO_OFF_VALUE = "ramp_time_to_off_value"
|
|
962
|
+
RAMP_TIME_UNIT = "ramp_time_unit"
|
|
963
|
+
RAMP_TIME_VALUE = "ramp_time_value"
|
|
964
|
+
REPETITIONS = "repetitions"
|
|
965
|
+
RSSI_DEVICE = "rssi_device"
|
|
966
|
+
RSSI_PEER = "rssi_peer"
|
|
967
|
+
SABOTAGE = "sabotage"
|
|
968
|
+
SATURATION = "saturation"
|
|
969
|
+
SECTION = "section"
|
|
970
|
+
SETPOINT = "setpoint"
|
|
971
|
+
SET_POINT_MODE = "set_point_mode"
|
|
972
|
+
SMOKE_DETECTOR_ALARM_STATUS = "smoke_detector_alarm_status"
|
|
973
|
+
SMOKE_DETECTOR_COMMAND = "smoke_detector_command"
|
|
974
|
+
SOUNDFILE = "soundfile"
|
|
975
|
+
STATE = "state"
|
|
976
|
+
STOP = "stop"
|
|
977
|
+
SWITCH_MAIN = "switch_main"
|
|
978
|
+
SWITCH_V1 = "vswitch_1"
|
|
979
|
+
SWITCH_V2 = "vswitch_2"
|
|
980
|
+
TEMPERATURE = "temperature"
|
|
981
|
+
TEMPERATURE_MAXIMUM = "temperature_maximum"
|
|
982
|
+
TEMPERATURE_MINIMUM = "temperature_minimum"
|
|
983
|
+
TEMPERATURE_OFFSET = "temperature_offset"
|
|
984
|
+
VALVE_STATE = "valve_state"
|
|
985
|
+
VOLTAGE = "voltage"
|
|
986
|
+
WEEK_PROGRAM_POINTER = "week_program_pointer"
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
class Flag(IntEnum):
|
|
990
|
+
"""Enum with Homematic flags."""
|
|
991
|
+
|
|
992
|
+
VISIBLE = 1
|
|
993
|
+
INTERNAL = 2
|
|
994
|
+
TRANSFORM = 4 # not used
|
|
995
|
+
SERVICE = 8
|
|
996
|
+
STICKY = 10 # This might be wrong. Documentation says 0x10 # not used
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
class ForcedDeviceAvailability(StrEnum):
|
|
1000
|
+
"""Enum with aiohomematic event types."""
|
|
1001
|
+
|
|
1002
|
+
FORCE_FALSE = "forced_not_available"
|
|
1003
|
+
FORCE_TRUE = "forced_available"
|
|
1004
|
+
NOT_SET = "not_set"
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
class InternalCustomID(StrEnum):
|
|
1008
|
+
"""Enum for Homematic internal custom IDs."""
|
|
1009
|
+
|
|
1010
|
+
DEFAULT = "cid_default"
|
|
1011
|
+
LINK_PEER = "cid_link_peer"
|
|
1012
|
+
MANU_TEMP = "cid_manu_temp"
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
class Manufacturer(StrEnum):
|
|
1016
|
+
"""Enum with aiohomematic system events."""
|
|
1017
|
+
|
|
1018
|
+
EQ3 = "eQ-3"
|
|
1019
|
+
HB = "Homebrew"
|
|
1020
|
+
MOEHLENHOFF = "Möhlenhoff"
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
class Operations(IntEnum):
|
|
1024
|
+
"""Enum with Homematic operations."""
|
|
1025
|
+
|
|
1026
|
+
NONE = 0 # not used
|
|
1027
|
+
READ = 1
|
|
1028
|
+
WRITE = 2
|
|
1029
|
+
EVENT = 4
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
class OptionalSettings(StrEnum):
|
|
1033
|
+
"""Enum with aiohomematic optional settings."""
|
|
1034
|
+
|
|
1035
|
+
ASYNC_RPC_SERVER = "ASYNC_RPC_SERVER"
|
|
1036
|
+
"""Use async XML-RPC server instead of thread-based (opt-in)."""
|
|
1037
|
+
|
|
1038
|
+
ENABLE_LINKED_ENTITY_CLIMATE_ACTIVITY = "ENABLE_LINKED_ENTITY_CLIMATE_ACTIVITY"
|
|
1039
|
+
SR_DISABLE_RANDOMIZE_OUTPUT = "SR_DISABLE_RANDOMIZED_OUTPUT"
|
|
1040
|
+
SR_RECORD_SYSTEM_INIT = "SR_RECORD_SYSTEM_INIT"
|
|
1041
|
+
|
|
1042
|
+
USE_INTERFACE_CLIENT = "USE_INTERFACE_CLIENT"
|
|
1043
|
+
"""Use InterfaceClient with Backend Strategy Pattern instead of legacy clients (opt-in)."""
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
class Parameter(StrEnum):
|
|
1047
|
+
"""Enum with Homematic parameters."""
|
|
1048
|
+
|
|
1049
|
+
ACOUSTIC_ALARM_ACTIVE = "ACOUSTIC_ALARM_ACTIVE"
|
|
1050
|
+
ACOUSTIC_ALARM_SELECTION = "ACOUSTIC_ALARM_SELECTION"
|
|
1051
|
+
ACOUSTIC_NOTIFICATION_SELECTION = "ACOUSTIC_NOTIFICATION_SELECTION"
|
|
1052
|
+
ACTIVE_PROFILE = "ACTIVE_PROFILE"
|
|
1053
|
+
ACTIVITY_STATE = "ACTIVITY_STATE"
|
|
1054
|
+
ACTUAL_HUMIDITY = "ACTUAL_HUMIDITY"
|
|
1055
|
+
ACTUAL_TEMPERATURE = "ACTUAL_TEMPERATURE"
|
|
1056
|
+
AUTO_MODE = "AUTO_MODE"
|
|
1057
|
+
BATTERY_STATE = "BATTERY_STATE"
|
|
1058
|
+
BOOST_MODE = "BOOST_MODE"
|
|
1059
|
+
BURST_LIMIT_WARNING = "BURST_LIMIT_WARNING"
|
|
1060
|
+
BUTTON_LOCK = "BUTTON_LOCK"
|
|
1061
|
+
CHANNEL_COLOR = "CHANNEL_COLOR"
|
|
1062
|
+
CHANNEL_LOCK = "CHANNEL_LOCK"
|
|
1063
|
+
CHANNEL_OPERATION_MODE = "CHANNEL_OPERATION_MODE"
|
|
1064
|
+
COLOR = "COLOR"
|
|
1065
|
+
COLOR_BEHAVIOUR = "COLOR_BEHAVIOUR"
|
|
1066
|
+
COLOR_TEMPERATURE = "COLOR_TEMPERATURE"
|
|
1067
|
+
COMBINED_PARAMETER = "COMBINED_PARAMETER"
|
|
1068
|
+
COMFORT_MODE = "COMFORT_MODE"
|
|
1069
|
+
CONCENTRATION = "CONCENTRATION"
|
|
1070
|
+
CONFIG_PENDING = "CONFIG_PENDING"
|
|
1071
|
+
CONTROL_MODE = "CONTROL_MODE"
|
|
1072
|
+
CURRENT = "CURRENT"
|
|
1073
|
+
CURRENT_ILLUMINATION = "CURRENT_ILLUMINATION"
|
|
1074
|
+
DEVICE_OPERATION_MODE = "DEVICE_OPERATION_MODE"
|
|
1075
|
+
DIRECTION = "DIRECTION"
|
|
1076
|
+
DIRT_LEVEL = "DIRT_LEVEL"
|
|
1077
|
+
DISPLAY_DATA_ALIGNMENT = "DISPLAY_DATA_ALIGNMENT"
|
|
1078
|
+
DISPLAY_DATA_BACKGROUND_COLOR = "DISPLAY_DATA_BACKGROUND_COLOR"
|
|
1079
|
+
DISPLAY_DATA_ICON = "DISPLAY_DATA_ICON"
|
|
1080
|
+
DISPLAY_DATA_TEXT_COLOR = "DISPLAY_DATA_TEXT_COLOR"
|
|
1081
|
+
DISPLAY_DATA_COMMIT = "DISPLAY_DATA_COMMIT"
|
|
1082
|
+
DISPLAY_DATA_ID = "DISPLAY_DATA_ID"
|
|
1083
|
+
DISPLAY_DATA_STRING = "DISPLAY_DATA_STRING"
|
|
1084
|
+
DOOR_COMMAND = "DOOR_COMMAND"
|
|
1085
|
+
DOOR_STATE = "DOOR_STATE"
|
|
1086
|
+
DURATION_UNIT = "DURATION_UNIT"
|
|
1087
|
+
DURATION_VALUE = "DURATION_VALUE"
|
|
1088
|
+
DUTYCYCLE = "DUTYCYCLE"
|
|
1089
|
+
DUTY_CYCLE = "DUTY_CYCLE"
|
|
1090
|
+
EFFECT = "EFFECT"
|
|
1091
|
+
ENERGY_COUNTER = "ENERGY_COUNTER"
|
|
1092
|
+
ENERGY_COUNTER_FEED_IN = "ENERGY_COUNTER_FEED_IN"
|
|
1093
|
+
ERROR = "ERROR"
|
|
1094
|
+
ERROR_JAMMED = "ERROR_JAMMED"
|
|
1095
|
+
FREQUENCY = "FREQUENCY"
|
|
1096
|
+
GLOBAL_BUTTON_LOCK = "GLOBAL_BUTTON_LOCK"
|
|
1097
|
+
HEATING_COOLING = "HEATING_COOLING"
|
|
1098
|
+
HEATING_VALVE_TYPE = "HEATING_VALVE_TYPE"
|
|
1099
|
+
HUE = "HUE"
|
|
1100
|
+
HUMIDITY = "HUMIDITY"
|
|
1101
|
+
ILLUMINATION = "ILLUMINATION"
|
|
1102
|
+
INTERVAL = "INTERVAL"
|
|
1103
|
+
LED_STATUS = "LED_STATUS"
|
|
1104
|
+
LEVEL = "LEVEL"
|
|
1105
|
+
LEVEL_2 = "LEVEL_2"
|
|
1106
|
+
LEVEL_COMBINED = "LEVEL_COMBINED"
|
|
1107
|
+
LEVEL_SLATS = "LEVEL_SLATS"
|
|
1108
|
+
LOCK_STATE = "LOCK_STATE"
|
|
1109
|
+
LOCK_TARGET_LEVEL = "LOCK_TARGET_LEVEL"
|
|
1110
|
+
LOWBAT = "LOWBAT"
|
|
1111
|
+
LOWERING_MODE = "LOWERING_MODE"
|
|
1112
|
+
LOW_BAT = "LOW_BAT"
|
|
1113
|
+
LOW_BAT_LIMIT = "LOW_BAT_LIMIT"
|
|
1114
|
+
MANU_MODE = "MANU_MODE"
|
|
1115
|
+
MASS_CONCENTRATION_PM_10_24H_AVERAGE = "MASS_CONCENTRATION_PM_10_24H_AVERAGE"
|
|
1116
|
+
MASS_CONCENTRATION_PM_1_24H_AVERAGE = "MASS_CONCENTRATION_PM_1_24H_AVERAGE"
|
|
1117
|
+
MASS_CONCENTRATION_PM_2_5_24H_AVERAGE = "MASS_CONCENTRATION_PM_2_5_24H_AVERAGE"
|
|
1118
|
+
MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE = "MIN_MAX_VALUE_NOT_RELEVANT_FOR_MANU_MODE"
|
|
1119
|
+
MOTION = "MOTION"
|
|
1120
|
+
MOTION_DETECTION_ACTIVE = "MOTION_DETECTION_ACTIVE"
|
|
1121
|
+
ON_TIME = "ON_TIME"
|
|
1122
|
+
ON_TIME_LIST_1 = "ON_TIME_LIST_1"
|
|
1123
|
+
OPEN = "OPEN"
|
|
1124
|
+
OPERATING_VOLTAGE = "OPERATING_VOLTAGE"
|
|
1125
|
+
OPTICAL_ALARM_ACTIVE = "OPTICAL_ALARM_ACTIVE"
|
|
1126
|
+
OPTICAL_ALARM_SELECTION = "OPTICAL_ALARM_SELECTION"
|
|
1127
|
+
OPTIMUM_START_STOP = "OPTIMUM_START_STOP"
|
|
1128
|
+
PARTY_MODE = "PARTY_MODE"
|
|
1129
|
+
PARTY_MODE_SUBMIT = "PARTY_MODE_SUBMIT"
|
|
1130
|
+
PARTY_TIME_END = "PARTY_TIME_END"
|
|
1131
|
+
PARTY_TIME_START = "PARTY_TIME_START"
|
|
1132
|
+
PONG = "PONG"
|
|
1133
|
+
POWER = "POWER"
|
|
1134
|
+
PRESS = "PRESS"
|
|
1135
|
+
PRESS_CONT = "PRESS_CONT"
|
|
1136
|
+
PRESS_LOCK = "PRESS_LOCK"
|
|
1137
|
+
PRESS_LONG = "PRESS_LONG"
|
|
1138
|
+
PRESS_LONG_RELEASE = "PRESS_LONG_RELEASE"
|
|
1139
|
+
PRESS_LONG_START = "PRESS_LONG_START"
|
|
1140
|
+
PRESS_SHORT = "PRESS_SHORT"
|
|
1141
|
+
PRESS_UNLOCK = "PRESS_UNLOCK"
|
|
1142
|
+
PROGRAM = "PROGRAM"
|
|
1143
|
+
RAMP_TIME = "RAMP_TIME"
|
|
1144
|
+
RAMP_TIME_TO_OFF_UNIT = "RAMP_TIME_TO_OFF_UNIT"
|
|
1145
|
+
RAMP_TIME_TO_OFF_VALUE = "RAMP_TIME_TO_OFF_VALUE"
|
|
1146
|
+
RAMP_TIME_UNIT = "RAMP_TIME_UNIT"
|
|
1147
|
+
RAMP_TIME_VALUE = "RAMP_TIME_VALUE"
|
|
1148
|
+
REPETITIONS = "REPETITIONS"
|
|
1149
|
+
RESET_MOTION = "RESET_MOTION"
|
|
1150
|
+
RSSI_DEVICE = "RSSI_DEVICE"
|
|
1151
|
+
RSSI_PEER = "RSSI_PEER"
|
|
1152
|
+
SABOTAGE = "SABOTAGE"
|
|
1153
|
+
SATURATION = "SATURATION"
|
|
1154
|
+
SECTION = "SECTION"
|
|
1155
|
+
SENSOR = "SENSOR"
|
|
1156
|
+
SENSOR_ERROR = "SENSOR_ERROR"
|
|
1157
|
+
SEQUENCE_OK = "SEQUENCE_OK"
|
|
1158
|
+
SETPOINT = "SETPOINT"
|
|
1159
|
+
SET_POINT_MODE = "SET_POINT_MODE"
|
|
1160
|
+
SET_POINT_TEMPERATURE = "SET_POINT_TEMPERATURE"
|
|
1161
|
+
SET_TEMPERATURE = "SET_TEMPERATURE"
|
|
1162
|
+
SMOKE_DETECTOR_ALARM_STATUS = "SMOKE_DETECTOR_ALARM_STATUS"
|
|
1163
|
+
SMOKE_DETECTOR_COMMAND = "SMOKE_DETECTOR_COMMAND"
|
|
1164
|
+
SMOKE_LEVEL = "SMOKE_LEVEL"
|
|
1165
|
+
SOUNDFILE = "SOUNDFILE"
|
|
1166
|
+
STATE = "STATE"
|
|
1167
|
+
STATUS = "STATUS"
|
|
1168
|
+
STICKY_UN_REACH = "STICKY_UNREACH"
|
|
1169
|
+
STOP = "STOP"
|
|
1170
|
+
SUNSHINE_DURATION = "SUNSHINEDURATION"
|
|
1171
|
+
TEMPERATURE = "TEMPERATURE"
|
|
1172
|
+
TEMPERATURE_MAXIMUM = "TEMPERATURE_MAXIMUM"
|
|
1173
|
+
TEMPERATURE_MINIMUM = "TEMPERATURE_MINIMUM"
|
|
1174
|
+
TEMPERATURE_OFFSET = "TEMPERATURE_OFFSET"
|
|
1175
|
+
TIME_OF_OPERATION = "TIME_OF_OPERATION"
|
|
1176
|
+
UN_REACH = "UNREACH"
|
|
1177
|
+
UPDATE_PENDING = "UPDATE_PENDING"
|
|
1178
|
+
VALVE_STATE = "VALVE_STATE"
|
|
1179
|
+
VOLTAGE = "VOLTAGE"
|
|
1180
|
+
WATER_FLOW = "WATER_FLOW"
|
|
1181
|
+
WATER_VOLUME = "WATER_VOLUME"
|
|
1182
|
+
WATER_VOLUME_SINCE_OPEN = "WATER_VOLUME_SINCE_OPEN"
|
|
1183
|
+
WEEK_PROGRAM_POINTER = "WEEK_PROGRAM_POINTER"
|
|
1184
|
+
WIND_DIRECTION = "WIND_DIRECTION"
|
|
1185
|
+
WIND_DIRECTION_RANGE = "WIND_DIRECTION_RANGE"
|
|
1186
|
+
WIND_SPEED = "WIND_SPEED"
|
|
1187
|
+
WORKING = "WORKING"
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
class ParamsetKey(StrEnum):
|
|
1191
|
+
"""Enum with paramset keys."""
|
|
1192
|
+
|
|
1193
|
+
CALCULATED = "CALCULATED"
|
|
1194
|
+
DUMMY = "DUMMY"
|
|
1195
|
+
LINK = "LINK"
|
|
1196
|
+
MASTER = "MASTER"
|
|
1197
|
+
SERVICE = "SERVICE"
|
|
1198
|
+
VALUES = "VALUES"
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
class ProductGroup(StrEnum):
|
|
1202
|
+
"""Enum with Homematic product groups."""
|
|
1203
|
+
|
|
1204
|
+
HM = "BidCos-RF"
|
|
1205
|
+
HMIP = "HmIP-RF"
|
|
1206
|
+
HMIPW = "HmIP-Wired"
|
|
1207
|
+
HMW = "BidCos-Wired"
|
|
1208
|
+
UNKNOWN = "unknown"
|
|
1209
|
+
VIRTUAL = "VirtualDevices"
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
class RegaScript(StrEnum):
|
|
1213
|
+
"""Enum with Homematic rega scripts."""
|
|
1214
|
+
|
|
1215
|
+
ACCEPT_DEVICE_IN_INBOX = "accept_device_in_inbox.fn"
|
|
1216
|
+
CREATE_BACKUP_START = "create_backup_start.fn"
|
|
1217
|
+
CREATE_BACKUP_STATUS = "create_backup_status.fn"
|
|
1218
|
+
FETCH_ALL_DEVICE_DATA = "fetch_all_device_data.fn"
|
|
1219
|
+
GET_BACKEND_INFO = "get_backend_info.fn"
|
|
1220
|
+
GET_INBOX_DEVICES = "get_inbox_devices.fn"
|
|
1221
|
+
GET_PROGRAM_DESCRIPTIONS = "get_program_descriptions.fn"
|
|
1222
|
+
GET_SERIAL = "get_serial.fn"
|
|
1223
|
+
GET_SERVICE_MESSAGES = "get_service_messages.fn"
|
|
1224
|
+
GET_SYSTEM_UPDATE_INFO = "get_system_update_info.fn"
|
|
1225
|
+
GET_SYSTEM_VARIABLE_DESCRIPTIONS = "get_system_variable_descriptions.fn"
|
|
1226
|
+
SET_PROGRAM_STATE = "set_program_state.fn"
|
|
1227
|
+
SET_SYSTEM_VARIABLE = "set_system_variable.fn"
|
|
1228
|
+
TRIGGER_FIRMWARE_UPDATE = "trigger_firmware_update.fn"
|
|
1229
|
+
|
|
1230
|
+
|
|
1231
|
+
class RPCType(StrEnum):
|
|
1232
|
+
"""Enum with Homematic rpc types."""
|
|
1233
|
+
|
|
1234
|
+
XML_RPC = "xmlrpc"
|
|
1235
|
+
JSON_RPC = "jsonrpc"
|
|
1236
|
+
|
|
1237
|
+
|
|
1238
|
+
class Interface(StrEnum):
|
|
1239
|
+
"""Enum with Homematic interfaces."""
|
|
1240
|
+
|
|
1241
|
+
BIDCOS_RF = "BidCos-RF"
|
|
1242
|
+
BIDCOS_WIRED = "BidCos-Wired"
|
|
1243
|
+
CCU_JACK = "CCU-Jack"
|
|
1244
|
+
CUXD = "CUxD"
|
|
1245
|
+
HMIP_RF = "HmIP-RF"
|
|
1246
|
+
VIRTUAL_DEVICES = "VirtualDevices"
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
class PingPongMismatchType(StrEnum):
|
|
1250
|
+
"""Enum for PING/PONG mismatch event types."""
|
|
1251
|
+
|
|
1252
|
+
PENDING = "pending" # PING sent but no PONG received
|
|
1253
|
+
UNKNOWN = "unknown" # PONG received without matching PING
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
class IntegrationIssueSeverity(StrEnum):
|
|
1257
|
+
"""Severity level for integration issues."""
|
|
1258
|
+
|
|
1259
|
+
ERROR = "error"
|
|
1260
|
+
WARNING = "warning"
|
|
1261
|
+
|
|
1262
|
+
|
|
1263
|
+
class IntegrationIssueType(StrEnum):
|
|
1264
|
+
"""
|
|
1265
|
+
Type of integration issue.
|
|
1266
|
+
|
|
1267
|
+
Each value serves as both:
|
|
1268
|
+
- issue_id prefix (e.g., "ping_pong_mismatch_{interface_id}")
|
|
1269
|
+
- translation_key (e.g., "ping_pong_mismatch")
|
|
1270
|
+
"""
|
|
1271
|
+
|
|
1272
|
+
PING_PONG_MISMATCH = "ping_pong_mismatch"
|
|
1273
|
+
FETCH_DATA_FAILED = "fetch_data_failed"
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
class DataRefreshType(StrEnum):
|
|
1277
|
+
"""Type of data refresh operation."""
|
|
1278
|
+
|
|
1279
|
+
CLIENT_DATA = "client_data"
|
|
1280
|
+
CONNECTIVITY = "connectivity"
|
|
1281
|
+
INBOX = "inbox"
|
|
1282
|
+
METRICS = "metrics"
|
|
1283
|
+
PROGRAM = "program"
|
|
1284
|
+
SYSTEM_UPDATE = "system_update"
|
|
1285
|
+
SYSVAR = "sysvar"
|
|
1286
|
+
|
|
1287
|
+
|
|
1288
|
+
class ProgramTrigger(StrEnum):
|
|
1289
|
+
"""Trigger source for program execution."""
|
|
1290
|
+
|
|
1291
|
+
API = "api"
|
|
1292
|
+
USER = "user"
|
|
1293
|
+
SCHEDULER = "scheduler"
|
|
1294
|
+
AUTOMATION = "automation"
|
|
1295
|
+
|
|
1296
|
+
|
|
1297
|
+
class ParameterType(StrEnum):
|
|
1298
|
+
"""Enum for Homematic parameter types."""
|
|
1299
|
+
|
|
1300
|
+
ACTION = "ACTION" # Usually buttons, send Boolean to trigger
|
|
1301
|
+
BOOL = "BOOL"
|
|
1302
|
+
DUMMY = "DUMMY"
|
|
1303
|
+
ENUM = "ENUM"
|
|
1304
|
+
FLOAT = "FLOAT"
|
|
1305
|
+
INTEGER = "INTEGER"
|
|
1306
|
+
STRING = "STRING"
|
|
1307
|
+
EMPTY = ""
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
class ProxyInitState(Enum):
|
|
1311
|
+
"""Enum with proxy handling results."""
|
|
1312
|
+
|
|
1313
|
+
INIT_FAILED = 0
|
|
1314
|
+
INIT_SUCCESS = 1
|
|
1315
|
+
DE_INIT_FAILED = 4
|
|
1316
|
+
DE_INIT_SUCCESS = 8
|
|
1317
|
+
DE_INIT_SKIPPED = 16
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
class ClientState(StrEnum):
|
|
1321
|
+
"""
|
|
1322
|
+
Client connection lifecycle states.
|
|
1323
|
+
|
|
1324
|
+
State Machine
|
|
1325
|
+
-------------
|
|
1326
|
+
The client follows this state machine:
|
|
1327
|
+
|
|
1328
|
+
```
|
|
1329
|
+
CREATED ─────► INITIALIZING ─────► INITIALIZED
|
|
1330
|
+
│ │
|
|
1331
|
+
│ (failure) │
|
|
1332
|
+
▼ ▼
|
|
1333
|
+
FAILED CONNECTING ◄────┐
|
|
1334
|
+
▲ │ │
|
|
1335
|
+
│ │ │ (re-connect)
|
|
1336
|
+
│ (failure) │ │
|
|
1337
|
+
│ ▼ │
|
|
1338
|
+
└───────────── CONNECTED │
|
|
1339
|
+
│ │
|
|
1340
|
+
┌─────────────┼────────┼──┐
|
|
1341
|
+
│ │ │ │
|
|
1342
|
+
▼ ▼ │ ▼
|
|
1343
|
+
DISCONNECTED RECONNECTING──┘ STOPPING
|
|
1344
|
+
│ │ │
|
|
1345
|
+
└─────────────┘ ▼
|
|
1346
|
+
STOPPED
|
|
1347
|
+
```
|
|
1348
|
+
|
|
1349
|
+
Valid Transitions
|
|
1350
|
+
-----------------
|
|
1351
|
+
- CREATED → INITIALIZING
|
|
1352
|
+
- INITIALIZING → INITIALIZED | FAILED
|
|
1353
|
+
- INITIALIZED → CONNECTING
|
|
1354
|
+
- CONNECTING → CONNECTED | FAILED
|
|
1355
|
+
- CONNECTED → DISCONNECTED | RECONNECTING | STOPPING
|
|
1356
|
+
- DISCONNECTED → CONNECTING | RECONNECTING | STOPPING
|
|
1357
|
+
- RECONNECTING → CONNECTED | DISCONNECTED | FAILED | CONNECTING
|
|
1358
|
+
- STOPPING → STOPPED
|
|
1359
|
+
- FAILED → INITIALIZING (retry)
|
|
1360
|
+
"""
|
|
1361
|
+
|
|
1362
|
+
CREATED = "created"
|
|
1363
|
+
INITIALIZING = "initializing"
|
|
1364
|
+
INITIALIZED = "initialized"
|
|
1365
|
+
CONNECTING = "connecting"
|
|
1366
|
+
CONNECTED = "connected"
|
|
1367
|
+
DISCONNECTED = "disconnected"
|
|
1368
|
+
RECONNECTING = "reconnecting"
|
|
1369
|
+
STOPPING = "stopping"
|
|
1370
|
+
STOPPED = "stopped"
|
|
1371
|
+
FAILED = "failed"
|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
class CircuitState(StrEnum):
|
|
1375
|
+
"""Circuit breaker states."""
|
|
1376
|
+
|
|
1377
|
+
CLOSED = "closed"
|
|
1378
|
+
"""Normal operation - requests are allowed through."""
|
|
1379
|
+
|
|
1380
|
+
OPEN = "open"
|
|
1381
|
+
"""Failure mode - requests are immediately rejected."""
|
|
1382
|
+
|
|
1383
|
+
HALF_OPEN = "half_open"
|
|
1384
|
+
"""Test mode - one request is allowed to test recovery."""
|
|
1385
|
+
|
|
1386
|
+
|
|
1387
|
+
class RpcServerType(StrEnum):
|
|
1388
|
+
"""Enum for Homematic rpc server types."""
|
|
1389
|
+
|
|
1390
|
+
XML_RPC = "xml_rpc"
|
|
1391
|
+
NONE = "none"
|
|
1392
|
+
|
|
1393
|
+
|
|
1394
|
+
class RxMode(IntEnum):
|
|
1395
|
+
"""Enum for Homematic rx modes."""
|
|
1396
|
+
|
|
1397
|
+
UNDEFINED = 0
|
|
1398
|
+
ALWAYS = 1
|
|
1399
|
+
BURST = 2
|
|
1400
|
+
CONFIG = 4
|
|
1401
|
+
WAKEUP = 8
|
|
1402
|
+
LAZY_CONFIG = 16
|
|
1403
|
+
|
|
1404
|
+
|
|
1405
|
+
class ServiceMessageType(IntEnum):
|
|
1406
|
+
"""Enum for CCU service message types (AlType)."""
|
|
1407
|
+
|
|
1408
|
+
GENERIC = 0
|
|
1409
|
+
STICKY = 1
|
|
1410
|
+
CONFIG_PENDING = 2
|
|
1411
|
+
|
|
1412
|
+
|
|
1413
|
+
class SourceOfDeviceCreation(StrEnum):
|
|
1414
|
+
"""Enum with source of device creation."""
|
|
1415
|
+
|
|
1416
|
+
CACHE = "CACHE"
|
|
1417
|
+
INIT = "INIT"
|
|
1418
|
+
MANUAL = "MANUAL"
|
|
1419
|
+
NEW = "NEW"
|
|
1420
|
+
REFRESH = "REFRESH"
|
|
1421
|
+
|
|
1422
|
+
|
|
1423
|
+
class HubValueType(StrEnum):
|
|
1424
|
+
"""Enum for Homematic hub value types."""
|
|
1425
|
+
|
|
1426
|
+
ALARM = "ALARM"
|
|
1427
|
+
FLOAT = "FLOAT"
|
|
1428
|
+
INTEGER = "INTEGER"
|
|
1429
|
+
LIST = "LIST"
|
|
1430
|
+
LOGIC = "LOGIC"
|
|
1431
|
+
NUMBER = "NUMBER"
|
|
1432
|
+
STRING = "STRING"
|
|
1433
|
+
|
|
1434
|
+
|
|
1435
|
+
CLICK_EVENTS: Final[frozenset[Parameter]] = frozenset(
|
|
1436
|
+
{
|
|
1437
|
+
Parameter.PRESS,
|
|
1438
|
+
Parameter.PRESS_CONT,
|
|
1439
|
+
Parameter.PRESS_LOCK,
|
|
1440
|
+
Parameter.PRESS_LONG,
|
|
1441
|
+
Parameter.PRESS_LONG_RELEASE,
|
|
1442
|
+
Parameter.PRESS_LONG_START,
|
|
1443
|
+
Parameter.PRESS_SHORT,
|
|
1444
|
+
Parameter.PRESS_UNLOCK,
|
|
1445
|
+
}
|
|
1446
|
+
)
|
|
1447
|
+
|
|
1448
|
+
DEVICE_ERROR_EVENTS: Final[tuple[Parameter, ...]] = (Parameter.ERROR, Parameter.SENSOR_ERROR)
|
|
1449
|
+
|
|
1450
|
+
DATA_POINT_EVENTS: Final[frozenset[DeviceTriggerEventType]] = frozenset(
|
|
1451
|
+
{
|
|
1452
|
+
DeviceTriggerEventType.IMPULSE,
|
|
1453
|
+
DeviceTriggerEventType.KEYPRESS,
|
|
1454
|
+
}
|
|
1455
|
+
)
|
|
1456
|
+
|
|
1457
|
+
|
|
1458
|
+
type DP_KEY_VALUE = tuple[DataPointKey, Any]
|
|
1459
|
+
type SYSVAR_TYPE = bool | float | int | str | None
|
|
1460
|
+
|
|
1461
|
+
HMIP_FIRMWARE_UPDATE_IN_PROGRESS_STATES: Final[frozenset[DeviceFirmwareState]] = frozenset(
|
|
1462
|
+
{
|
|
1463
|
+
DeviceFirmwareState.DO_UPDATE_PENDING,
|
|
1464
|
+
DeviceFirmwareState.PERFORMING_UPDATE,
|
|
1465
|
+
}
|
|
1466
|
+
)
|
|
1467
|
+
|
|
1468
|
+
HMIP_FIRMWARE_UPDATE_READY_STATES: Final[frozenset[DeviceFirmwareState]] = frozenset(
|
|
1469
|
+
{
|
|
1470
|
+
DeviceFirmwareState.READY_FOR_UPDATE,
|
|
1471
|
+
DeviceFirmwareState.DO_UPDATE_PENDING,
|
|
1472
|
+
DeviceFirmwareState.PERFORMING_UPDATE,
|
|
1473
|
+
}
|
|
1474
|
+
)
|
|
1475
|
+
|
|
1476
|
+
IMPULSE_EVENTS: Final[frozenset[Parameter]] = frozenset({Parameter.SEQUENCE_OK})
|
|
1477
|
+
|
|
1478
|
+
KEY_CHANNEL_OPERATION_MODE_VISIBILITY: Final[Mapping[str, frozenset[str]]] = MappingProxyType(
|
|
1479
|
+
{
|
|
1480
|
+
Parameter.STATE: frozenset({"BINARY_BEHAVIOR"}),
|
|
1481
|
+
Parameter.PRESS_LONG: frozenset({"KEY_BEHAVIOR", "SWITCH_BEHAVIOR"}),
|
|
1482
|
+
Parameter.PRESS_LONG_RELEASE: frozenset({"KEY_BEHAVIOR", "SWITCH_BEHAVIOR"}),
|
|
1483
|
+
Parameter.PRESS_LONG_START: frozenset({"KEY_BEHAVIOR", "SWITCH_BEHAVIOR"}),
|
|
1484
|
+
Parameter.PRESS_SHORT: frozenset({"KEY_BEHAVIOR", "SWITCH_BEHAVIOR"}),
|
|
1485
|
+
}
|
|
1486
|
+
)
|
|
1487
|
+
|
|
1488
|
+
BLOCKED_CATEGORIES: Final[tuple[DataPointCategory, ...]] = (
|
|
1489
|
+
DataPointCategory.ACTION,
|
|
1490
|
+
DataPointCategory.ACTION_SELECT,
|
|
1491
|
+
)
|
|
1492
|
+
|
|
1493
|
+
HUB_CATEGORIES: Final[tuple[DataPointCategory, ...]] = (
|
|
1494
|
+
DataPointCategory.HUB_BINARY_SENSOR,
|
|
1495
|
+
DataPointCategory.HUB_BUTTON,
|
|
1496
|
+
DataPointCategory.HUB_NUMBER,
|
|
1497
|
+
DataPointCategory.HUB_SELECT,
|
|
1498
|
+
DataPointCategory.HUB_SENSOR,
|
|
1499
|
+
DataPointCategory.HUB_SWITCH,
|
|
1500
|
+
DataPointCategory.HUB_TEXT,
|
|
1501
|
+
DataPointCategory.HUB_UPDATE,
|
|
1502
|
+
)
|
|
1503
|
+
|
|
1504
|
+
CATEGORIES: Final[tuple[DataPointCategory, ...]] = (
|
|
1505
|
+
DataPointCategory.ACTION_SELECT,
|
|
1506
|
+
DataPointCategory.BINARY_SENSOR,
|
|
1507
|
+
DataPointCategory.BUTTON,
|
|
1508
|
+
DataPointCategory.CLIMATE,
|
|
1509
|
+
DataPointCategory.COVER,
|
|
1510
|
+
DataPointCategory.EVENT,
|
|
1511
|
+
DataPointCategory.LIGHT,
|
|
1512
|
+
DataPointCategory.LOCK,
|
|
1513
|
+
DataPointCategory.NUMBER,
|
|
1514
|
+
DataPointCategory.SELECT,
|
|
1515
|
+
DataPointCategory.SENSOR,
|
|
1516
|
+
DataPointCategory.SIREN,
|
|
1517
|
+
DataPointCategory.SWITCH,
|
|
1518
|
+
DataPointCategory.TEXT,
|
|
1519
|
+
DataPointCategory.TEXT_DISPLAY,
|
|
1520
|
+
DataPointCategory.UPDATE,
|
|
1521
|
+
DataPointCategory.VALVE,
|
|
1522
|
+
)
|
|
1523
|
+
|
|
1524
|
+
PRIMARY_CLIENT_CANDIDATE_INTERFACES: Final[frozenset[Interface]] = frozenset(
|
|
1525
|
+
{
|
|
1526
|
+
Interface.HMIP_RF,
|
|
1527
|
+
Interface.BIDCOS_RF,
|
|
1528
|
+
Interface.BIDCOS_WIRED,
|
|
1529
|
+
}
|
|
1530
|
+
)
|
|
1531
|
+
|
|
1532
|
+
RELEVANT_INIT_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
|
|
1533
|
+
{
|
|
1534
|
+
Parameter.CONFIG_PENDING,
|
|
1535
|
+
Parameter.STICKY_UN_REACH,
|
|
1536
|
+
Parameter.UN_REACH,
|
|
1537
|
+
}
|
|
1538
|
+
)
|
|
1539
|
+
|
|
1540
|
+
INTERFACES_SUPPORTING_FIRMWARE_UPDATES: Final[frozenset[Interface]] = frozenset(
|
|
1541
|
+
{
|
|
1542
|
+
Interface.BIDCOS_RF,
|
|
1543
|
+
Interface.BIDCOS_WIRED,
|
|
1544
|
+
Interface.HMIP_RF,
|
|
1545
|
+
}
|
|
1546
|
+
)
|
|
1547
|
+
|
|
1548
|
+
INTERFACES_REQUIRING_XML_RPC: Final[frozenset[Interface]] = frozenset(
|
|
1549
|
+
{
|
|
1550
|
+
Interface.BIDCOS_RF,
|
|
1551
|
+
Interface.BIDCOS_WIRED,
|
|
1552
|
+
Interface.HMIP_RF,
|
|
1553
|
+
Interface.VIRTUAL_DEVICES,
|
|
1554
|
+
}
|
|
1555
|
+
)
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
INTERFACES_SUPPORTING_RPC_CALLBACK: Final[frozenset[Interface]] = frozenset(INTERFACES_REQUIRING_XML_RPC)
|
|
1559
|
+
|
|
1560
|
+
|
|
1561
|
+
INTERFACES_REQUIRING_JSON_RPC_CLIENT: Final[frozenset[Interface]] = frozenset(
|
|
1562
|
+
{
|
|
1563
|
+
Interface.CUXD,
|
|
1564
|
+
Interface.CCU_JACK,
|
|
1565
|
+
}
|
|
1566
|
+
)
|
|
1567
|
+
|
|
1568
|
+
DEFAULT_INTERFACES_REQUIRING_PERIODIC_REFRESH: Final[frozenset[Interface]] = frozenset(
|
|
1569
|
+
INTERFACES_REQUIRING_JSON_RPC_CLIENT - INTERFACES_REQUIRING_XML_RPC
|
|
1570
|
+
)
|
|
1571
|
+
|
|
1572
|
+
INTERFACE_RPC_SERVER_TYPE: Final[Mapping[Interface, RpcServerType]] = MappingProxyType(
|
|
1573
|
+
{
|
|
1574
|
+
Interface.BIDCOS_RF: RpcServerType.XML_RPC,
|
|
1575
|
+
Interface.BIDCOS_WIRED: RpcServerType.XML_RPC,
|
|
1576
|
+
Interface.HMIP_RF: RpcServerType.XML_RPC,
|
|
1577
|
+
Interface.VIRTUAL_DEVICES: RpcServerType.XML_RPC,
|
|
1578
|
+
Interface.CUXD: RpcServerType.NONE,
|
|
1579
|
+
Interface.CCU_JACK: RpcServerType.NONE,
|
|
1580
|
+
}
|
|
1581
|
+
)
|
|
1582
|
+
|
|
1583
|
+
LINKABLE_INTERFACES: Final[frozenset[Interface]] = frozenset(
|
|
1584
|
+
{
|
|
1585
|
+
Interface.BIDCOS_RF,
|
|
1586
|
+
Interface.BIDCOS_WIRED,
|
|
1587
|
+
Interface.HMIP_RF,
|
|
1588
|
+
}
|
|
1589
|
+
)
|
|
1590
|
+
|
|
1591
|
+
|
|
1592
|
+
DEFAULT_USE_PERIODIC_SCAN_FOR_INTERFACES: Final = True
|
|
1593
|
+
|
|
1594
|
+
IGNORE_FOR_UN_IGNORE_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
|
|
1595
|
+
{
|
|
1596
|
+
Parameter.CONFIG_PENDING,
|
|
1597
|
+
Parameter.STICKY_UN_REACH,
|
|
1598
|
+
Parameter.UN_REACH,
|
|
1599
|
+
}
|
|
1600
|
+
)
|
|
1601
|
+
|
|
1602
|
+
|
|
1603
|
+
# Ignore Parameter on initial load that end with
|
|
1604
|
+
_IGNORE_ON_INITIAL_LOAD_PARAMETERS_END_RE: Final = re.compile(r".*(_ERROR)$")
|
|
1605
|
+
# Ignore Parameter on initial load that start with
|
|
1606
|
+
_IGNORE_ON_INITIAL_LOAD_PARAMETERS_START_RE: Final = re.compile(r"^(ERROR_|RSSI_)")
|
|
1607
|
+
_IGNORE_ON_INITIAL_LOAD_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
|
|
1608
|
+
{
|
|
1609
|
+
Parameter.DUTY_CYCLE,
|
|
1610
|
+
Parameter.DUTYCYCLE,
|
|
1611
|
+
Parameter.LOW_BAT,
|
|
1612
|
+
Parameter.LOWBAT,
|
|
1613
|
+
Parameter.OPERATING_VOLTAGE,
|
|
1614
|
+
}
|
|
1615
|
+
)
|
|
1616
|
+
|
|
1617
|
+
_CLIMATE_SOURCE_ROLES: Final[tuple[str, ...]] = ("CLIMATE",)
|
|
1618
|
+
_CLIMATE_TARGET_ROLES: Final[tuple[str, ...]] = ("CLIMATE", "SWITCH", "LEVEL")
|
|
1619
|
+
_CLIMATE_TRANSMITTER_RE: Final = re.compile(r"(?:CLIMATE|HEATING).*(?:TRANSMITTER|TRANSCEIVER)")
|
|
1620
|
+
_CLIMATE_RECEIVER_RE: Final = re.compile(r"(?:CLIMATE|HEATING).*(?:TRANSCEIVER|RECEIVER)")
|
|
1621
|
+
|
|
1622
|
+
|
|
1623
|
+
def get_link_source_categories(
|
|
1624
|
+
*, source_roles: tuple[str, ...], channel_type_name: str
|
|
1625
|
+
) -> tuple[DataPointCategory, ...]:
|
|
1626
|
+
"""Return the channel sender roles."""
|
|
1627
|
+
result: set[DataPointCategory] = set()
|
|
1628
|
+
has_climate = False
|
|
1629
|
+
if _CLIMATE_TRANSMITTER_RE.search(channel_type_name):
|
|
1630
|
+
result.add(DataPointCategory.CLIMATE)
|
|
1631
|
+
has_climate = True
|
|
1632
|
+
|
|
1633
|
+
if not has_climate and source_roles and any("CLIMATE" in role for role in source_roles):
|
|
1634
|
+
result.add(DataPointCategory.CLIMATE)
|
|
1635
|
+
|
|
1636
|
+
return tuple(result)
|
|
1637
|
+
|
|
1638
|
+
|
|
1639
|
+
def get_link_target_categories(
|
|
1640
|
+
*, target_roles: tuple[str, ...], channel_type_name: str
|
|
1641
|
+
) -> tuple[DataPointCategory, ...]:
|
|
1642
|
+
"""Return the channel receiver roles."""
|
|
1643
|
+
result: set[DataPointCategory] = set()
|
|
1644
|
+
has_climate = False
|
|
1645
|
+
if _CLIMATE_RECEIVER_RE.search(channel_type_name):
|
|
1646
|
+
result.add(DataPointCategory.CLIMATE)
|
|
1647
|
+
has_climate = True
|
|
1648
|
+
|
|
1649
|
+
if (
|
|
1650
|
+
not has_climate
|
|
1651
|
+
and target_roles
|
|
1652
|
+
and any(cl_role in role for role in target_roles for cl_role in _CLIMATE_TARGET_ROLES)
|
|
1653
|
+
):
|
|
1654
|
+
result.add(DataPointCategory.CLIMATE)
|
|
1655
|
+
|
|
1656
|
+
return tuple(result)
|
|
1657
|
+
|
|
1658
|
+
|
|
1659
|
+
RECEIVER_PARAMETERS: Final[frozenset[Parameter]] = frozenset(
|
|
1660
|
+
{
|
|
1661
|
+
Parameter.LEVEL,
|
|
1662
|
+
Parameter.STATE,
|
|
1663
|
+
}
|
|
1664
|
+
)
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
def check_ignore_parameter_on_initial_load(*, parameter: str) -> bool:
|
|
1668
|
+
"""Check if a parameter matches common wildcard patterns."""
|
|
1669
|
+
return (
|
|
1670
|
+
bool(_IGNORE_ON_INITIAL_LOAD_PARAMETERS_START_RE.match(parameter))
|
|
1671
|
+
or bool(_IGNORE_ON_INITIAL_LOAD_PARAMETERS_END_RE.match(parameter))
|
|
1672
|
+
or parameter in _IGNORE_ON_INITIAL_LOAD_PARAMETERS
|
|
1673
|
+
)
|
|
1674
|
+
|
|
1675
|
+
|
|
1676
|
+
# Ignore Parameter on initial load that start with
|
|
1677
|
+
_IGNORE_ON_INITIAL_LOAD_MODEL_START_RE: Final = re.compile(r"^(HmIP-SWSD)")
|
|
1678
|
+
_IGNORE_ON_INITIAL_LOAD_MODEL: Final = ("HmIP-SWD",)
|
|
1679
|
+
_IGNORE_ON_INITIAL_LOAD_MODEL_LOWER: Final = tuple(model.lower() for model in _IGNORE_ON_INITIAL_LOAD_MODEL)
|
|
1680
|
+
|
|
1681
|
+
|
|
1682
|
+
def check_ignore_model_on_initial_load(*, model: str) -> bool:
|
|
1683
|
+
"""Check if a model matches common wildcard patterns."""
|
|
1684
|
+
return (
|
|
1685
|
+
bool(_IGNORE_ON_INITIAL_LOAD_MODEL_START_RE.match(model))
|
|
1686
|
+
or model.lower() in _IGNORE_ON_INITIAL_LOAD_MODEL_LOWER
|
|
1687
|
+
)
|
|
1688
|
+
|
|
1689
|
+
|
|
1690
|
+
# virtual remotes s
|
|
1691
|
+
VIRTUAL_REMOTE_MODELS: Final[tuple[str, ...]] = (
|
|
1692
|
+
"HM-RCV-50",
|
|
1693
|
+
"HMW-RCV-50",
|
|
1694
|
+
"HmIP-RCV-50",
|
|
1695
|
+
)
|
|
1696
|
+
|
|
1697
|
+
VIRTUAL_REMOTE_ADDRESSES: Final[tuple[str, ...]] = (
|
|
1698
|
+
"BidCoS-RF",
|
|
1699
|
+
"BidCoS-Wir",
|
|
1700
|
+
"HmIP-RCV-1",
|
|
1701
|
+
)
|
|
1702
|
+
|
|
1703
|
+
|
|
1704
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
1705
|
+
class HubData:
|
|
1706
|
+
"""Dataclass for hub data points."""
|
|
1707
|
+
|
|
1708
|
+
legacy_name: str
|
|
1709
|
+
enabled_default: bool = False
|
|
1710
|
+
description: str | None = None
|
|
1711
|
+
|
|
1712
|
+
|
|
1713
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
1714
|
+
class ProgramData(HubData):
|
|
1715
|
+
"""Dataclass for programs."""
|
|
1716
|
+
|
|
1717
|
+
pid: str
|
|
1718
|
+
is_active: bool
|
|
1719
|
+
is_internal: bool
|
|
1720
|
+
last_execute_time: str
|
|
1721
|
+
|
|
1722
|
+
|
|
1723
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
1724
|
+
class SystemVariableData(HubData):
|
|
1725
|
+
"""Dataclass for system variables."""
|
|
1726
|
+
|
|
1727
|
+
vid: str
|
|
1728
|
+
value: SYSVAR_TYPE
|
|
1729
|
+
data_type: HubValueType | None = None
|
|
1730
|
+
extended_sysvar: bool = False
|
|
1731
|
+
max_value: float | int | None = None
|
|
1732
|
+
min_value: float | int | None = None
|
|
1733
|
+
unit: str | None = None
|
|
1734
|
+
values: tuple[str, ...] | None = None
|
|
1735
|
+
|
|
1736
|
+
|
|
1737
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
1738
|
+
class InstallModeData:
|
|
1739
|
+
"""Dataclass for install mode data points."""
|
|
1740
|
+
|
|
1741
|
+
name: str
|
|
1742
|
+
interface: Interface
|
|
1743
|
+
|
|
1744
|
+
|
|
1745
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
1746
|
+
class SystemInformation:
|
|
1747
|
+
"""
|
|
1748
|
+
System information of the backend.
|
|
1749
|
+
|
|
1750
|
+
CCU types:
|
|
1751
|
+
- CCU: Original CCU2/CCU3 hardware and debmatic (CCU clone)
|
|
1752
|
+
- OPENCCU: OpenCCU (modern variants)
|
|
1753
|
+
"""
|
|
1754
|
+
|
|
1755
|
+
available_interfaces: tuple[str, ...] = field(default_factory=tuple)
|
|
1756
|
+
auth_enabled: bool | None = None
|
|
1757
|
+
https_redirect_enabled: bool | None = None
|
|
1758
|
+
serial: str | None = None
|
|
1759
|
+
# Backend info fields
|
|
1760
|
+
version: str = ""
|
|
1761
|
+
hostname: str = ""
|
|
1762
|
+
ccu_type: CCUType = CCUType.UNKNOWN
|
|
1763
|
+
|
|
1764
|
+
@property
|
|
1765
|
+
def has_backup(self) -> bool:
|
|
1766
|
+
"""Return True if backend supports online firmware update checks."""
|
|
1767
|
+
return self.ccu_type == CCUType.OPENCCU
|
|
1768
|
+
|
|
1769
|
+
|
|
1770
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
1771
|
+
class InboxDeviceData:
|
|
1772
|
+
"""Dataclass for inbox devices."""
|
|
1773
|
+
|
|
1774
|
+
device_id: str
|
|
1775
|
+
address: str
|
|
1776
|
+
name: str
|
|
1777
|
+
device_type: str
|
|
1778
|
+
interface: str
|
|
1779
|
+
|
|
1780
|
+
|
|
1781
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
1782
|
+
class ServiceMessageData:
|
|
1783
|
+
"""Dataclass for service messages."""
|
|
1784
|
+
|
|
1785
|
+
msg_id: str
|
|
1786
|
+
name: str
|
|
1787
|
+
timestamp: str
|
|
1788
|
+
msg_type: int
|
|
1789
|
+
address: str = ""
|
|
1790
|
+
device_name: str = ""
|
|
1791
|
+
|
|
1792
|
+
|
|
1793
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
1794
|
+
class SystemUpdateData:
|
|
1795
|
+
"""Dataclass for system update information."""
|
|
1796
|
+
|
|
1797
|
+
current_firmware: str
|
|
1798
|
+
available_firmware: str
|
|
1799
|
+
update_available: bool
|
|
1800
|
+
check_script_available: bool = False
|
|
1801
|
+
|
|
1802
|
+
|
|
1803
|
+
class BackupStatus(StrEnum):
|
|
1804
|
+
"""Enum with backup status values."""
|
|
1805
|
+
|
|
1806
|
+
IDLE = "idle"
|
|
1807
|
+
RUNNING = "running"
|
|
1808
|
+
COMPLETED = "completed"
|
|
1809
|
+
FAILED = "failed"
|
|
1810
|
+
|
|
1811
|
+
|
|
1812
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
1813
|
+
class BackupStatusData:
|
|
1814
|
+
"""Dataclass for backup status information."""
|
|
1815
|
+
|
|
1816
|
+
status: BackupStatus
|
|
1817
|
+
file_path: str = ""
|
|
1818
|
+
filename: str = ""
|
|
1819
|
+
size: int = 0
|
|
1820
|
+
|
|
1821
|
+
|
|
1822
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
1823
|
+
class BackupData:
|
|
1824
|
+
"""Dataclass for backup download result."""
|
|
1825
|
+
|
|
1826
|
+
filename: str
|
|
1827
|
+
content: bytes
|
|
1828
|
+
|
|
1829
|
+
|
|
1830
|
+
class ParameterData(TypedDict, total=False):
|
|
1831
|
+
"""Typed dict for parameter data."""
|
|
1832
|
+
|
|
1833
|
+
DEFAULT: Any
|
|
1834
|
+
FLAGS: int
|
|
1835
|
+
ID: str
|
|
1836
|
+
MAX: Any
|
|
1837
|
+
MIN: Any
|
|
1838
|
+
OPERATIONS: int
|
|
1839
|
+
SPECIAL: Mapping[str, Any]
|
|
1840
|
+
TYPE: ParameterType
|
|
1841
|
+
UNIT: str
|
|
1842
|
+
VALUE_LIST: Iterable[str]
|
|
1843
|
+
|
|
1844
|
+
|
|
1845
|
+
class DeviceDescription(TypedDict, total=False):
|
|
1846
|
+
"""
|
|
1847
|
+
Typed dict for device descriptions.
|
|
1848
|
+
|
|
1849
|
+
Based on HM_XmlRpc_API.pdf V2.16 and HMIP_XmlRpc_API_Addendum.pdf V2.10.
|
|
1850
|
+
"""
|
|
1851
|
+
|
|
1852
|
+
# Required fields per API spec
|
|
1853
|
+
TYPE: Required[str]
|
|
1854
|
+
ADDRESS: Required[str]
|
|
1855
|
+
PARAMSETS: Required[list[str]]
|
|
1856
|
+
# Optional fields - Common
|
|
1857
|
+
CHILDREN: list[str]
|
|
1858
|
+
PARENT: str | None
|
|
1859
|
+
PARENT_TYPE: str | None
|
|
1860
|
+
SUBTYPE: str | None
|
|
1861
|
+
# Optional fields - Firmware
|
|
1862
|
+
FIRMWARE: str | None
|
|
1863
|
+
AVAILABLE_FIRMWARE: str | None
|
|
1864
|
+
UPDATABLE: bool
|
|
1865
|
+
FIRMWARE_UPDATE_STATE: str | None
|
|
1866
|
+
FIRMWARE_UPDATABLE: bool | None
|
|
1867
|
+
# Optional fields - Interface/Connectivity
|
|
1868
|
+
INTERFACE: str | None
|
|
1869
|
+
RX_MODE: int | None
|
|
1870
|
+
# Optional fields - Links
|
|
1871
|
+
LINK_SOURCE_ROLES: str | None
|
|
1872
|
+
LINK_TARGET_ROLES: str | None
|
|
1873
|
+
# Optional fields - Device metadata
|
|
1874
|
+
RF_ADDRESS: int | None
|
|
1875
|
+
INDEX: int | None
|
|
1876
|
+
AES_ACTIVE: int | None
|
|
1877
|
+
VERSION: int | None
|
|
1878
|
+
FLAGS: int | None
|
|
1879
|
+
DIRECTION: int | None
|
|
1880
|
+
# Optional fields - Groups/Teams
|
|
1881
|
+
GROUP: str | None
|
|
1882
|
+
TEAM: str | None
|
|
1883
|
+
TEAM_TAG: str | None
|
|
1884
|
+
TEAM_CHANNELS: list[str] | None
|
|
1885
|
+
ROAMING: int | None
|
|
1886
|
+
|
|
1887
|
+
|
|
1888
|
+
class ChannelDetail(TypedDict):
|
|
1889
|
+
"""Typed dict for channel details from JSON-RPC Device.listAllDetail."""
|
|
1890
|
+
|
|
1891
|
+
address: str
|
|
1892
|
+
name: str
|
|
1893
|
+
id: int
|
|
1894
|
+
|
|
1895
|
+
|
|
1896
|
+
class DeviceDetail(TypedDict):
|
|
1897
|
+
"""Typed dict for device details from JSON-RPC Device.listAllDetail."""
|
|
1898
|
+
|
|
1899
|
+
address: str
|
|
1900
|
+
name: str
|
|
1901
|
+
id: int
|
|
1902
|
+
interface: str
|
|
1903
|
+
channels: list[ChannelDetail]
|
|
1904
|
+
|
|
1905
|
+
|
|
1906
|
+
# Interface default ports mapping
|
|
1907
|
+
_INTERFACE_DEFAULT_PORTS: Final[dict[str, tuple[int, int]]] = {
|
|
1908
|
+
"BidCos-RF": DETECTION_PORT_BIDCOS_RF,
|
|
1909
|
+
"BidCos-Wired": DETECTION_PORT_BIDCOS_WIRED,
|
|
1910
|
+
"HmIP-RF": DETECTION_PORT_HMIP_RF,
|
|
1911
|
+
"VirtualDevices": DETECTION_PORT_VIRTUAL_DEVICES,
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
|
|
1915
|
+
def get_interface_default_port(*, interface: Interface | str, tls: bool) -> int | None:
|
|
1916
|
+
"""
|
|
1917
|
+
Get the default port for an interface based on TLS setting.
|
|
1918
|
+
|
|
1919
|
+
Args:
|
|
1920
|
+
interface: The interface (Interface enum or string value).
|
|
1921
|
+
tls: Whether TLS is enabled.
|
|
1922
|
+
|
|
1923
|
+
Returns:
|
|
1924
|
+
The default port number, or None if the interface has no default port
|
|
1925
|
+
(e.g., CCU-Jack, CUxD which don't use XML-RPC ports).
|
|
1926
|
+
|
|
1927
|
+
Example:
|
|
1928
|
+
>>> get_interface_default_port(Interface.HMIP_RF, tls=False)
|
|
1929
|
+
2010
|
|
1930
|
+
>>> get_interface_default_port(Interface.HMIP_RF, tls=True)
|
|
1931
|
+
42010
|
|
1932
|
+
>>> get_interface_default_port(Interface.CCU_JACK, tls=True)
|
|
1933
|
+
None
|
|
1934
|
+
|
|
1935
|
+
"""
|
|
1936
|
+
interface_key = interface.value if isinstance(interface, Interface) else interface
|
|
1937
|
+
if ports := _INTERFACE_DEFAULT_PORTS.get(interface_key):
|
|
1938
|
+
return ports[1] if tls else ports[0]
|
|
1939
|
+
return None
|
|
1940
|
+
|
|
1941
|
+
|
|
1942
|
+
def get_json_rpc_default_port(*, tls: bool) -> int:
|
|
1943
|
+
"""
|
|
1944
|
+
Get the default JSON-RPC port based on TLS setting.
|
|
1945
|
+
|
|
1946
|
+
Args:
|
|
1947
|
+
tls: Whether TLS is enabled.
|
|
1948
|
+
|
|
1949
|
+
Returns:
|
|
1950
|
+
The default JSON-RPC port (443 for TLS, 80 for non-TLS).
|
|
1951
|
+
|
|
1952
|
+
"""
|
|
1953
|
+
return DEFAULT_JSON_RPC_TLS_PORT if tls else DEFAULT_JSON_RPC_PORT
|
|
1954
|
+
|
|
1955
|
+
|
|
1956
|
+
def is_interface_default_port(*, interface: Interface | str, port: int) -> bool:
|
|
1957
|
+
"""
|
|
1958
|
+
Check if a port is a default port (TLS or non-TLS) for the given interface.
|
|
1959
|
+
|
|
1960
|
+
Args:
|
|
1961
|
+
interface: The interface (Interface enum or string value).
|
|
1962
|
+
port: The port number to check.
|
|
1963
|
+
|
|
1964
|
+
Returns:
|
|
1965
|
+
True if the port is either the TLS or non-TLS default for this interface.
|
|
1966
|
+
|
|
1967
|
+
"""
|
|
1968
|
+
interface_key = interface.value if isinstance(interface, Interface) else interface
|
|
1969
|
+
if ports := _INTERFACE_DEFAULT_PORTS.get(interface_key):
|
|
1970
|
+
return port in ports
|
|
1971
|
+
return False
|
|
1972
|
+
|
|
1973
|
+
|
|
1974
|
+
class AstroType(IntEnum):
|
|
1975
|
+
"""Enum for astro event types."""
|
|
1976
|
+
|
|
1977
|
+
SUNRISE = 0
|
|
1978
|
+
SUNSET = 1
|
|
1979
|
+
|
|
1980
|
+
|
|
1981
|
+
class ScheduleActorChannel(IntEnum):
|
|
1982
|
+
"""Enum for target actor channels (bitwise)."""
|
|
1983
|
+
|
|
1984
|
+
CHANNEL_1_1 = 1
|
|
1985
|
+
CHANNEL_1_2 = 2
|
|
1986
|
+
CHANNEL_1_3 = 4
|
|
1987
|
+
CHANNEL_2_1 = 8
|
|
1988
|
+
CHANNEL_2_2 = 16
|
|
1989
|
+
CHANNEL_2_3 = 32
|
|
1990
|
+
CHANNEL_3_1 = 64
|
|
1991
|
+
CHANNEL_3_2 = 128
|
|
1992
|
+
CHANNEL_3_3 = 256
|
|
1993
|
+
CHANNEL_4_1 = 512
|
|
1994
|
+
CHANNEL_4_2 = 1024
|
|
1995
|
+
CHANNEL_4_3 = 2048
|
|
1996
|
+
CHANNEL_5_1 = 4096
|
|
1997
|
+
CHANNEL_5_2 = 8192
|
|
1998
|
+
CHANNEL_5_3 = 16384
|
|
1999
|
+
CHANNEL_6_1 = 32768
|
|
2000
|
+
CHANNEL_6_2 = 65536
|
|
2001
|
+
CHANNEL_6_3 = 131072
|
|
2002
|
+
CHANNEL_7_1 = 262144
|
|
2003
|
+
CHANNEL_7_2 = 524288
|
|
2004
|
+
CHANNEL_7_3 = 1048576
|
|
2005
|
+
CHANNEL_8_1 = 2097152
|
|
2006
|
+
CHANNEL_8_2 = 4194304
|
|
2007
|
+
CHANNEL_8_3 = 8388608
|
|
2008
|
+
|
|
2009
|
+
|
|
2010
|
+
class ScheduleCondition(IntEnum):
|
|
2011
|
+
"""Enum for schedule trigger conditions."""
|
|
2012
|
+
|
|
2013
|
+
FIXED_TIME = 0
|
|
2014
|
+
ASTRO = 1
|
|
2015
|
+
FIXED_IF_BEFORE_ASTRO = 2
|
|
2016
|
+
ASTRO_IF_BEFORE_FIXED = 3
|
|
2017
|
+
FIXED_IF_AFTER_ASTRO = 4
|
|
2018
|
+
ASTRO_IF_AFTER_FIXED = 5
|
|
2019
|
+
EARLIEST_OF_FIXED_AND_ASTRO = 6
|
|
2020
|
+
LATEST_OF_FIXED_AND_ASTRO = 7
|
|
2021
|
+
|
|
2022
|
+
|
|
2023
|
+
class ScheduleField(StrEnum):
|
|
2024
|
+
"""Enum for switch schedule field names."""
|
|
2025
|
+
|
|
2026
|
+
ASTRO_OFFSET = "ASTRO_OFFSET"
|
|
2027
|
+
ASTRO_TYPE = "ASTRO_TYPE"
|
|
2028
|
+
CONDITION = "CONDITION"
|
|
2029
|
+
DURATION_BASE = "DURATION_BASE"
|
|
2030
|
+
DURATION_FACTOR = "DURATION_FACTOR"
|
|
2031
|
+
FIXED_HOUR = "FIXED_HOUR"
|
|
2032
|
+
FIXED_MINUTE = "FIXED_MINUTE"
|
|
2033
|
+
LEVEL = "LEVEL"
|
|
2034
|
+
LEVEL_2 = "LEVEL_2"
|
|
2035
|
+
RAMP_TIME_BASE = "RAMP_TIME_BASE"
|
|
2036
|
+
RAMP_TIME_FACTOR = "RAMP_TIME_FACTOR"
|
|
2037
|
+
TARGET_CHANNELS = "TARGET_CHANNELS"
|
|
2038
|
+
WEEKDAY = "WEEKDAY"
|
|
2039
|
+
|
|
2040
|
+
|
|
2041
|
+
class ScheduleSlotType(StrEnum):
|
|
2042
|
+
"""Enum for climate item type."""
|
|
2043
|
+
|
|
2044
|
+
ENDTIME = "ENDTIME"
|
|
2045
|
+
STARTTIME = "STARTTIME"
|
|
2046
|
+
TEMPERATURE = "TEMPERATURE"
|
|
2047
|
+
|
|
2048
|
+
|
|
2049
|
+
class ScheduleProfile(StrEnum):
|
|
2050
|
+
"""Enum for climate profiles."""
|
|
2051
|
+
|
|
2052
|
+
P1 = "P1"
|
|
2053
|
+
P2 = "P2"
|
|
2054
|
+
P3 = "P3"
|
|
2055
|
+
P4 = "P4"
|
|
2056
|
+
P5 = "P5"
|
|
2057
|
+
P6 = "P6"
|
|
2058
|
+
|
|
2059
|
+
|
|
2060
|
+
class TimeBase(IntEnum):
|
|
2061
|
+
"""Enum for duration base units."""
|
|
2062
|
+
|
|
2063
|
+
MS_100 = 0 # 100 milliseconds
|
|
2064
|
+
SEC_1 = 1 # 1 second
|
|
2065
|
+
SEC_5 = 2 # 5 seconds
|
|
2066
|
+
SEC_10 = 3 # 10 seconds
|
|
2067
|
+
MIN_1 = 4 # 1 minute
|
|
2068
|
+
MIN_5 = 5 # 5 minutes
|
|
2069
|
+
MIN_10 = 6 # 10 minutes
|
|
2070
|
+
HOUR_1 = 7 # 1 hour
|
|
2071
|
+
|
|
2072
|
+
|
|
2073
|
+
class WeekdayInt(IntEnum):
|
|
2074
|
+
"""Enum for weekdays (bitwise)."""
|
|
2075
|
+
|
|
2076
|
+
SUNDAY = 1
|
|
2077
|
+
MONDAY = 2
|
|
2078
|
+
TUESDAY = 4
|
|
2079
|
+
WEDNESDAY = 8
|
|
2080
|
+
THURSDAY = 16
|
|
2081
|
+
FRIDAY = 32
|
|
2082
|
+
SATURDAY = 64
|
|
2083
|
+
|
|
2084
|
+
|
|
2085
|
+
class WeekdayStr(StrEnum):
|
|
2086
|
+
"""Enum for climate week days."""
|
|
2087
|
+
|
|
2088
|
+
MONDAY = "MONDAY"
|
|
2089
|
+
TUESDAY = "TUESDAY"
|
|
2090
|
+
WEDNESDAY = "WEDNESDAY"
|
|
2091
|
+
THURSDAY = "THURSDAY"
|
|
2092
|
+
FRIDAY = "FRIDAY"
|
|
2093
|
+
SATURDAY = "SATURDAY"
|
|
2094
|
+
SUNDAY = "SUNDAY"
|
|
2095
|
+
|
|
2096
|
+
|
|
2097
|
+
CLIMATE_MAX_SCHEDULER_TIME: Final = "24:00"
|
|
2098
|
+
CLIMATE_MIN_SCHEDULER_TIME: Final = "00:00"
|
|
2099
|
+
CLIMATE_RELEVANT_SLOT_TYPES: Final = ("endtime", "temperature")
|
|
2100
|
+
|
|
2101
|
+
|
|
2102
|
+
class ScheduleSlot(TypedDict):
|
|
2103
|
+
"""
|
|
2104
|
+
A single time slot in a climate schedule.
|
|
2105
|
+
|
|
2106
|
+
Each slot defines when a temperature period ends and what temperature to maintain.
|
|
2107
|
+
Climate devices use 13 slots per weekday, with unused slots filled with "24:00".
|
|
2108
|
+
|
|
2109
|
+
Attributes:
|
|
2110
|
+
endtime: End time in "HH:MM" format (e.g., "06:00", "24:00")
|
|
2111
|
+
temperature: Target temperature in degrees Celsius
|
|
2112
|
+
|
|
2113
|
+
Example:
|
|
2114
|
+
{"endtime": "06:00", "temperature": 18.0}
|
|
2115
|
+
|
|
2116
|
+
"""
|
|
2117
|
+
|
|
2118
|
+
endtime: str
|
|
2119
|
+
temperature: float
|
|
2120
|
+
|
|
2121
|
+
|
|
2122
|
+
ClimateWeekdaySchedule = dict[int, ScheduleSlot]
|
|
2123
|
+
"""Schedule slots for a single weekday, keyed by slot number (1-13)."""
|
|
2124
|
+
|
|
2125
|
+
ClimateProfileSchedule = dict[WeekdayStr, ClimateWeekdaySchedule]
|
|
2126
|
+
"""Schedule for all weekdays in a profile."""
|
|
2127
|
+
|
|
2128
|
+
ClimateScheduleDict = dict[ScheduleProfile, ClimateProfileSchedule]
|
|
2129
|
+
"""Complete schedule with all profiles (P1-P6)."""
|
|
2130
|
+
CLIMATE_SCHEDULE_SLOT_IN_RANGE: Final = range(1, 14)
|
|
2131
|
+
CLIMATE_SCHEDULE_SLOT_RANGE: Final = range(1, 13)
|
|
2132
|
+
CLIMATE_SCHEDULE_TIME_RANGE: Final = range(1441)
|
|
2133
|
+
|
|
2134
|
+
|
|
2135
|
+
class SimpleSchedulePeriod(TypedDict):
|
|
2136
|
+
"""
|
|
2137
|
+
A single temperature period in a simple schedule.
|
|
2138
|
+
|
|
2139
|
+
Uses lowercase string keys for JSON serialization compatibility with custom cards.
|
|
2140
|
+
|
|
2141
|
+
Attributes:
|
|
2142
|
+
starttime: Start time in "HH:MM" format (e.g., "06:00")
|
|
2143
|
+
endtime: End time in "HH:MM" format (e.g., "22:00")
|
|
2144
|
+
temperature: Target temperature in degrees Celsius
|
|
2145
|
+
|
|
2146
|
+
"""
|
|
2147
|
+
|
|
2148
|
+
starttime: str
|
|
2149
|
+
endtime: str
|
|
2150
|
+
temperature: float
|
|
2151
|
+
|
|
2152
|
+
|
|
2153
|
+
class SimpleWeekdaySchedule(TypedDict):
|
|
2154
|
+
"""
|
|
2155
|
+
Schedule for a single weekday with base temperature and heating periods.
|
|
2156
|
+
|
|
2157
|
+
Attributes:
|
|
2158
|
+
base_temperature: Default temperature when no period is active
|
|
2159
|
+
periods: List of temperature periods with start/end times
|
|
2160
|
+
|
|
2161
|
+
Example:
|
|
2162
|
+
{
|
|
2163
|
+
"base_temperature": 18.0,
|
|
2164
|
+
"periods": [
|
|
2165
|
+
{"starttime": "06:00", "endtime": "08:00", "temperature": 21.0},
|
|
2166
|
+
{"starttime": "17:00", "endtime": "22:00", "temperature": 21.0}
|
|
2167
|
+
]
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
"""
|
|
2171
|
+
|
|
2172
|
+
base_temperature: float
|
|
2173
|
+
periods: list[SimpleSchedulePeriod]
|
|
2174
|
+
|
|
2175
|
+
|
|
2176
|
+
# Type aliases for higher-level structures
|
|
2177
|
+
SimpleProfileSchedule = dict[WeekdayStr, SimpleWeekdaySchedule]
|
|
2178
|
+
"""Schedule for all weekdays in a profile."""
|
|
2179
|
+
|
|
2180
|
+
SimpleScheduleDict = dict[ScheduleProfile, SimpleProfileSchedule]
|
|
2181
|
+
"""Complete schedule with all profiles."""
|
|
2182
|
+
|
|
2183
|
+
DEFAULT_CLIMATE_FILL_TEMPERATURE: Final = 18.0
|
|
2184
|
+
DEFAULT_SCHEDULE_GROUP = dict[ScheduleField, Any]
|
|
2185
|
+
DEFAULT_SCHEDULE_DICT = dict[int, DEFAULT_SCHEDULE_GROUP]
|
|
2186
|
+
RAW_SCHEDULE_DICT = dict[str, float | int]
|
|
2187
|
+
SCHEDULE_PATTERN: Final = re.compile(r"^\d+_WP_")
|
|
2188
|
+
|
|
2189
|
+
|
|
2190
|
+
# Define public API for this module
|
|
2191
|
+
__all__ = tuple(
|
|
2192
|
+
sorted(
|
|
2193
|
+
name
|
|
2194
|
+
for name, obj in globals().items()
|
|
2195
|
+
if not name.startswith("_")
|
|
2196
|
+
and (
|
|
2197
|
+
name.isupper() # constants like VERSION, patterns, defaults
|
|
2198
|
+
or inspect.isclass(obj) # Enums, dataclasses, TypedDicts, NamedTuple classes
|
|
2199
|
+
or inspect.isfunction(obj) # module functions
|
|
2200
|
+
)
|
|
2201
|
+
and (
|
|
2202
|
+
getattr(obj, "__module__", __name__) == __name__
|
|
2203
|
+
if not isinstance(obj, int | float | str | bytes | tuple | frozenset | dict)
|
|
2204
|
+
else True
|
|
2205
|
+
)
|
|
2206
|
+
)
|
|
2207
|
+
)
|