aiohomematic 2025.10.18__tar.gz → 2025.10.19__tar.gz

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 (82) hide show
  1. {aiohomematic-2025.10.18/aiohomematic.egg-info → aiohomematic-2025.10.19}/PKG-INFO +1 -1
  2. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/async_support.py +26 -9
  3. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/client/__init__.py +23 -20
  4. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/client/json_rpc.py +16 -16
  5. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/const.py +3 -2
  6. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/device.py +1 -1
  7. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19/aiohomematic.egg-info}/PKG-INFO +1 -1
  8. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/LICENSE +0 -0
  9. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/MANIFEST.in +0 -0
  10. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/README.md +0 -0
  11. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/__init__.py +0 -0
  12. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/central/__init__.py +0 -0
  13. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/central/decorators.py +0 -0
  14. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/central/rpc_server.py +0 -0
  15. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/client/_rpc_errors.py +0 -0
  16. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/client/rpc_proxy.py +0 -0
  17. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/context.py +0 -0
  18. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/converter.py +0 -0
  19. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/decorators.py +0 -0
  20. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/exceptions.py +0 -0
  21. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/hmcli.py +0 -0
  22. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/__init__.py +0 -0
  23. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/calculated/__init__.py +0 -0
  24. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/calculated/climate.py +0 -0
  25. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/calculated/data_point.py +0 -0
  26. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/calculated/operating_voltage_level.py +0 -0
  27. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/calculated/support.py +0 -0
  28. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/custom/__init__.py +0 -0
  29. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/custom/climate.py +0 -0
  30. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/custom/const.py +0 -0
  31. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/custom/cover.py +0 -0
  32. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/custom/data_point.py +0 -0
  33. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/custom/definition.py +0 -0
  34. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/custom/light.py +0 -0
  35. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/custom/lock.py +0 -0
  36. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/custom/siren.py +0 -0
  37. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/custom/support.py +0 -0
  38. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/custom/switch.py +0 -0
  39. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/custom/valve.py +0 -0
  40. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/data_point.py +0 -0
  41. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/event.py +0 -0
  42. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/generic/__init__.py +0 -0
  43. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/generic/action.py +0 -0
  44. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/generic/binary_sensor.py +0 -0
  45. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/generic/button.py +0 -0
  46. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/generic/data_point.py +0 -0
  47. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/generic/number.py +0 -0
  48. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/generic/select.py +0 -0
  49. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/generic/sensor.py +0 -0
  50. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/generic/switch.py +0 -0
  51. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/generic/text.py +0 -0
  52. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/hub/__init__.py +0 -0
  53. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/hub/binary_sensor.py +0 -0
  54. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/hub/button.py +0 -0
  55. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/hub/data_point.py +0 -0
  56. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/hub/number.py +0 -0
  57. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/hub/select.py +0 -0
  58. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/hub/sensor.py +0 -0
  59. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/hub/switch.py +0 -0
  60. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/hub/text.py +0 -0
  61. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/support.py +0 -0
  62. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/model/update.py +0 -0
  63. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/property_decorators.py +0 -0
  64. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/py.typed +0 -0
  65. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/rega_scripts/fetch_all_device_data.fn +0 -0
  66. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/rega_scripts/get_program_descriptions.fn +0 -0
  67. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/rega_scripts/get_serial.fn +0 -0
  68. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/rega_scripts/get_system_variable_descriptions.fn +0 -0
  69. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/rega_scripts/set_program_state.fn +0 -0
  70. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/rega_scripts/set_system_variable.fn +0 -0
  71. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/store/__init__.py +0 -0
  72. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/store/dynamic.py +0 -0
  73. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/store/persistent.py +0 -0
  74. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/store/visibility.py +0 -0
  75. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/support.py +0 -0
  76. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic/validator.py +0 -0
  77. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic.egg-info/SOURCES.txt +0 -0
  78. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic.egg-info/dependency_links.txt +0 -0
  79. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic.egg-info/requires.txt +0 -0
  80. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/aiohomematic.egg-info/top_level.txt +0 -0
  81. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/pyproject.toml +0 -0
  82. {aiohomematic-2025.10.18 → aiohomematic-2025.10.19}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiohomematic
