aiohomematic 2025.10.1__py3-none-any.whl → 2025.10.3__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.

Potentially problematic release.


This version of aiohomematic might be problematic. Click here for more details.

Files changed (57) hide show
  1. aiohomematic/async_support.py +8 -8
  2. aiohomematic/caches/dynamic.py +31 -26
  3. aiohomematic/caches/persistent.py +34 -32
  4. aiohomematic/caches/visibility.py +19 -7
  5. aiohomematic/central/__init__.py +88 -75
  6. aiohomematic/central/decorators.py +2 -2
  7. aiohomematic/central/xml_rpc_server.py +33 -25
  8. aiohomematic/client/__init__.py +72 -56
  9. aiohomematic/client/_rpc_errors.py +3 -3
  10. aiohomematic/client/json_rpc.py +33 -25
  11. aiohomematic/client/xml_rpc.py +14 -9
  12. aiohomematic/const.py +2 -1
  13. aiohomematic/converter.py +19 -19
  14. aiohomematic/exceptions.py +2 -1
  15. aiohomematic/model/__init__.py +4 -3
  16. aiohomematic/model/calculated/__init__.py +1 -1
  17. aiohomematic/model/calculated/climate.py +9 -9
  18. aiohomematic/model/calculated/data_point.py +13 -7
  19. aiohomematic/model/calculated/operating_voltage_level.py +2 -2
  20. aiohomematic/model/calculated/support.py +7 -7
  21. aiohomematic/model/custom/__init__.py +3 -3
  22. aiohomematic/model/custom/climate.py +57 -34
  23. aiohomematic/model/custom/cover.py +32 -18
  24. aiohomematic/model/custom/data_point.py +9 -7
  25. aiohomematic/model/custom/definition.py +23 -17
  26. aiohomematic/model/custom/light.py +52 -23
  27. aiohomematic/model/custom/lock.py +16 -12
  28. aiohomematic/model/custom/siren.py +8 -3
  29. aiohomematic/model/custom/switch.py +3 -2
  30. aiohomematic/model/custom/valve.py +3 -2
  31. aiohomematic/model/data_point.py +63 -49
  32. aiohomematic/model/device.py +48 -42
  33. aiohomematic/model/event.py +6 -5
  34. aiohomematic/model/generic/__init__.py +6 -4
  35. aiohomematic/model/generic/action.py +1 -1
  36. aiohomematic/model/generic/data_point.py +8 -6
  37. aiohomematic/model/generic/number.py +3 -3
  38. aiohomematic/model/generic/select.py +1 -1
  39. aiohomematic/model/generic/sensor.py +2 -2
  40. aiohomematic/model/generic/switch.py +3 -3
  41. aiohomematic/model/hub/__init__.py +17 -16
  42. aiohomematic/model/hub/data_point.py +12 -7
  43. aiohomematic/model/hub/number.py +3 -3
  44. aiohomematic/model/hub/select.py +3 -3
  45. aiohomematic/model/hub/text.py +2 -2
  46. aiohomematic/model/support.py +10 -9
  47. aiohomematic/model/update.py +6 -6
  48. aiohomematic/property_decorators.py +2 -0
  49. aiohomematic/support.py +44 -38
  50. aiohomematic/validator.py +6 -6
  51. {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.3.dist-info}/METADATA +1 -1
  52. aiohomematic-2025.10.3.dist-info/RECORD +78 -0
  53. aiohomematic_support/client_local.py +26 -14
  54. aiohomematic-2025.10.1.dist-info/RECORD +0 -78
  55. {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.3.dist-info}/WHEEL +0 -0
  56. {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.3.dist-info}/licenses/LICENSE +0 -0
  57. {aiohomematic-2025.10.1.dist-info → aiohomematic-2025.10.3.dist-info}/top_level.txt +0 -0
aiohomematic/support.py CHANGED
@@ -54,7 +54,7 @@ from aiohomematic.property_decorators import Kind, get_hm_property_by_kind, get_
54
54
  _LOGGER: Final = logging.getLogger(__name__)
55
55
 
56
56
 
57
- def extract_exc_args(exc: Exception) -> tuple[Any, ...] | Any:
57
+ def extract_exc_args(*, exc: Exception) -> tuple[Any, ...] | Any:
58
58
  """Return the first arg, if there is only one arg."""
59
59
  if exc.args:
60
60
  return exc.args[0] if len(exc.args) == 1 else exc.args
@@ -62,6 +62,7 @@ def extract_exc_args(exc: Exception) -> tuple[Any, ...] | Any:
62
62
 
63
63
 
