Skip to content

Commit

Permalink
Refactor ping pong (#1296)
Browse files Browse the repository at this point in the history
* Refactor ping pong cache

* Refactor ping pong
  • Loading branch information
SukramJ authored Nov 20, 2023
1 parent be39ffd commit 43382f3
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 42 deletions.
4 changes: 2 additions & 2 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Version 2023.11.1 (2023-11-19)
# Version 2023.11.1 (2023-11-20)

- Improve ping/pong mechanism
- Improve ping/pong mechanism. Fire event, if mismatch is 15 within 5 Minutes

# Version 2023.11.0 (2023-11-01)

Expand Down
31 changes: 23 additions & 8 deletions hahomematic/caches/dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Any, Final

from hahomematic import central as hmcu
from hahomematic.config import PING_PONG_MISMATCH_COUNT
from hahomematic.config import PING_PONG_MISMATCH_COUNT, PING_PONG_MISMATCH_COUNT_TTL
from hahomematic.const import (
INIT_DATETIME,
MAX_CACHE_AGE,
Expand Down Expand Up @@ -194,7 +194,10 @@ class PingPongCache:
"""Cache to collect ping/pong events with ttl."""

def __init__(
self, interface_id: str, allowed_delta: int = PING_PONG_MISMATCH_COUNT, ttl: int = 300
self,
interface_id: str,
allowed_delta: int = PING_PONG_MISMATCH_COUNT,
ttl: int = PING_PONG_MISMATCH_COUNT_TTL,
):
"""Initialize the cache with ttl."""
assert ttl > 0
Expand All @@ -205,14 +208,28 @@ def __init__(
self._pending_pongs: set[datetime] = set()
self._unknown_pongs: set[datetime] = set()

@property
def high_pending_pongs(self) -> bool:
"""Check, if store contains too many pending pongs."""
self._cleanup_pending_pongs()
return len(self._pending_pongs) > self._allowed_delta

@property
def high_unknown_pongs(self) -> bool:
"""Check, if store contains too many unknown pongs."""
self._cleanup_unknown_pongs()
return len(self._unknown_pongs) > self._allowed_delta

@property
def low_pending_pongs(self) -> bool:
"""Return the pending pong count is low."""
self._cleanup_pending_pongs()
return len(self._pending_pongs) < (self._allowed_delta / 2)

@property
def low_unknown_pongs(self) -> bool:
"""Return the unknown pong count is low."""
self._cleanup_unknown_pongs()
return len(self._unknown_pongs) < (self._allowed_delta / 2)

@property
Expand Down Expand Up @@ -253,22 +270,20 @@ def handle_received_pong(self, pong_ts: datetime) -> None:
self.unknown_pong_count,
)

def check_pending_pongs(self) -> bool:
"""Check, if store contains too many pending pongs."""
def _cleanup_pending_pongs(self) -> None:
"""Cleanup too old pending pongs."""
with self._sema:
dt_now = datetime.now()
for ping_ts in list(self._pending_pongs):
delta = dt_now - ping_ts
if delta.seconds > self._ttl:
self._pending_pongs.remove(ping_ts)
return len(self._pending_pongs) > self._allowed_delta

def check_unknown_pongs(self) -> bool:
"""Check, if store contains too many unknown pongs."""
def _cleanup_unknown_pongs(self) -> None:
"""Cleanup too old unknown pongs."""
with self._sema:
dt_now = datetime.now()
for pong_ts in list(self._unknown_pongs):
delta = dt_now - pong_ts
if delta.seconds > self._ttl:
self._unknown_pongs.remove(pong_ts)
return len(self._unknown_pongs) > self._allowed_delta
61 changes: 33 additions & 28 deletions hahomematic/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@
EVENT_DATA,
EVENT_INSTANCE_NAME,
EVENT_INTERFACE_ID,
EVENT_PENDING_PONGS,
EVENT_PONG_MISMATCH_COUNT,
EVENT_SECONDS_SINCE_LAST_EVENT,
EVENT_TYPE,
EVENT_UNKNOWN_PONGS,
HOMEGEAR_SERIAL,
INIT_DATETIME,
VIRTUAL_REMOTE_TYPES,
Expand Down Expand Up @@ -345,35 +344,32 @@ def handle_received_pong(self, pong_ts: datetime) -> None:
def _check_and_fire_pending_pong_event(self) -> None:
"""Fire an event about the pending pong status."""

def get_event_data(pending_pong_count: int) -> dict[str, Any]:
return {
EVENT_INTERFACE_ID: self.interface_id,
EVENT_TYPE: InterfaceEventType.PENDING_PONG,
EVENT_DATA: {
EVENT_INSTANCE_NAME: self.central.config.name,
EVENT_PENDING_PONGS: pending_pong_count,
},
}

if self._ping_pong_cache.low_pending_pongs is True:
self.central.fire_ha_event_callback(
event_type=EventType.INTERFACE,
event_data=cast(
dict[str, Any],
hmcu.INTERFACE_EVENT_SCHEMA(get_event_data(pending_pong_count=0)),
hmcu.INTERFACE_EVENT_SCHEMA(
self._get_pong_event_data(
event_type=InterfaceEventType.PENDING_PONG, pong_mismatch_count=0
)
),
),
)
return

if self._ping_pong_cache.check_pending_pongs() is False:
if self._ping_pong_cache.high_pending_pongs is False:
return

self.central.fire_ha_event_callback(
event_type=EventType.INTERFACE,
event_data=cast(
dict[str, Any],
hmcu.INTERFACE_EVENT_SCHEMA(
get_event_data(pending_pong_count=self._ping_pong_cache.pending_pong_count)
self._get_pong_event_data(
event_type=InterfaceEventType.PENDING_PONG,
pong_mismatch_count=self._ping_pong_cache.pending_pong_count,
)
),
),
)
Expand All @@ -392,35 +388,32 @@ def get_event_data(pending_pong_count: int) -> dict[str, Any]:
def _check_and_fire_unknown_pong_event(self) -> None:
"""Fire an event about the unknown pong status."""

def get_event_data(unknown_pong_count: int) -> dict[str, Any]:
return {
EVENT_INTERFACE_ID: self.interface_id,
EVENT_TYPE: InterfaceEventType.UNKNOWN_PONG,
EVENT_DATA: {
EVENT_INSTANCE_NAME: self.central.config.name,
EVENT_UNKNOWN_PONGS: unknown_pong_count,
},
}

if self._ping_pong_cache.low_unknown_pongs is True:
self.central.fire_ha_event_callback(
event_type=EventType.INTERFACE,
event_data=cast(
dict[str, Any],
hmcu.INTERFACE_EVENT_SCHEMA(get_event_data(unknown_pong_count=0)),
hmcu.INTERFACE_EVENT_SCHEMA(
self._get_pong_event_data(
event_type=InterfaceEventType.UNKNOWN_PONG, pong_mismatch_count=0
)
),
),
)
return

if self._ping_pong_cache.check_unknown_pongs() is False:
if self._ping_pong_cache.high_unknown_pongs is False:
return

self.central.fire_ha_event_callback(
event_type=EventType.INTERFACE,
event_data=cast(
dict[str, Any],
hmcu.INTERFACE_EVENT_SCHEMA(
get_event_data(unknown_pong_count=self._ping_pong_cache.unknown_pong_count)
self._get_pong_event_data(
event_type=InterfaceEventType.UNKNOWN_PONG,
pong_mismatch_count=self._ping_pong_cache.unknown_pong_count,
)
),
),
)
Expand All @@ -434,6 +427,18 @@ def get_event_data(unknown_pong_count: int) -> dict[str, Any]:

self._unknown_pong_logged = True

def _get_pong_event_data(
self, event_type: InterfaceEventType, pong_mismatch_count: int
) -> dict[str, Any]:
return {
EVENT_INTERFACE_ID: self.interface_id,
EVENT_TYPE: event_type,
EVENT_DATA: {
EVENT_INSTANCE_NAME: self.central.config.name,
EVENT_PONG_MISMATCH_COUNT: pong_mismatch_count,
},
}

async def set_install_mode(
self,
on: bool = True,
Expand Down
2 changes: 2 additions & 0 deletions hahomematic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
DEFAULT_CONNECTION_CHECKER_INTERVAL,
DEFAULT_JSON_SESSION_AGE,
DEFAULT_PING_PONG_MISMATCH_COUNT,
DEFAULT_PING_PONG_MISMATCH_COUNT_TTL,
DEFAULT_RECONNECT_WAIT,
DEFAULT_TIMEOUT,
)
Expand All @@ -13,5 +14,6 @@
CONNECTION_CHECKER_INTERVAL = DEFAULT_CONNECTION_CHECKER_INTERVAL
JSON_SESSION_AGE = DEFAULT_JSON_SESSION_AGE
PING_PONG_MISMATCH_COUNT = DEFAULT_PING_PONG_MISMATCH_COUNT
PING_PONG_MISMATCH_COUNT_TTL = DEFAULT_PING_PONG_MISMATCH_COUNT_TTL
RECONNECT_WAIT = DEFAULT_RECONNECT_WAIT
TIMEOUT = DEFAULT_TIMEOUT
4 changes: 2 additions & 2 deletions hahomematic/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
DEFAULT_ENCODING: Final = "UTF-8"
DEFAULT_JSON_SESSION_AGE: Final = 90
DEFAULT_PING_PONG_MISMATCH_COUNT: Final = 15
DEFAULT_PING_PONG_MISMATCH_COUNT_TTL: Final = 300
DEFAULT_RECONNECT_WAIT: Final = 120 # wait with reconnect after a first ping was successful
DEFAULT_TIMEOUT: Final = 60 # default timeout for a connection
DEFAULT_TLS: Final = False
Expand Down Expand Up @@ -54,11 +55,10 @@
EVENT_DEVICE_TYPE: Final = "device_type"
EVENT_INSTANCE_NAME: Final = "instance_name"
EVENT_INTERFACE_ID: Final = "interface_id"
EVENT_PENDING_PONGS: Final = "pending_pongs"
EVENT_PARAMETER: Final = "parameter"
EVENT_PONG_MISMATCH_COUNT: Final = "pong_mismatch_count"
EVENT_SECONDS_SINCE_LAST_EVENT: Final = "seconds_since_last_event"
EVENT_TYPE: Final = "type"
EVENT_UNKNOWN_PONGS: Final = "unknown_pongs"
EVENT_VALUE: Final = "value"

FILE_DEVICES: Final = "homematic_devices.json"
Expand Down
4 changes: 2 additions & 2 deletions tests/test_central.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ async def test_pending_pong_failure(factory: helper.Factory) -> None:
{
"data": {
"instance_name": "CentralTest",
"pending_pongs": 16,
"pong_mismatch_count": 16,
},
"interface_id": "CentralTest-BidCos-RF",
"type": InterfaceEventType.PENDING_PONG,
Expand Down Expand Up @@ -505,7 +505,7 @@ async def test_unknown_pong_failure(factory: helper.Factory) -> None:
{
"data": {
"instance_name": "CentralTest",
"unknown_pongs": 16,
"pong_mismatch_count": 16,
},
"interface_id": "CentralTest-BidCos-RF",
"type": InterfaceEventType.UNKNOWN_PONG,
Expand Down

0 comments on commit 43382f3

Please sign in to comment.