3
- Version: 2025.10.18
3
+ Version: 2025.10.19
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>
@@ -8,9 +8,11 @@ import asyncio
8
8
  from collections.abc import Callable, Collection, Coroutine
9
9
  from concurrent.futures import ThreadPoolExecutor
10
10
  from concurrent.futures._base import CancelledError
11
+ import contextlib
11
12
  from functools import wraps
12
13
  import logging
13
14
  from time import monotonic
15
+ from types import CoroutineType
14
16
  from typing import Any, Final, cast
15
17
 
16
18
  from aiohomematic.const import BLOCK_LOG_TIMEOUT
@@ -100,20 +102,35 @@ class Looper:
100
102
  return pending_set
101
103
  return set()
102
104
 
103
- def create_task(self, *, target: Coroutine[Any, Any, Any], name: str) -> None:
104
- """Add task to the executor pool."""
105
+ def create_task(
106
+ self, *, target: Coroutine[Any, Any, Any] | Callable[[], Coroutine[Any, Any, Any]], name: str
107
+ ) -> None:
108
+ """
109
+ Schedule a coroutine to run in the loop.
110
+
111
+ Accepts either an already-created coroutine object or a zero-argument
112
+ callable that returns a coroutine. The callable form defers coroutine
113
+ creation until inside the event loop, which avoids "was never awaited"
114
+ warnings if callers only inspect the parameters (e.g. in tests).
115
+ """
105
116
  try:
106
117
  self._loop.call_soon_threadsafe(self._async_create_task, target, name)
107
118
  except CancelledError:
108
- _LOGGER.debug(
109
- "create_task: task cancelled for %s",
110
- name,
111
- )
119
+ # Scheduling failed; if a coroutine object was provided, close it to
120
+ # avoid 'was never awaited' warnings.
121
+ if asyncio.iscoroutine(target):
122
+ with contextlib.suppress(Exception):
123
+ cast(CoroutineType, target).close()
124
+ _LOGGER.debug("create_task: task cancelled for %s", name)
112
125
  return
113
126
 
114
- def _async_create_task[R](self, target: Coroutine[Any, Any, R], name: str) -> asyncio.Task[R]: # kwonly: disable
115
- """Create a task from within the event_loop. This method must be run in the event_loop."""
116
- task = self._loop.create_task(target, name=name)
127
+ def _async_create_task[R]( # kwonly: disable
128
+ self, target: Coroutine[Any, Any, R] | Callable[[], Coroutine[Any, Any, R]], name: str
129
+ ) -> asyncio.Task[R]:
130
+ """Create a task from within the event loop. Must be run in the event loop."""
131
+ # If target is a callable, call it here to create the coroutine inside the loop
132
+ coro: Coroutine[Any, Any, R] = target if asyncio.iscoroutine(target) else target()
133
+ task = self._loop.create_task(coro, name=name)
117
134
  self._tasks.add(task)
118
135
  task.add_done_callback(self._tasks.remove)
119
136
  return task
@@ -58,7 +58,8 @@ from aiohomematic import central as hmcu
58
58
  from aiohomematic.client.json_rpc import AioJsonRpcAioHttpClient
59
59
  from aiohomematic.client.rpc_proxy import AioXmlRpcProxy, BaseRpcProxy
