Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
spacemanspiff2007 committed Jan 8, 2024
1 parent df74ef4 commit d3f6826
Show file tree
Hide file tree
Showing 14 changed files with 88 additions and 14 deletions.
24 changes: 24 additions & 0 deletions docs/util.rst
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,30 @@ Example
# It's possible to get statistics about the limiter and the corresponding windows
print(limiter.info())

# There is a counter that keeps track of the total skips that can be reset
print('Counter:')
print(limiter.total_skips)
limiter.reset() # Can be reset
print(limiter.total_skips)


Recommendation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Limiting external requests to an external API works well with the leaky bucket algorithm (maybe with some initial hits).
For limiting notifications the best results can be achieved by combining both algorithms.
Fixed window elastic expiry will notify but block until an issue is resolved,
that's why it's more suited for small intervals. Leaky bucket will allow hits even while the issue persists,
that's why it's more suited for larger intervals.

.. exec_code::

from HABApp.util import RateLimiter

limiter = RateLimiter('MyNotifications')
limiter.parse_limits('5 in 1 minute', algorithm='fixed_window_elastic_expiry')
limiter.parse_limits("20 in 1 hour", algorithm='leaky_bucket')


Documentation
^^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ MyOpenhabRule()
```

# Changelog
#### 23.12.0-DEV (2023-XX-XX)
#### 24.01.0-DEV (2024-XX-XX)
- Added HABApp.util.RateLimiter
- Added CompressedMidnightRotatingFileHandler
- Updated dependencies
Expand Down
41 changes: 38 additions & 3 deletions run/conf_testing/rules/openhab/test_persistence.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from __future__ import annotations

from datetime import datetime, timedelta
from typing import Final, Any
from typing import TYPE_CHECKING, Any, Final

from HABApp.openhab.definitions.helpers import OpenhabPersistenceData
from HABAppTests import ItemWaiter, TestBaseRule

from HABApp.core.connections import Connections
from HABApp.openhab.items import NumberItem
from HABAppTests import TestBaseRule


if TYPE_CHECKING:
from HABApp.openhab.definitions.helpers import OpenhabPersistenceData


class TestPersistenceBase(TestBaseRule):
Expand All @@ -20,6 +25,9 @@ def __init__(self, service_name: str, item_name: str):

def set_up(self):
i = NumberItem.get_item(self.item_name)
if i.value is None:
i.oh_post_update(0)
ItemWaiter(self.item_name).wait_for_state(0)
i.oh_post_update(int(i.value) + 1 if i.value < 10 else 0)

def test_service_available(self):
Expand Down Expand Up @@ -64,3 +72,30 @@ def test_get(self):


TestMapDB()


class TestInMemory(TestPersistenceBase):

def __init__(self):
super().__init__('inmemory', 'RRD4J_Item')

if Connections.get('openhab').context.version >= (4, 1):
self.add_test('InMemory', self.test_in_memory)
else:
print('Skip "TestInMemory" because of no InMemoryDb')

def test_in_memory(self):
now = datetime.now().replace(microsecond=0)
t1 = now - timedelta(milliseconds=100)
t2 = now + timedelta(milliseconds=100)

self.set_persistence_data(t1, 5)
self.set_persistence_data(now, 6)
self.set_persistence_data(t2, 7)
value = self.get_persistence_data(now - timedelta(milliseconds=200), now + timedelta(milliseconds=200))

objs = value.get_data()
assert objs == {t1.timestamp(): 5, now.timestamp(): 6, t2.timestamp(): 7}


TestInMemory()
2 changes: 1 addition & 1 deletion src/HABApp/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
# Development versions contain the DEV-COUNTER postfix:
# - 23.09.0.DEV-1

__version__ = '23.12.0.DEV-1'
__version__ = '24.01.0.DEV-1'
3 changes: 3 additions & 0 deletions src/HABApp/mqtt/connection/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ async def on_disconnected(self, connection: MqttConnection, context: CONTEXT_TYP
assert context is not None

connection.log.info('Disconnected')
# remove this check when https://github.com/sbtinstruments/aiomqtt/pull/249 gets merged
if not context._lock.locked():
await context._lock.acquire()
await context.__aexit__(None, None, None)


Expand Down
2 changes: 2 additions & 0 deletions src/HABApp/openhab/connection/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class OpenhabContext:
session: aiohttp.ClientSession
session_options: dict[str, Any]

workaround_small_floats: bool


CONTEXT_TYPE: TypeAlias = Optional[OpenhabContext]

Expand Down
4 changes: 3 additions & 1 deletion src/HABApp/openhab/connection/handler/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ async def on_connecting(self, connection: OpenhabConnection):
version=vers, is_oh3=vers < (4, 0),
waited_for_openhab=False,
created_items={}, created_things={},
session=self.session, session_options=self.options
session=self.session, session_options=self.options,

workaround_small_floats=vers < (4, 1)
)

# during startup we get OpenhabCredentialsInvalidError even though credentials are correct
Expand Down
7 changes: 5 additions & 2 deletions src/HABApp/openhab/connection/handler/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
from typing import Any

from HABApp.core.items import BaseValueItem
from HABApp.core.types import RGB, HSB
from HABApp.core.types import HSB, RGB


def convert_to_oh_type(obj: Any) -> str:
def convert_to_oh_type(obj: Any, scientific_floats=False) -> str:
if isinstance(obj, (str, int, bool)):
return str(obj)

if isinstance(obj, float):
if scientific_floats:
return str(obj)

v = str(obj)
if 'e-' not in v:
return v
Expand Down
4 changes: 3 additions & 1 deletion src/HABApp/openhab/connection/plugins/out.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ async def queue_worker(self):
queue: Final = self.queue
to_str: Final = convert_to_oh_type

scientific_floats = not self.plugin_connection.context.workaround_small_floats

while True:
try:
while True:
Expand All @@ -84,7 +86,7 @@ async def queue_worker(self):
item = item._name

if not isinstance(state, str):
state = to_str(state)
state = to_str(state, scientific_floats=scientific_floats)

if is_cmd:
await post(f'/rest/items/{item:s}', data=state)
Expand Down
1 change: 1 addition & 0 deletions src/HABApp/openhab/definitions/helpers/persistence_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from HABApp.openhab.definitions.rest import ItemHistoryResp


OPTIONAL_DT = Optional[datetime]


Expand Down
4 changes: 2 additions & 2 deletions src/HABApp/util/rate_limiter/limiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def __init__(self, name: str):

@property
def total_skips(self) -> int:
"""A user counter to track skips which can be manually reset"""
"""A counter to track skips which can be manually reset"""
return self._skips_total

def __repr__(self):
Expand Down Expand Up @@ -146,7 +146,7 @@ def info(self) -> 'LimiterInfo':
)

def reset(self) -> 'Limiter':
"""Reset the manual skip counter"""
"""Reset the skip counter"""
self._skips_total = 0
return self

Expand Down
2 changes: 1 addition & 1 deletion src/HABApp/util/rate_limiter/limits/leaky_bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def __init__(self, allowed: int, interval: int, hits: int = 0):
super().__init__(allowed, interval, hits)

self.drop_interval: Final = interval / allowed
self.next_drop: float = -1.0
self.next_drop: float = monotonic() + self.drop_interval

def repr_text(self):
return f'drop_interval={self.drop_interval:.1f}s'
Expand Down
2 changes: 1 addition & 1 deletion tests/test_openhab/test_items/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
def test_OnOff(cls):
c = cls('item_name')
assert not c.is_on()
if not __version__.startswith('23.12.0'):
if not __version__.startswith('24.01.0'):
assert not c.is_off()

c.set_value(OnOffValue('ON'))
Expand Down
4 changes: 3 additions & 1 deletion tests/test_openhab/test_plugins/test_load_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ async def test_item_sync(monkeypatch, ir: ItemRegistry, test_logs):
waited_for_openhab=False,
created_items={}, created_things={},

session=None, session_options=None
session=None, session_options=None,

workaround_small_floats=False
)
# initial item create
await LoadOpenhabItemsPlugin().on_connected(context)
Expand Down

0 comments on commit d3f6826

Please sign in to comment.