aiohomematic 2025.10.21__py3-none-any.whl → 2025.10.22__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.
- aiohomematic/const.py +2 -2
- aiohomematic/store/dynamic.py +71 -98
- aiohomematic/store/visibility.py +1 -1
- {aiohomematic-2025.10.21.dist-info → aiohomematic-2025.10.22.dist-info}/METADATA +1 -1
- {aiohomematic-2025.10.21.dist-info → aiohomematic-2025.10.22.dist-info}/RECORD +8 -8
- {aiohomematic-2025.10.21.dist-info → aiohomematic-2025.10.22.dist-info}/WHEEL +0 -0
- {aiohomematic-2025.10.21.dist-info → aiohomematic-2025.10.22.dist-info}/licenses/LICENSE +0 -0
- {aiohomematic-2025.10.21.dist-info → aiohomematic-2025.10.22.dist-info}/top_level.txt +0 -0
aiohomematic/const.py
CHANGED
|
@@ -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.
|
|
22
|
+
VERSION: Final = "2025.10.22"
|
|
23
23
|
|
|
24
24
|
# Detect test speedup mode via environment
|
|
25
25
|
_TEST_SPEEDUP: Final = (
|
|
@@ -303,7 +303,7 @@ class EventKey(StrEnum):
|
|
|
303
303
|
INTERFACE_ID = "interface_id"
|
|
304
304
|
MODEL = "model"
|
|
305
305
|
PARAMETER = "parameter"
|
|
306
|
-
|
|
306
|
+
PONG_MISMATCH_ACCEPTABLE = "pong_mismatch_allowed"
|
|
307
307
|
PONG_MISMATCH_COUNT = "pong_mismatch_count"
|
|
308
308
|
SECONDS_SINCE_LAST_EVENT = "seconds_since_last_event"
|
|
309
309
|
TYPE = "type"
|
aiohomematic/store/dynamic.py
CHANGED
|
@@ -392,36 +392,12 @@ class PingPongCache:
|
|
|
392
392
|
return self._allowed_delta
|
|
393
393
|
|
|
394
394
|
@property
|
|
395
|
-
def
|
|
396
|
-
"""Check, if store contains too many pending pongs."""
|
|
397
|
-
self._cleanup_pending_pongs()
|
|
398
|
-
return len(self._pending_pongs) > self._allowed_delta
|
|
399
|
-
|
|
400
|
-
@property
|
|
401
|
-
def high_unknown_pongs(self) -> bool:
|
|
402
|
-
"""Check, if store contains too many unknown pongs."""
|
|
403
|
-
self._cleanup_unknown_pongs()
|
|
404
|
-
return len(self._unknown_pongs) > self._allowed_delta
|
|
405
|
-
|
|
406
|
-
@property
|
|
407
|
-
def low_pending_pongs(self) -> bool:
|
|
408
|
-
"""Return True when pending pong count is at or below the allowed delta (i.e., not high)."""
|
|
409
|
-
self._cleanup_pending_pongs()
|
|
410
|
-
return len(self._pending_pongs) <= self._allowed_delta
|
|
411
|
-
|
|
412
|
-
@property
|
|
413
|
-
def low_unknown_pongs(self) -> bool:
|
|
414
|
-
"""Return True when unknown pong count is at or below the allowed delta (i.e., not high)."""
|
|
415
|
-
self._cleanup_unknown_pongs()
|
|
416
|
-
return len(self._unknown_pongs) <= self._allowed_delta
|
|
417
|
-
|
|
418
|
-
@property
|
|
419
|
-
def pending_pong_count(self) -> int:
|
|
395
|
+
def _pending_pong_count(self) -> int:
|
|
420
396
|
"""Return the pending pong count."""
|
|
421
397
|
return len(self._pending_pongs)
|
|
422
398
|
|
|
423
399
|
@property
|
|
424
|
-
def
|
|
400
|
+
def _unknown_pong_count(self) -> int:
|
|
425
401
|
"""Return the unknown pong count."""
|
|
426
402
|
return len(self._unknown_pongs)
|
|
427
403
|
|
|
@@ -435,18 +411,16 @@ class PingPongCache:
|
|
|
435
411
|
def handle_send_ping(self, *, ping_ts: datetime) -> None:
|
|
436
412
|
"""Handle send ping timestamp."""
|
|
437
413
|
self._pending_pongs.add(ping_ts)
|
|
414
|
+
self._cleanup_pending_pongs()
|
|
438
415
|
# Throttle event emission to every second ping to avoid spamming callbacks,
|
|
439
416
|
# but always emit when crossing the high threshold.
|
|
440
|
-
count = self.
|
|
417
|
+
count = self._pending_pong_count
|
|
441
418
|
if (count > self._allowed_delta) or (count % 2 == 0):
|
|
442
|
-
self._check_and_fire_pong_event(
|
|
443
|
-
event_type=InterfaceEventType.PENDING_PONG,
|
|
444
|
-
pong_mismatch_count=count,
|
|
445
|
-
)
|
|
419
|
+
self._check_and_fire_pong_event(event_type=InterfaceEventType.PENDING_PONG)
|
|
446
420
|
_LOGGER.debug(
|
|
447
421
|
"PING PONG CACHE: Increase pending PING count: %s - %i for ts: %s",
|
|
448
422
|
self._interface_id,
|
|
449
|
-
|
|
423
|
+
count,
|
|
450
424
|
ping_ts,
|
|
451
425
|
)
|
|
452
426
|
|
|
@@ -454,59 +428,56 @@ class PingPongCache:
|
|
|
454
428
|
"""Handle received pong timestamp."""
|
|
455
429
|
if pong_ts in self._pending_pongs:
|
|
456
430
|
self._pending_pongs.remove(pong_ts)
|
|
457
|
-
self.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
)
|
|
431
|
+
self._cleanup_pending_pongs()
|
|
432
|
+
count = self._pending_pong_count
|
|
433
|
+
self._check_and_fire_pong_event(event_type=InterfaceEventType.PENDING_PONG)
|
|
461
434
|
_LOGGER.debug(
|
|
462
435
|
"PING PONG CACHE: Reduce pending PING count: %s - %i for ts: %s",
|
|
463
436
|
self._interface_id,
|
|
464
|
-
|
|
437
|
+
count,
|
|
438
|
+
pong_ts,
|
|
439
|
+
)
|
|
440
|
+
else:
|
|
441
|
+
self._unknown_pongs.add(pong_ts)
|
|
442
|
+
self._cleanup_unknown_pongs()
|
|
443
|
+
count = self._unknown_pong_count
|
|
444
|
+
self._check_and_fire_pong_event(event_type=InterfaceEventType.UNKNOWN_PONG)
|
|
445
|
+
_LOGGER.debug(
|
|
446
|
+
"PING PONG CACHE: Increase unknown PONG count: %s - %i for ts: %s",
|
|
447
|
+
self._interface_id,
|
|
448
|
+
count,
|
|
465
449
|
pong_ts,
|
|
466
450
|
)
|
|
467
|
-
return
|
|
468
|
-
|
|
469
|
-
self._unknown_pongs.add(pong_ts)
|
|
470
|
-
self._check_and_fire_pong_event(
|
|
471
|
-
event_type=InterfaceEventType.UNKNOWN_PONG,
|
|
472
|
-
pong_mismatch_count=self.unknown_pong_count,
|
|
473
|
-
)
|
|
474
|
-
_LOGGER.debug(
|
|
475
|
-
"PING PONG CACHE: Increase unknown PONG count: %s - %i for ts: %s",
|
|
476
|
-
self._interface_id,
|
|
477
|
-
self.unknown_pong_count,
|
|
478
|
-
pong_ts,
|
|
479
|
-
)
|
|
480
451
|
|
|
481
452
|
def _cleanup_pending_pongs(self) -> None:
|
|
482
453
|
"""Cleanup too old pending pongs."""
|
|
483
454
|
dt_now = datetime.now()
|
|
484
|
-
for
|
|
455
|
+
for pp_pong_ts in list(self._pending_pongs):
|
|
485
456
|
# Only expire entries that are actually older than the TTL.
|
|
486
|
-
if (dt_now -
|
|
487
|
-
self._pending_pongs.remove(
|
|
457
|
+
if (dt_now - pp_pong_ts).total_seconds() > self._ttl:
|
|
458
|
+
self._pending_pongs.remove(pp_pong_ts)
|
|
488
459
|
_LOGGER.debug(
|
|
489
460
|
"PING PONG CACHE: Removing expired pending PONG: %s - %i for ts: %s",
|
|
490
461
|
self._interface_id,
|
|
491
|
-
self.
|
|
492
|
-
|
|
462
|
+
self._pending_pong_count,
|
|
463
|
+
pp_pong_ts,
|
|
493
464
|
)
|
|
494
465
|
|
|
495
466
|
def _cleanup_unknown_pongs(self) -> None:
|
|
496
467
|
"""Cleanup too old unknown pongs."""
|
|
497
468
|
dt_now = datetime.now()
|
|
498
|
-
for
|
|
469
|
+
for up_pong_ts in list(self._unknown_pongs):
|
|
499
470
|
# Only expire entries that are actually older than the TTL.
|
|
500
|
-
if (dt_now -
|
|
501
|
-
self._unknown_pongs.remove(
|
|
471
|
+
if (dt_now - up_pong_ts).total_seconds() > self._ttl:
|
|
472
|
+
self._unknown_pongs.remove(up_pong_ts)
|
|
502
473
|
_LOGGER.debug(
|
|
503
474
|
"PING PONG CACHE: Removing expired unknown PONG: %s - %i or ts: %s",
|
|
504
475
|
self._interface_id,
|
|
505
|
-
self.
|
|
506
|
-
|
|
476
|
+
self._unknown_pong_count,
|
|
477
|
+
up_pong_ts,
|
|
507
478
|
)
|
|
508
479
|
|
|
509
|
-
def _check_and_fire_pong_event(self, *, event_type: InterfaceEventType
|
|
480
|
+
def _check_and_fire_pong_event(self, *, event_type: InterfaceEventType) -> None:
|
|
510
481
|
"""Fire an event about the pong status."""
|
|
511
482
|
|
|
512
483
|
def _fire_event(mismatch_count: int) -> None:
|
|
@@ -520,7 +491,7 @@ class PingPongCache:
|
|
|
520
491
|
EventKey.TYPE: event_type,
|
|
521
492
|
EventKey.DATA: {
|
|
522
493
|
EventKey.CENTRAL_NAME: self._central.name,
|
|
523
|
-
EventKey.
|
|
494
|
+
EventKey.PONG_MISMATCH_ACCEPTABLE: mismatch_count <= self._allowed_delta,
|
|
524
495
|
EventKey.PONG_MISMATCH_COUNT: mismatch_count,
|
|
525
496
|
},
|
|
526
497
|
}
|
|
@@ -528,44 +499,46 @@ class PingPongCache:
|
|
|
528
499
|
),
|
|
529
500
|
)
|
|
530
501
|
|
|
531
|
-
if
|
|
502
|
+
if event_type == InterfaceEventType.PENDING_PONG:
|
|
503
|
+
self._cleanup_pending_pongs()
|
|
504
|
+
count = self._pending_pong_count
|
|
505
|
+
if self._pending_pong_count > self._allowed_delta:
|
|
506
|
+
# Emit interface event to inform subscribers about high pending pong count.
|
|
507
|
+
_fire_event(mismatch_count=count)
|
|
508
|
+
if self._pending_pong_logged is False:
|
|
509
|
+
_LOGGER.warning(
|
|
510
|
+
"Pending PONG mismatch: There is a mismatch between send ping events and received pong events for instance %s. "
|
|
511
|
+
"Possible reason 1: You are running multiple instances with the same instance name configured for this integration. "
|
|
512
|
+
"Re-add one instance! Otherwise this instance will not receive update events from your CCU. "
|
|
513
|
+
"Possible reason 2: Something is stuck on the CCU or hasn't been cleaned up. Therefore, try a CCU restart."
|
|
514
|
+
"Possible reason 3: Your setup is misconfigured and this instance is not able to receive events from the CCU.",
|
|
515
|
+
self._interface_id,
|
|
516
|
+
)
|
|
517
|
+
self._pending_pong_logged = True
|
|
532
518
|
# In low state:
|
|
533
519
|
# - If we previously logged a high state, emit a reset event (mismatch=0) exactly once.
|
|
534
520
|
# - Otherwise, throttle emission to every second ping (even counts > 0) to avoid spamming.
|
|
535
|
-
|
|
521
|
+
elif self._pending_pong_logged:
|
|
536
522
|
_fire_event(mismatch_count=0)
|
|
537
523
|
self._pending_pong_logged = False
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
)
|
|
560
|
-
self._pending_pong_logged = True
|
|
561
|
-
|
|
562
|
-
if self.high_unknown_pongs and event_type == InterfaceEventType.UNKNOWN_PONG:
|
|
563
|
-
if self._unknown_pong_logged is False:
|
|
564
|
-
_LOGGER.warning(
|
|
565
|
-
"Unknown PONG Mismatch: Your instance %s receives PONG events, that it hasn't send. "
|
|
566
|
-
"Possible reason 1: You are running multiple instances with the same instance name configured for this integration. "
|
|
567
|
-
"Re-add one instance! Otherwise the other instance will not receive update events from your CCU. "
|
|
568
|
-
"Possible reason 2: Something is stuck on the CCU or hasn't been cleaned up. Therefore, try a CCU restart.",
|
|
569
|
-
self._interface_id,
|
|
570
|
-
)
|
|
571
|
-
self._unknown_pong_logged = True
|
|
524
|
+
elif count > 0 and count % 2 == 0:
|
|
525
|
+
_fire_event(mismatch_count=count)
|
|
526
|
+
elif event_type == InterfaceEventType.UNKNOWN_PONG:
|
|
527
|
+
self._cleanup_unknown_pongs()
|
|
528
|
+
count = self._unknown_pong_count
|
|
529
|
+
if self._unknown_pong_count > self._allowed_delta:
|
|
530
|
+
# Emit interface event to inform subscribers about high unknown pong count.
|
|
531
|
+
_fire_event(mismatch_count=count)
|
|
532
|
+
if self._unknown_pong_logged is False:
|
|
533
|
+
_LOGGER.warning(
|
|
534
|
+
"Unknown PONG Mismatch: Your instance %s receives PONG events, that it hasn't send. "
|
|
535
|
+
"Possible reason 1: You are running multiple instances with the same instance name configured for this integration. "
|
|
536
|
+
"Re-add one instance! Otherwise the other instance will not receive update events from your CCU. "
|
|
537
|
+
"Possible reason 2: Something is stuck on the CCU or hasn't been cleaned up. Therefore, try a CCU restart.",
|
|
538
|
+
self._interface_id,
|
|
539
|
+
)
|
|
540
|
+
self._unknown_pong_logged = True
|
|
541
|
+
else:
|
|
542
|
+
# For unknown pongs, only reset the logged flag when we drop below the threshold.
|
|
543
|
+
# We do not emit an event here since there is no explicit expectation for a reset notification.
|
|
544
|
+
self._unknown_pong_logged = False
|
aiohomematic/store/visibility.py
CHANGED
|
@@ -317,7 +317,7 @@ _IGNORE_PARAMETERS_BY_DEVICE: Final[Mapping[Parameter, frozenset[TModelName]]] =
|
|
|
317
317
|
"HmIP-WGT",
|
|
318
318
|
}
|
|
319
319
|
),
|
|
320
|
-
Parameter.VALVE_STATE: frozenset({"HmIPW-FALMOT-C12", "HmIP-FALMOT-C12"}),
|
|
320
|
+
Parameter.VALVE_STATE: frozenset({"HmIP-FALMOT-C8", "HmIPW-FALMOT-C12", "HmIP-FALMOT-C12"}),
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
_IGNORE_PARAMETERS_BY_DEVICE_LOWER: Final[dict[TParameterName, frozenset[TModelName]]] = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiohomematic
|
|
3
|
-
Version: 2025.10.
|
|
3
|
+
Version: 2025.10.22
|
|
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>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
aiohomematic/__init__.py,sha256=Uo9CIoil0Arl3GwtgMZAwM8jhcgoBKcZEgj8cXYlswY,2258
|
|
2
2
|
aiohomematic/async_support.py,sha256=Fg6RLD7Irt1mTwXbLkfphJbfd7oU_Svhp23i3Bb4Q7k,8762
|
|
3
|
-
aiohomematic/const.py,sha256=
|
|
3
|
+
aiohomematic/const.py,sha256=omyYHT1lCVMAju-lRA3nTU0yuX2C_vM8MuZ5K3ofstA,27482
|
|
4
4
|
aiohomematic/context.py,sha256=hGE-iPcPt21dY-1MZar-Hyh9YaKL-VS42xjrulIVyRQ,429
|
|
5
5
|
aiohomematic/converter.py,sha256=FiHU71M5RZ7N5FXJYh2CN14s63-PM-SHdb0cJ_CLx54,3602
|
|
6
6
|
aiohomematic/decorators.py,sha256=cSW0aF3PzrW_qW6H0sjRNH9eqO8ysqhXZDgJ2OJTZM4,11038
|
|
@@ -66,11 +66,11 @@ aiohomematic/rega_scripts/get_system_variable_descriptions.fn,sha256=UKXvC0_5lSA
|
|
|
66
66
|
aiohomematic/rega_scripts/set_program_state.fn,sha256=0bnv7lUj8FMjDZBz325tDVP61m04cHjVj4kIOnUUgpY,279
|
|
67
67
|
aiohomematic/rega_scripts/set_system_variable.fn,sha256=sTmr7vkPTPnPkor5cnLKlDvfsYRbGO1iq2z_2pMXq5E,383
|
|
68
68
|
aiohomematic/store/__init__.py,sha256=PHwF_tw_zL20ODwLywHgpOLWrghQo_BMZzeiQSXN1Fc,1081
|
|
69
|
-
aiohomematic/store/dynamic.py,sha256=
|
|
69
|
+
aiohomematic/store/dynamic.py,sha256=7grL-rpIh4CYWVNqL7WO66zRJuw7qfi34S2sxSbsOac,22049
|
|
70
70
|
aiohomematic/store/persistent.py,sha256=SBL8AhqUzpoPtJ50GkLYHwvRJS52fBWqNPjgvykxbY8,40233
|
|
71
|
-
aiohomematic/store/visibility.py,sha256=
|
|
72
|
-
aiohomematic-2025.10.
|
|
73
|
-
aiohomematic-2025.10.
|
|
74
|
-
aiohomematic-2025.10.
|
|
75
|
-
aiohomematic-2025.10.
|
|
76
|
-
aiohomematic-2025.10.
|
|
71
|
+
aiohomematic/store/visibility.py,sha256=VAoaHMsCnIwcaTQjx_RkpPb64o5xjVNRldCfB2MGc1M,31680
|
|
72
|
+
aiohomematic-2025.10.22.dist-info/licenses/LICENSE,sha256=q-B0xpREuZuvKsmk3_iyVZqvZ-vJcWmzMZpeAd0RqtQ,1083
|
|
73
|
+
aiohomematic-2025.10.22.dist-info/METADATA,sha256=EG8oIYVQQVkO-mIfd5qUP8m1dxEULgWytT8DReLcBlo,7672
|
|
74
|
+
aiohomematic-2025.10.22.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
75
|
+
aiohomematic-2025.10.22.dist-info/top_level.txt,sha256=iGUvt1N-E72vKRq7Anpp62HwkQngStrUK0JfL1zj1TE,13
|
|
76
|
+
aiohomematic-2025.10.22.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|