60
60
  from aiohomematic.const import (
61
- CALLBACK_WARN_INTERVAL,
61
+ CALLBACK_WARN_ARM_INTERVAL,
62
+ CALLBACK_WARN_DISARM_INTERVAL,
62
63
  DATETIME_FORMAT_MILLIS,
63
64
  DEFAULT_MAX_WORKERS,
64
65
  DP_KEY_VALUE,
@@ -145,7 +146,6 @@ class Client(ABC, LogContextMixin):
145
146
  self._last_value_send_cache = CommandCache(interface_id=client_config.interface_id)
146
147
  self._available: bool = True
147
148
  self._connection_error_count: int = 0
148
- self._is_callback_alive: bool = True
149
149
  self._is_initialized: bool = False
150
150
  self._ping_pong_cache: Final = PingPongCache(
151
151
  central=client_config.central, interface_id=client_config.interface_id
@@ -395,7 +395,7 @@ class Client(ABC, LogContextMixin):
395
395
  return False
396
396
  if not self.supports_push_updates:
397
397
  return True
398
- return (datetime.now() - self.modified_at).total_seconds() < CALLBACK_WARN_INTERVAL
398
+ return (datetime.now() - self.modified_at).total_seconds() < CALLBACK_WARN_ARM_INTERVAL
399
399
 
400
400
  def is_callback_alive(self) -> bool:
401
401
  """Return if XmlRPC-Server is alive based on received events for this client."""
@@ -404,31 +404,29 @@ class Client(ABC, LogContextMixin):
404
404
  if (
405
405
  last_events_dt := self.central.get_last_event_seen_for_interface(interface_id=self.interface_id)
406
406
  ) is not None:
407
- if (seconds_since_last_event := (datetime.now() - last_events_dt).total_seconds()) > CALLBACK_WARN_INTERVAL:
408
- if self._is_callback_alive:
409
- self.central.fire_interface_event(
410
- interface_id=self.interface_id,
411
- interface_event_type=InterfaceEventType.CALLBACK,
412
- data={
413
- EventKey.AVAILABLE: False,
414
- EventKey.SECONDS_SINCE_LAST_EVENT: int(seconds_since_last_event),
415
- },
416
- )
417
- self._is_callback_alive = False
407
+ if (
408
+ seconds_since_last_event := (datetime.now() - last_events_dt).total_seconds()
409
+ ) > CALLBACK_WARN_ARM_INTERVAL:
410
+ self.central.fire_interface_event(
411
+ interface_id=self.interface_id,
412
+ interface_event_type=InterfaceEventType.CALLBACK,
413
+ data={
414
+ EventKey.AVAILABLE: False,
415
+ EventKey.SECONDS_SINCE_LAST_EVENT: int(seconds_since_last_event),
416
+ },
417
+ )
418
418
  _LOGGER.warning(
419
419
  "IS_CALLBACK_ALIVE: Callback for %s has not received events for %is",
420
420
  self.interface_id,
421
421
  seconds_since_last_event,
422
422
  )
423
423
  return False
424
-
425
- if not self._is_callback_alive:
424
+ if ((datetime.now() - last_events_dt).total_seconds()) < CALLBACK_WARN_DISARM_INTERVAL:
426
425
  self.central.fire_interface_event(
427
426
  interface_id=self.interface_id,
428
427
  interface_event_type=InterfaceEventType.CALLBACK,
429
428
  data={EventKey.AVAILABLE: True},
430
429
  )
431
- self._is_callback_alive = True
432
430
  return True
433
431
 
434
432
  @abstractmethod
@@ -1510,8 +1508,13 @@ class ClientJsonCCU(ClientCCU):
1510
1508
  return True
1511
1509
 
1512
1510
 
1513
- class ClientHomegear(Client):
1514
- """Client implementation for Homegear backend."""
1511
+ class ClientHomegear(ClientCCU):
1512
+ """
1513
+ Client implementation for Homegear backend.
1514
+
1515
+ Inherit from ClientCCU to share common behavior used by tests and code paths
1516
+ that expect a CCU-like client interface for Homegear selections.
1517
+ """
1515
1518
 
1516
1519
  @property
1517
1520
  def model(self) -> str:
@@ -1525,7 +1528,7 @@ class ClientHomegear(Client):
1525
1528
  """Return the supports_ping_pong info of the backend."""
1526
1529
  return False
1527
1530
 
1528
- @inspector(re_raise=False)
1531
+ @inspector(re_raise=False, measure_performance=True)
1529
1532
  async def fetch_all_device_data(self) -> None:
1530
1533
  """Fetch all device data from the backend."""
1531
1534
  return
@@ -483,18 +483,6 @@ class AioJsonRpcAioHttpClient(LogContextMixin):
483
483
  )
484
484
  raise
485
485
 
486
- except ClientConnectorError as cceerr:
487
- self.clear_session()
488
- message = f"ClientConnectorError[{cceerr}]"
489
- log_boundary_error(
490
- logger=_LOGGER,
491
- boundary="json-rpc",
492
- action=str(method),
493
- err=cceerr,
494
- level=logging.ERROR,
495
- log_context=self.log_context,
496
- )
497
- raise ClientException(message) from cceerr
498
486
  except ClientConnectorCertificateError as cccerr:
499
487
  self.clear_session()
500
488
  message = f"ClientConnectorCertificateError[{cccerr}]"
@@ -512,6 +500,18 @@ class AioJsonRpcAioHttpClient(LogContextMixin):
512
500
  log_context=self.log_context,
513
501
  )