64
64
  def build_xml_rpc_uri(
65
+ *,
65
66
  host: str,
66
67
  port: int | None,
67
68
  path: str | None,
@@ -80,6 +81,7 @@ def build_xml_rpc_uri(
80
81
 
81
82
 
82
83
  def build_xml_rpc_headers(
84
+ *,
83
85
  username: str,
84
86
  password: str,
85
87
  ) -> list[tuple[str, str]]:
@@ -90,6 +92,7 @@ def build_xml_rpc_headers(
90
92
 
91
93
 
92
94
  def check_config(
95
+ *,
93
96
  central_name: str,
94
97
  host: str,
95
98
  username: str,
@@ -111,10 +114,10 @@ def check_config(
111
114
  config_failures.append("Username must not be empty")
112
115
  if not password:
113
116
  config_failures.append("Password is required")
114
- if not check_password(password):
117
+ if not check_password(password=password):
115
118
  config_failures.append("Password is not valid")
116
119
  try:
117
- check_or_create_directory(storage_folder)
120
+ check_or_create_directory(directory=storage_folder)
118
121
  except BaseHomematicException as bhexc:
119
122
  config_failures.append(extract_exc_args(exc=bhexc)[0])
120
123
  if callback_host and not (is_hostname(hostname=callback_host) or is_ipv4_address(address=callback_host)):
@@ -129,7 +132,7 @@ def check_config(
129
132
  return config_failures
130
133
 
131
134
 
132
- def has_primary_client(interface_configs: AbstractSet[hmcl.InterfaceConfig]) -> bool:
135
+ def has_primary_client(*, interface_configs: AbstractSet[hmcl.InterfaceConfig]) -> bool:
133
136
  """Check if all configured clients exists in central."""
134
137
  for interface_config in interface_configs:
135
138
  if interface_config.interface in PRIMARY_CLIENT_CANDIDATE_INTERFACES:
@@ -137,7 +140,7 @@ def has_primary_client(interface_configs: AbstractSet[hmcl.InterfaceConfig]) ->
137
140
  return False
138
141
 
139
142
 
140
- def delete_file(folder: str, file_name: str) -> None:
143
+ def delete_file(*, folder: str, file_name: str) -> None:
141
144
  """Delete the file."""
142
145
  file_path = os.path.join(folder, file_name)
143
146
  if (
@@ -148,7 +151,7 @@ def delete_file(folder: str, file_name: str) -> None:
148
151
  os.unlink(file_path)
149
152
 
150
153
 
151
- def check_or_create_directory(directory: str) -> bool:
154
+ def check_or_create_directory(*, directory: str) -> bool:
152
155
  """Check / create directory."""
153
156
  if not directory:
154
157
  return False
@@ -162,12 +165,12 @@ def check_or_create_directory(directory: str) -> bool:
162
165
  return True
163
166
 
164
167
 
165
- def parse_sys_var(data_type: SysvarType | None, raw_value: Any) -> Any:
168
+ def parse_sys_var(*, data_type: SysvarType | None, raw_value: Any) -> Any:
166
169
  """Parse system variables to fix type."""
167
170
  if not data_type:
168
171
  return raw_value
169
172
  if data_type in (SysvarType.ALARM, SysvarType.LOGIC):
170
- return to_bool(raw_value)
173
+ return to_bool(value=raw_value)
171
174
  if data_type == SysvarType.FLOAT:
172
175
  return float(raw_value)
173
176
  if data_type in (SysvarType.INTEGER, SysvarType.LIST):
@@ -175,7 +178,7 @@ def parse_sys_var(data_type: SysvarType | None, raw_value: Any) -> Any:
175
178
  return raw_value
176
179
 
177
180
 
178
- def to_bool(value: Any) -> bool:
181
+ def to_bool(*, value: Any) -> bool:
179
182
  """Convert defined string values to bool."""
180
183
  if isinstance(value, bool):
181
184
  return value
@@ -186,7 +189,7 @@ def to_bool(value: Any) -> bool:
186
189
  return value.lower() in ["y", "yes", "t", "true", "on", "1"]
187
190
 
188
191
 
189
- def check_password(password: str | None) -> bool:
192
+ def check_password(*, password: str | None) -> bool:
190
193
  """Check password."""
191
194
  if password is None:
192
195
  return False
@@ -200,7 +203,7 @@ def check_password(password: str | None) -> bool:
200
203
  return True
201
204
 
202
205
 
203
- def regular_to_default_dict_hook(origin: dict) -> defaultdict[Any, Any]:
206
+ def regular_to_default_dict_hook(origin: dict, /) -> defaultdict[Any, Any]:
204
207
  """Use defaultdict in json.loads object_hook."""
205
208
  new_dict: Callable = lambda: defaultdict(new_dict)
206
209
  new_instance = new_dict()
@@ -208,7 +211,7 @@ def regular_to_default_dict_hook(origin: dict) -> defaultdict[Any, Any]:
208
211
  return cast(defaultdict[Any, Any], new_instance)
209
212
 
210
213
 
211
- def _create_tls_context(verify_tls: bool) -> ssl.SSLContext:
214
+ def _create_tls_context(*, verify_tls: bool) -> ssl.SSLContext:
212
215
  """Create tls verified/unverified context."""
213
216
  sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
214
217
  if not verify_tls:
@@ -225,48 +228,48 @@ _DEFAULT_NO_VERIFY_SSL_CONTEXT = _create_tls_context(verify_tls=False)
225
228
  _DEFAULT_SSL_CONTEXT = _create_tls_context(verify_tls=True)
226
229
 
227
230
 
228
- def get_tls_context(verify_tls: bool) -> ssl.SSLContext:
231
+ def get_tls_context(*, verify_tls: bool) -> ssl.SSLContext:
229
232
  """Return tls verified/unverified context."""
230
233
  return _DEFAULT_SSL_CONTEXT if verify_tls else _DEFAULT_NO_VERIFY_SSL_CONTEXT
231
234
 
232
235
 
233
- def get_channel_address(device_address: str, channel_no: int | None) -> str:
236
+ def get_channel_address(*, device_address: str, channel_no: int | None) -> str:
234
237
  """Return the channel address."""
235
238
  return device_address if channel_no is None else f"{device_address}:{channel_no}"
236
239
 
237
240
 
238
- def get_device_address(address: str) -> str:
241
+ def get_device_address(*, address: str) -> str:
239
242
  """Return the device part of an address."""
240
243
  return get_split_channel_address(channel_address=address)[0]
241
244
 
242
245
 
243
- def get_channel_no(address: str) -> int | None:
246
+ def get_channel_no(*, address: str) -> int | None:
244
247
  """Return the channel part of an address."""
245
248
  return get_split_channel_address(channel_address=address)[1]
246
249
 
247
250
 
248
- def is_address(address: str) -> bool:
251
+ def is_address(*, address: str) -> bool:
249
252
  """Check if it is a address."""
250
253
  return is_device_address(address=address) or is_channel_address(address=address)
251
254
 
252
255
 
253
- def is_channel_address(address: str) -> bool:
256
+ def is_channel_address(*, address: str) -> bool:
254
257
  """Check if it is a channel address."""
255
258
  return CHANNEL_ADDRESS_PATTERN.match(address) is not None
256
259
 
257
260
 
258
- def is_device_address(address: str) -> bool:
261
+ def is_device_address(*, address: str) -> bool:
259
262
  """Check if it is a device address."""
260
263
  return DEVICE_ADDRESS_PATTERN.match(address) is not None
261
264
 
262
265
 
263
- def is_paramset_key(paramset_key: ParamsetKey | str) -> bool:
266
+ def is_paramset_key(*, paramset_key: ParamsetKey | str) -> bool:
264
267
  """Check if it is a paramset key."""
265
268
  return isinstance(paramset_key, ParamsetKey) or (isinstance(paramset_key, str) and paramset_key in ParamsetKey)
266
269
 
267
270
 
268
271
  @lru_cache(maxsize=4096)
269
- def get_split_channel_address(channel_address: str) -> tuple[str, int | None]:
272
+ def get_split_channel_address(*, channel_address: str) -> tuple[str, int | None]:
270
273
  """
271
274
  Return the device part of an address.
272
275
 
@@ -281,7 +284,7 @@ def get_split_channel_address(channel_address: str) -> tuple[str, int | None]:
281
284
  return channel_address, None
282
285
 
283
286
 
284
- def changed_within_seconds(last_change: datetime, max_age: int = MAX_CACHE_AGE) -> bool:
287
+ def changed_within_seconds(*, last_change: datetime, max_age: int = MAX_CACHE_AGE) -> bool:
285
288
  """DataPoint has been modified within X minutes."""
286
289
  if last_change == INIT_DATETIME:
287
290
  return False
@@ -297,7 +300,7 @@ def find_free_port() -> int:
297
300
  return int(sock.getsockname()[1])
298
301
 
299
302
 
300
- def get_ip_addr(host: str, port: int) -> str | None:
303
+ def get_ip_addr(host: str, port: int, /) -> str | None:
301
304
  """Get local_ip from socket."""
302
305
  try:
303
306
  socket.gethostbyname(host)
@@ -314,7 +317,7 @@ def get_ip_addr(host: str, port: int) -> str | None:
314
317
  return local_ip
315
318
 
316
319
 
317
- def is_hostname(hostname: str | None) -> bool:
320
+ def is_hostname(*, hostname: str | None) -> bool:
318
321
  """Return True if hostname is valid."""
319
322
  if not hostname:
320
323
  return False
@@ -333,7 +336,7 @@ def is_hostname(hostname: str | None) -> bool:
333
336
  return all(ALLOWED_HOSTNAME_PATTERN.match(label) for label in labels)
334
337
 
335
338
 
336
- def is_ipv4_address(address: str | None) -> bool:
339
+ def is_ipv4_address(*, address: str | None) -> bool:
337
340
  """Return True if ipv4_address is valid."""
338
341
  if not address:
339
342
  return False
@@ -344,12 +347,13 @@ def is_ipv4_address(address: str | None) -> bool:
344
347
  return True
345
348
 
346
349
 
347
- def is_port(port: int) -> bool:
350
+ def is_port(*, port: int) -> bool:
348
351
  """Return True if port is valid."""
349
352
  return 0 <= port <= 65535
350
353
 
351
354
 
352
355
  def element_matches_key(
356
+ *,
353
357
  search_elements: str | Collection[str],
354
358
  compare_with: str | None,
355
359
  search_key: str | None = None,
@@ -401,7 +405,7 @@ def element_matches_key(
401
405
  return False
402
406
 
403
407
 
404
- def _get_search_key(search_elements: Collection[str], search_key: str) -> str | None:
408
+ def _get_search_key(*, search_elements: Collection[str], search_key: str) -> str | None:
405
409
  """Search for a matching key in a collection."""
406
410
  for element in search_elements:
407
411
  if search_key.startswith(element):
@@ -446,7 +450,7 @@ def debug_enabled() -> bool:
446
450
  return False
447
451
 
448
452
 
449
- def hash_sha256(value: Any) -> str:
453
+ def hash_sha256(*, value: Any) -> str:
450
454
  """
451
455
  Hash a value with sha256.
452
456
 
@@ -459,26 +463,26 @@ def hash_sha256(value: Any) -> str:
459
463
  data = orjson.dumps(value, option=orjson.OPT_SORT_KEYS | orjson.OPT_NON_STR_KEYS)
460
464
  except Exception:
461
465
  # Fallback: convert to a hashable representation and use repr()
462
- data = repr(_make_value_hashable(value)).encode()
466
+ data = repr(_make_value_hashable(value=value)).encode()
463
467
  hasher.update(data)
464
468
  return base64.b64encode(hasher.digest()).decode()
465
469
 
466
470
 
467
- def _make_value_hashable(value: Any) -> Any:
471
+ def _make_value_hashable(*, value: Any) -> Any:
468
472
  """Make a hashable object."""
469
473
  if isinstance(value, tuple | list):
470
- return tuple(_make_value_hashable(e) for e in value)
474
+ return tuple(_make_value_hashable(value=e) for e in value)
471
475
 
472
476
  if isinstance(value, dict):
473
- return tuple(sorted((k, _make_value_hashable(v)) for k, v in value.items()))
477
+ return tuple(sorted((k, _make_value_hashable(value=v)) for k, v in value.items()))
474
478
 
475
479
  if isinstance(value, set | frozenset):
476
- return tuple(sorted(_make_value_hashable(e) for e in value))
480
+ return tuple(sorted(_make_value_hashable(value=e) for e in value))
477
481
 
478
482
  return value
479
483
 
480
484
 
481
- def get_rx_modes(mode: int) -> tuple[RxMode, ...]:
485
+ def get_rx_modes(*, mode: int) -> tuple[RxMode, ...]:
482
486
  """Convert int to rx modes."""
483
487
  rx_modes: set[RxMode] = set()
484
488
  if mode & RxMode.LAZY_CONFIG:
@@ -498,14 +502,14 @@ def get_rx_modes(mode: int) -> tuple[RxMode, ...]:
498
502
  return tuple(rx_modes)
499
503
 
500
504
 
501
- def supports_rx_mode(command_rx_mode: CommandRxMode, rx_modes: tuple[RxMode, ...]) -> bool:
505
+ def supports_rx_mode(*, command_rx_mode: CommandRxMode, rx_modes: tuple[RxMode, ...]) -> bool:
502
506
  """Check if rx mode is supported."""
503
507
  return (command_rx_mode == CommandRxMode.BURST and RxMode.BURST in rx_modes) or (
504
508
  command_rx_mode == CommandRxMode.WAKEUP and RxMode.WAKEUP in rx_modes
505
509
  )
506
510
 
507
511
 
508
- def cleanup_text_from_html_tags(text: str) -> str:
512
+ def cleanup_text_from_html_tags(*, text: str) -> str:
509
513
  """Cleanup text from html tags."""
510
514
  return re.sub(HTMLTAG_PATTERN, "", text)
511
515
 
@@ -515,7 +519,7 @@ def cleanup_text_from_html_tags(text: str) -> str:
515
519
  _BOUNDARY_MSG = "error_boundary"
516
520
 
517
521
 
518
- def _safe_log_context(context: Mapping[str, Any] | None) -> dict[str, Any]:
522
+ def _safe_log_context(*, context: Mapping[str, Any] | None) -> dict[str, Any]:
519
523
  """Extract safe context from a mapping."""
520
524
  ctx: dict[str, Any] = {}
521
525
  if not context:
@@ -565,7 +569,9 @@ def log_boundary_error(
565
569
  log_message += f" {message}"
566
570
 
567
571
  if log_context:
568
- log_message += f" ctx={orjson.dumps(_safe_log_context(log_context), option=orjson.OPT_SORT_KEYS).decode()}"
572
+ log_message += (
573
+ f" ctx={orjson.dumps(_safe_log_context(context=log_context), option=orjson.OPT_SORT_KEYS).decode()}"
574
+ )
569
575
 
570
576
  # Choose level if not provided:
571
577
  if (chosen_level := level) is None:
aiohomematic/validator.py CHANGED
@@ -28,42 +28,42 @@ positive_int = vol.All(vol.Coerce(int), vol.Range(min=0))
28
28
  wait_for = vol.All(vol.Coerce(int), vol.Range(min=1, max=MAX_WAIT_FOR_CALLBACK))
29
29
 
30
30
 
31
- def channel_address(value: str) -> str:
31
+ def channel_address(value: str, /) -> str:
32
32
  """Validate channel_address."""
33
33
  if is_channel_address(address=value):
34
34
  return value
35
35
  raise vol.Invalid("channel_address is invalid")
36
36
 
37
37
 
38
- def device_address(value: str) -> str:
38
+ def device_address(value: str, /) -> str:
39
39
  """Validate channel_address."""
40
40
  if is_device_address(address=value):
41
41
  return value
42
42
  raise vol.Invalid("device_address is invalid")
43
43
 
44
44
 
45
- def hostname(value: str) -> str:
45
+ def hostname(value: str, /) -> str:
46
46
  """Validate hostname."""
47
47
  if is_hostname(hostname=value):
48
48
  return value
49
49
  raise vol.Invalid("hostname is invalid")
50
50
 
51
51
 
52
- def ipv4_address(value: str) -> str:
52
+ def ipv4_address(value: str, /) -> str:
53
53
  """Validate ipv4_address."""
54
54
  if is_ipv4_address(address=value):
55
55
  return value
56
56
  raise vol.Invalid("ipv4_address is invalid")
57
57
 
58
58
 
59
- def password(value: str) -> str:
59
+ def password(value: str, /) -> str:
60
60
  """Validate password."""
61
61
  if check_password(password=value):
62
62
  return value
63
63
  raise vol.Invalid("password is invalid")
64
64
 
65
65
 
66
- def paramset_key(value: str) -> str:
66
+ def paramset_key(value: str, /) -> str:
67
67
  """Validate paramset_key."""
68
68
  if is_paramset_key(paramset_key=value):
69
69
  return value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiohomematic
3
- Version: 2025.10.1
3
+ Version: 2025.10.3
4
4
  Summary: Homematic interface for Home Assistant running on Python 3.
5
5
  Home-page: https://github.com/sukramj/aiohomematic
6
6
  Author-email: SukramJ <sukramj@icloud.com>, Daniel Perna <danielperna84@gmail.com>
@@ -0,0 +1,78 @@
1
+ aiohomematic/__init__.py,sha256=ngULK_anZQwwUUCVcberBdVjguYfboiuG9VoueKy9fA,2283
2
+ aiohomematic/async_support.py,sha256=EEzOwI3LAWjl16Q2QgtTLS7P9ktodBvs8Cw3ZKsjyxw,6153
3
+ aiohomematic/const.py,sha256=mJmfDFhm3NFQP4qkRlb63d89qi-pDidoHUq0RYYOM6I,25565
4
+ aiohomematic/context.py,sha256=M7gkA7KFT0dp35gzGz2dzKVXu1PP0sAnepgLlmjyRS4,451
5
+ aiohomematic/converter.py,sha256=gaNHe-WEiBStZMuuRz9iGn3Mo_CGz1bjgLtlYBJJAko,3624
6
+ aiohomematic/decorators.py,sha256=oHEFSJoJVd-h9RYb6NLTJ60erkpRHPYv7EEipKn1a9Q,10636
7
+ aiohomematic/exceptions.py,sha256=8Uu3rADawhYlAz6y4J52aJ-wKok8Z7YbUYUwWeGMKhs,5028
8
+ aiohomematic/hmcli.py,sha256=qNstNDX6q8t3mJFCGlXlmRVobGabntrPtFi3kchf1Eg,4933
9
+ aiohomematic/property_decorators.py,sha256=tAsmcSDnLsUiWlD1KUEe5fvYoIGpk5syYB0DrGuN4QU,16231
10
+ aiohomematic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ aiohomematic/support.py,sha256=w05YlO1IrelF2a1zKkUGWyr5E-nDJ-knh0LQqOHpR34,20514
12
+ aiohomematic/validator.py,sha256=HUikmo-SFksehFBAdZmBv4ajy0XkjgvXvcCfbexnzZo,3563
13
+ aiohomematic/caches/__init__.py,sha256=_gI30tbsWgPRaHvP6cRxOQr6n9bYZzU-jp1WbHhWg-A,470
14
+ aiohomematic/caches/dynamic.py,sha256=vyQsjBc2uGoNCCrWPMGmNBlUQ9GYvgVuupA9bhqH5hI,21466
15
+ aiohomematic/caches/persistent.py,sha256=2bgASru4zbrFEd8rOn2YcReeHPiPJE8hMgMUPAbGh68,20117
16
+ aiohomematic/caches/visibility.py,sha256=SThfEO3LKbIIERD4Novyj4ZZUkcYrBuvYdNQfPO29JQ,31676
17
+ aiohomematic/central/__init__.py,sha256=nXbA9IYHSn-Gm59U_T9gqMeKwQxmliv3jdrkXnMtpXs,86940
18
+ aiohomematic/central/decorators.py,sha256=v0gCa5QEQPNEvGy0O2YIOhoJy7OKiJZJWtK64OQm7y4,6918
19
+ aiohomematic/central/xml_rpc_server.py,sha256=1Vr8BXakLSo-RC967IlRI9LPUCl4iXp2y-AXSZuDyPc,10777
20
+ aiohomematic/client/__init__.py,sha256=XWHg8VTtiahu7jfHwq4yOVXOxUy8nw80JF9uyY7QNJI,70457
21
+ aiohomematic/client/_rpc_errors.py,sha256=-NPtGvkQPJ4V2clDxv1tKy09M9JZm61pUCeki9DDh6s,2984
22
+ aiohomematic/client/json_rpc.py,sha256=k5euUJkDzIQXdOX5JJxkua_7AUUTFspK8rTGnVKoCf8,49568
23
+ aiohomematic/client/xml_rpc.py,sha256=9PEOWoTG0EMUAMyqiInF4iA_AduHv_hhjJW3YhYjYqU,9614
24
+ aiohomematic/model/__init__.py,sha256=KO7gas_eEzm67tODKqWTs0617CSGeKKjOWOlDbhRo_Q,5458
25
+ aiohomematic/model/data_point.py,sha256=Ml8AOQ1RcRezTYWiGBlIXwcTLolQMX5Cyb-O7GtNDm4,41586
26
+ aiohomematic/model/device.py,sha256=15z5G2X3jSJaj-yz7jX_tnirzipRIGBJPymObY3Dmjk,52942
27
+ aiohomematic/model/event.py,sha256=82H8M_QNMCCC29mP3R16alJyKWS3Hb3aqY_aFMSqCvo,6874
28
+ aiohomematic/model/support.py,sha256=l5E9Oon20nkGWOSEmbYtqQbpbh6-H4rIk8xtEtk5Fcg,19657
29
+ aiohomematic/model/update.py,sha256=5F39xNz9B2GKJ8TvJHPMC-Wu97HfkiiMawjnHEYMnoA,5156
30
+ aiohomematic/model/calculated/__init__.py,sha256=UGLePgKDH8JpLqjhPBgvBzjggI34omcaCPsc6tcM8Xs,2811
31
+ aiohomematic/model/calculated/climate.py,sha256=GXBsC5tnrC_BvnFBkJ9KUqE7uVcGD1KTU_6-OleF5H4,8545
32
+ aiohomematic/model/calculated/data_point.py,sha256=oTN8y3B9weh7CX3ZFiDyZFgvX77iUwge-acg49pd1sI,11609
33
+ aiohomematic/model/calculated/operating_voltage_level.py,sha256=ZrOPdNoWQ5QLr4yzMRsoPG3UuJKRkBUHfchIrpKZU4o,13527
34
+ aiohomematic/model/calculated/support.py,sha256=vOxTvWe8SBCwJpLzcVA8ibtfw4eP8yTUZj4Jt9hWt9k,6695
35
+ aiohomematic/model/custom/__init__.py,sha256=UzczqjsUqWvS9ZaqKeb6elbjb2y5W3cgFPB0YQUHaeM,6095
36
+ aiohomematic/model/custom/climate.py,sha256=zSLQUY_tU7tDlbM-vW15BGuyWRjcR_DyqOwSg1_Vmfw,57217
37
+ aiohomematic/model/custom/const.py,sha256=Kh1pnab6nmwbaY43CfXQy3yrWpPwsrQdl1Ea2aZ6aw0,4961
38
+ aiohomematic/model/custom/cover.py,sha256=hlIeQD0cZpq7X222J7ygm6kD4AE6h5IN-wcqzvZCLFA,29057
39
+ aiohomematic/model/custom/data_point.py,sha256=l2pTz7Fu5jGCstXHK1cWCFfBWIJeKmtt37qdGLmrQhA,14155
40
+ aiohomematic/model/custom/definition.py,sha256=9kSdqVOHQs65Q2Op5QknNQv5lLmZkZlGCUUCRGicOaw,35662
41
+ aiohomematic/model/custom/light.py,sha256=2UxQOoupwTpQ-5iwY51gL_B815sgDXNW-HG-QhAFb9E,44448
42
+ aiohomematic/model/custom/lock.py,sha256=ndzZ0hp7FBohw7T_qR0jPobwlcwxus9M1DuDu_7vfPw,11996
43
+ aiohomematic/model/custom/siren.py,sha256=DT8RoOCl7FqstgRSBK-RWRcY4T29LuEdnlhaWCB6ATk,9785
44
+ aiohomematic/model/custom/support.py,sha256=UvencsvCwgpm4iqRNRt5KRs560tyw1NhYP5ZaqmCT2k,1453
45
+ aiohomematic/model/custom/switch.py,sha256=VKknWPJOtSwIzV-Ag_8Zg1evtkyjKh768Be_quU_R54,6885
46
+ aiohomematic/model/custom/valve.py,sha256=u9RYzeJ8FNmpFO6amlLElXTQdAeqac5yo7NbZYS6Z9U,4242
47
+ aiohomematic/model/generic/__init__.py,sha256=-ho8m9gFlORBGNPn2i8c9i5-GVLLFvTlf5FFpaTJbFw,7675
48
+ aiohomematic/model/generic/action.py,sha256=niJPvTs43b9GiKomdBaBKwjOwtmNxR_YRhj5Fpje9NU,997
49
+ aiohomematic/model/generic/binary_sensor.py,sha256=U5GvfRYbhwe0jRmaedD4LVZ_24SyyPbVr74HEfZXoxE,887
50
+ aiohomematic/model/generic/button.py,sha256=6jZ49woI9gYJEx__PjguDNbc5vdE1P-YcLMZZFkYRCg,740
51
+ aiohomematic/model/generic/data_point.py,sha256=2NvdU802JUo4NZh0v6oMI-pVtlNluSFse7ISMGqo70g,6084
52
+ aiohomematic/model/generic/number.py,sha256=nJgOkMZwNfPtzBrX2o5RAjBt-o8KrKuqtDa9LBj0Jw0,2678
53
+ aiohomematic/model/generic/select.py,sha256=vWfLUdQBjZLG-q-WZMxHk9Klawg_iNOEeSoVHrvG35I,1538
54
+ aiohomematic/model/generic/sensor.py,sha256=wCnQ8IoC8uPTN29R250pfJa4x6y9sh4c3vxQ4Km8Clg,2262
55
+ aiohomematic/model/generic/switch.py,sha256=VIMwIVok9kSRoSb-s5saYRHeiZcNWH4J5FyMSxUAbpw,1842
56
+ aiohomematic/model/generic/text.py,sha256=vtNV7YxZuxF6LzNRKRAeOtSQtPQxPaEd560OFaVR13U,854
57
+ aiohomematic/model/hub/__init__.py,sha256=g2m-5rba6SNCfGrlxwqYa8mlP5-N2obFAvyHJV8i4FY,13525
58
+ aiohomematic/model/hub/binary_sensor.py,sha256=Z4o-zghHSc83ZHUUCtHqWEGueD9K1Fe0JEt_xJNdx_Y,752
59
+ aiohomematic/model/hub/button.py,sha256=XMnoImnz5vDybxfrP4GWDp2M5gEMG71d8ba1YzVYAnE,890
60
+ aiohomematic/model/hub/data_point.py,sha256=E6qn1gVhZ4meti-Tpd03f-YyfKnkUh-FpLbwBaa-d1c,10653
61
+ aiohomematic/model/hub/number.py,sha256=12BK6mBOJn4aP7DWuWvMYfiaLmDX7ejd5tDCoHa2bUk,1237
62
+ aiohomematic/model/hub/select.py,sha256=C9ke_8U_pzI0fctIeOZHwq-fvWj5s64jW6-DcZT-SrU,1674
63
+ aiohomematic/model/hub/sensor.py,sha256=cIr-7bsbOLBxnH33EHQ6D42vc61abO4QYLWLzkm1T10,1192
64
+ aiohomematic/model/hub/switch.py,sha256=510Vrlyak5TRsMvURrGYMkad-CbGUKWh20jjHQGOios,1390
65
+ aiohomematic/model/hub/text.py,sha256=JOy-hfDSu95NbxP5JeGgga9NGrR8JLNmz_h4k8J1GXU,999
66
+ aiohomematic/rega_scripts/fetch_all_device_data.fn,sha256=7uxhHoelAOsH6yYr1n1M1XwIRgDmItiHnWIMhDYEimk,4373
67
+ aiohomematic/rega_scripts/get_program_descriptions.fn,sha256=pGmj377MkqbZi6j-UBKQAsXTphwh1kDwDKqXij8zUBE,835
68
+ aiohomematic/rega_scripts/get_serial.fn,sha256=t1oeo-sB_EuVeiY24PLcxFSkdQVgEWGXzpemJQZFybY,1079
69
+ aiohomematic/rega_scripts/get_system_variable_descriptions.fn,sha256=UKXvC0_5lSApdQ2atJc0E5Stj5Zt3lqh0EcliokYu2c,849
70
+ aiohomematic/rega_scripts/set_program_state.fn,sha256=0bnv7lUj8FMjDZBz325tDVP61m04cHjVj4kIOnUUgpY,279
71
+ aiohomematic/rega_scripts/set_system_variable.fn,sha256=sTmr7vkPTPnPkor5cnLKlDvfsYRbGO1iq2z_2pMXq5E,383
72
+ aiohomematic-2025.10.3.dist-info/licenses/LICENSE,sha256=q-B0xpREuZuvKsmk3_iyVZqvZ-vJcWmzMZpeAd0RqtQ,1083
73
+ aiohomematic_support/__init__.py,sha256=_0YtF4lTdC_k6-zrM2IefI0u0LMr_WA61gXAyeGLgbY,66
74
+ aiohomematic_support/client_local.py,sha256=CGZ6N_JCQ12_u2h56M_WCZ5SbcOK4xBsPYjLH2eyqlo,12820
75
+ aiohomematic-2025.10.3.dist-info/METADATA,sha256=npEp8wqV-PrVJzhabx_yKlOicBz3Q7pnqrLf1nqqlLg,7603
76
+ aiohomematic-2025.10.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
77
+ aiohomematic-2025.10.3.dist-info/top_level.txt,sha256=5TDRlUWQPThIUwQjOj--aUo4UA-ow4m0sNhnoCBi5n8,34
78
+ aiohomematic-2025.10.3.dist-info/RECORD,,
@@ -41,7 +41,7 @@ BACKEND_LOCAL: Final = "Local CCU"
41
41
  class ClientLocal(Client): # pragma: no cover
42
42
  """Local client object to provide access to locally stored files."""
43
43
 
44
- def __init__(self, client_config: _ClientConfig, local_resources: LocalRessources) -> None:
44
+ def __init__(self, *, client_config: _ClientConfig, local_resources: LocalRessources) -> None:
45
45
  """Initialize the Client."""
46
46
  super().__init__(client_config=client_config)
47
47
  self._local_resources = local_resources
@@ -63,7 +63,7 @@ class ClientLocal(Client): # pragma: no cover
63
63
  """Return the model of the backend."""
64
64
  return BACKEND_LOCAL
65
65
 
66
- def get_product_group(self, model: str) -> ProductGroup:
66
+ def get_product_group(self, *, model: str) -> ProductGroup:
67
67
  """Return the product group."""
68
68
  l_model = model.lower()
69
69
  if l_model.startswith("hmipw"):
@@ -120,46 +120,46 @@ class ClientLocal(Client): # pragma: no cover
120
120
  return True
121
121
 
122
122
  @inspector(re_raise=False, no_raise_return=False)
123
- async def check_connection_availability(self, handle_ping_pong: bool) -> bool:
123
+ async def check_connection_availability(self, *, handle_ping_pong: bool) -> bool:
124
124
  """Send ping to the backend to generate PONG event."""
125
125
  if handle_ping_pong and self.supports_ping_pong:
126
126
  self._ping_pong_cache.handle_send_ping(ping_ts=datetime.now())
127
127
  return True
128
128
 
129
129
  @inspector
130
- async def execute_program(self, pid: str) -> bool:
130
+ async def execute_program(self, *, pid: str) -> bool:
131
131
  """Execute a program on the backend."""
132
132
  return True
133
133
 
134
134
  @inspector
135
- async def set_program_state(self, pid: str, state: bool) -> bool:
135
+ async def set_program_state(self, *, pid: str, state: bool) -> bool:
136
136
  """Set the program state on the backend."""
137
137
  return True
138
138
 
139
139
  @inspector(measure_performance=True)
140
- async def set_system_variable(self, legacy_name: str, value: Any) -> bool:
140
+ async def set_system_variable(self, *, legacy_name: str, value: Any) -> bool:
141
141
  """Set a system variable on the backend."""
142
142
  return True
143
143
 
144
144
  @inspector
145
- async def delete_system_variable(self, name: str) -> bool:
145
+ async def delete_system_variable(self, *, name: str) -> bool:
146
146
  """Delete a system variable from the backend."""
147
147
  return True
148
148
 
149
149
  @inspector
150
- async def get_system_variable(self, name: str) -> str:
150
+ async def get_system_variable(self, *, name: str) -> str:
151
151
  """Get single system variable from the backend."""
152
152
  return "Empty"
153
153
 
154
154
  @inspector(re_raise=False)
155
155
  async def get_all_system_variables(
156
- self, markers: tuple[DescriptionMarker | str, ...]
156
+ self, *, markers: tuple[DescriptionMarker | str, ...]
157
157
  ) -> tuple[SystemVariableData, ...]:
158
158
  """Get all system variables from the backend."""
159
159
  return ()
160
160
 
161
161
  @inspector(re_raise=False)
162
- async def get_all_programs(self, markers: tuple[DescriptionMarker | str, ...]) -> tuple[ProgramData, ...]:
162
+ async def get_all_programs(self, *, markers: tuple[DescriptionMarker | str, ...]) -> tuple[ProgramData, ...]:
163
163
  """Get all programs, if available."""
164
164
  return ()
165
165
 
@@ -203,6 +203,7 @@ class ClientLocal(Client): # pragma: no cover
203
203
  @inspector(log_level=logging.NOTSET)
204
204
  async def get_value(
205
205
  self,
206
+ *,
206
207
  channel_address: str,
207
208
  paramset_key: ParamsetKey,
208
209
  parameter: str,
@@ -214,6 +215,7 @@ class ClientLocal(Client): # pragma: no cover
214
215
  @inspector(re_raise=False, no_raise_return=set())
215
216
  async def set_value(
216
217
  self,
218
+ *,
217
219
  channel_address: str,
218
220
  paramset_key: ParamsetKey,
219
221
  parameter: str,
@@ -228,12 +230,15 @@ class ClientLocal(Client): # pragma: no cover
228
230
  channel_address=channel_address, parameter=parameter, value=value
229
231
  )
230
232
  # fire an event to fake the state change for a simple parameter
231
- await self.central.data_point_event(self.interface_id, channel_address, parameter, value)
233
+ await self.central.data_point_event(
234
+ interface_id=self.interface_id, channel_address=channel_address, parameter=parameter, value=value
235
+ )
232
236
  return result
233
237
 
234
238
  @inspector
235
239
  async def get_paramset(
236
240
  self,
241
+ *,
237
242
  address: str,
238
243
  paramset_key: ParamsetKey | str,
239
244
  call_source: CallSource = CallSource.MANUAL_OR_SCHEDULED,
@@ -247,7 +252,7 @@ class ClientLocal(Client): # pragma: no cover
247
252
  return {}
248
253
 
249
254
  async def _get_paramset_description(
250
- self, address: str, paramset_key: ParamsetKey
255
+ self, *, address: str, paramset_key: ParamsetKey
251
256
  ) -> dict[str, ParameterData] | None:
252
257
  """Get paramset description from the backend."""
253
258
  if not self._local_resources:
@@ -275,6 +280,7 @@ class ClientLocal(Client): # pragma: no cover
275
280
  @inspector(measure_performance=True)
276
281
  async def put_paramset(
277
282
  self,
283
+ *,
278
284
  channel_address: str,
279
285
  paramset_key_or_link_address: ParamsetKey | str,
280
286
  values: Any,
@@ -300,11 +306,17 @@ class ClientLocal(Client): # pragma: no cover
300
306
 
301
307
  # fire an event to fake the state change for the content of a paramset
302
308
  for parameter in values:
303
- await self.central.data_point_event(self.interface_id, channel_address, parameter, values[parameter])
309
+ await self.central.data_point_event(
310
+ interface_id=self.interface_id,
311
+ channel_address=channel_address,
312
+ parameter=parameter,
313
+ value=values[parameter],
314
+ )
304
315
  return result
305
316
 
306
317
  async def _load_all_json_files(
307
318
  self,
319
+ *,
308
320
  anchor: str,
309
321
  resource: str,
310
322
  include_list: list[str] | None = None,
@@ -324,7 +336,7 @@ class ClientLocal(Client): # pragma: no cover
324
336
  result.append(file_content)
325
337
  return result
326
338
 
327
- async def _load_json_file(self, anchor: str, resource: str, filename: str) -> Any | None:
339
+ async def _load_json_file(self, *, anchor: str, resource: str, filename: str) -> Any | None:
328
340
  """Load json file from disk into dict."""
329
341
  package_path = str(importlib.resources.files(anchor))
330
342