514
502
  raise ClientException(message) from cccerr
503
+ except ClientConnectorError as cceerr:
504
+ self.clear_session()
505
+ message = f"ClientConnectorError[{cceerr}]"
506
+ log_boundary_error(
507
+ logger=_LOGGER,
508
+ boundary="json-rpc",
509
+ action=str(method),
510
+ err=cceerr,
511
+ level=logging.ERROR,
512
+ log_context=self.log_context,
513
+ )
514
+ raise ClientException(message) from cceerr
515
515
  except (ClientError, OSError) as err:
516
516
  self.clear_session()
517
517
  log_boundary_error(
@@ -1182,11 +1182,11 @@ class AioJsonRpcAioHttpClient(LogContextMixin):
1182
1182
  """Get the supported methods of the backend."""
1183
1183
  supported_methods: tuple[str, ...] = ()
1184
1184
 
1185
- await self._login_or_renew()
1186
- if not (session_id := self._session_id):
1187
- raise ClientException("Error while logging in")
1188
-
1189
1185
  try:
1186
+ await self._login_or_renew()
1187
+ if not (session_id := self._session_id):
1188
+ raise ClientException("Error while logging in")
1189
+
1190
1190
  response = await self._do_post(
1191
1191
  session_id=session_id,
1192
1192
  method=_JsonRpcMethod.SYSTEM_LIST_METHODS,
@@ -19,7 +19,7 @@ import sys
19
19
  from types import MappingProxyType
20
20
  from typing import Any, Final, NamedTuple, Required, TypeAlias, TypedDict
21
21
 
22
- VERSION: Final = "2025.10.18"
22
+ VERSION: Final = "2025.10.19"
23
23
 
24
24
  # Detect test speedup mode via environment
25
25
  _TEST_SPEEDUP: Final = (
@@ -133,7 +133,8 @@ WAIT_FOR_CALLBACK: Final[int | None] = None
133
133
  SCHEDULER_NOT_STARTED_SLEEP: Final = 0.2 if _TEST_SPEEDUP else 10
134
134
  SCHEDULER_LOOP_SLEEP: Final = 0.2 if _TEST_SPEEDUP else 5
135
135
 
136
- CALLBACK_WARN_INTERVAL: Final = CONNECTION_CHECKER_INTERVAL * 40
136
+ CALLBACK_WARN_ARM_INTERVAL: Final = CONNECTION_CHECKER_INTERVAL * 40
137
+ CALLBACK_WARN_DISARM_INTERVAL: Final = CONNECTION_CHECKER_INTERVAL * 20
137
138
 
138
139
  # Path
139
140
  PROGRAM_SET_PATH_ROOT: Final = "program/set"
@@ -640,7 +640,7 @@ class Device(LogContextMixin, PayloadMixin):
640
640
  await self._central.refresh_firmware_data(device_address=self._address)
641
641
 
642
642
  if refresh_after_update_intervals:
643
- self._central.looper.create_task(target=refresh_data(), name="refresh_firmware_data")
643
+ self._central.looper.create_task(target=refresh_data, name="refresh_firmware_data")
644
644
 
645
645
  return update_result
646
646
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiohomematic
3
- Version: 2025.10.18
3
+ Version: 2025.10.19
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>