Skip to content

Commit

Permalink
1.0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
spacemanspiff2007 authored Aug 9, 2022
1 parent 062e87f commit decea19
Show file tree
Hide file tree
Showing 42 changed files with 369 additions and 162 deletions.
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
* text=auto

*.py text
*.rst text
*.yml text
*.yaml text
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ repos:
# name: isort (python)

- repo: https://gitlab.com/PyCQA/flake8
rev: '3.9.1'
rev: '4.0.1'
hooks:
- id: flake8
71 changes: 62 additions & 9 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ a list of openHAB items and adds them to the internal registry.
Rules and HABApp derived libraries may add additional local items which can be used
to share states across rules and/or files.

Access
""""""""""""""""""""""""""""""""""""""

An item is created and added to the item registry through the corresponding class factory method

.. exec_code::
Expand All @@ -104,12 +107,14 @@ An item is created and added to the item registry through the corresponding clas
# This will create an item in the local (HABApp) item registry
item = Item.get_create_item("an-item-name", "a value")

Values
""""""""""""""""""""""""""""""""""""""

Posting values from the item will automatically create the events on the event bus.
This example will create an item in HABApp (locally) and post some updates to it.
To access items from openHAB use the correct openHAB item type (see :ref:`the openHAB item description <OPENHAB_ITEM_TYPES>`).

.. exec_code::
:caption: Output

# ------------ hide: start ------------
import logging
Expand Down Expand Up @@ -158,9 +163,56 @@ To access items from openHAB use the correct openHAB item type (see :ref:`the op
# ------------ hide: stop -------------


Timestamps
""""""""""""""""""""""""""""""""""""""

All items have two additional timestamps set which can be used to simplify rule logic.

* The time when the item was last updated
* The time when the item was last changed.


.. exec_code::

# ------------ hide: start ------------
from pendulum import DateTime
from HABApp.core.items import Item
from rule_runner import SimpleRuleRunner

runner = SimpleRuleRunner()
runner.set_up()

item = Item.get_create_item('Item_Name', initial_value='old_value')
item._last_update.dt = DateTime(2022, 8, 20, 12, 16)
item._last_change.dt = DateTime(2022, 8, 20, 10, 30)

# ------------ hide: stop -------------
import HABApp
from HABApp.core.items import Item

class TimestampRule(HABApp.Rule):
def __init__(self):
super().__init__()
# This item was created by another rule, that's why "get_item" is used
self.my_item = Item.get_item('Item_Name')

# Access of timestamps
print(f'Last update: {self.my_item.last_update}')
print(f'Last change: {self.my_item.last_change}')

TimestampRule()

# ------------ hide: start ------------
runner.tear_down()
# ------------ hide: stop -------------



Watch items for events
------------------------------
It is possible to watch items for changes or updates.
The ``listen_event`` function takes an instance of ``EventFilter`` which describes the kind of event that will be
passed to the callback.


.. exec_code::
Expand All @@ -186,25 +238,26 @@ It is possible to watch items for changes or updates.
# Run this function whenever the item receives an ValueUpdateEvent
self.listen_event(self.my_item, self.item_updated, ValueUpdateEventFilter())

# Run this function whenever the item receives an ValueChangeEvent
self.listen_event(self.my_item, self.item_changed, ValueChangeEventFilter())

# If you already have an item you can use the more convenient method of the item
# This is the recommended way to use event listener
# This is the recommended way to use the event listener
self.my_item.listen_event(self.item_updated, ValueUpdateEventFilter())

# Run this function whenever the item receives an ValueChangeEvent
self.my_item.listen_event(self.item_changed, ValueChangeEventFilter())

# the function has 1 argument which is the event
def item_updated(self, event: ValueUpdateEvent):
print(f'{event.name} updated value: "{event.value}"')
print(f'Last update of {self.my_item.name}: {self.my_item.last_update}')

def item_changed(self, event: ValueChangeEvent):
print(f'{event.name} changed from "{event.old_value}" to "{event.value}"')
print(f'Last change of {self.my_item.name}: {self.my_item.last_change}')

def item_updated(self, event: ValueUpdateEvent):
print(f'{event.name} updated value: "{event.value}"')
print(f'Last update of {self.my_item.name}: {self.my_item.last_update}')

MyFirstRule()
# ------------ hide: start ------------
i = Item.get_create_item('Item_Name')
i = Item.get_item('Item_Name')
i.post_value('Changed value')
runner.process_events()
runner.tear_down()
Expand Down
22 changes: 22 additions & 0 deletions docs/troubleshooting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@
Troubleshooting
**************************************

Warnings
======================================

Starting of <FUNC_NAME> took too long.
--------------------------------------

This warning appears in the HABApp log, e.g.::

Starting of MyRule.my_func took too long: 0.08s. Maybe there are not enough threads?

It means that the duration from when the event was received to the start of the execution of the function
took longer than expected.

This can be the case if suddenly many events are received at once.
Another reason for this warning might be that currently running function calls take too long to finish and thus no free
workers are available. This can either be the case for complex calculations,
but most of the time it's blocking function calls or a ``time.sleep`` call.

If these warnings pile up in the log it's an indicator that the worker is congested.
Make sure there is no use of long sleeps and instead the scheduler is used.

If this warning only appears now and then it can be ignored.

Errors
======================================
Expand Down
6 changes: 3 additions & 3 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ ignore_missing_imports = True
[mypy-aiohttp_sse_client]
ignore_missing_imports = True

[mypy-EasyCo]
ignore_missing_imports = True

[mypy-ruamel]
ignore_missing_imports = True

Expand Down Expand Up @@ -48,6 +45,9 @@ ignore_missing_imports = True
[mypy-watchdog.observers]
ignore_missing_imports = True

[mypy-stack_data]
ignore_missing_imports = True

#------------------------------------------------------------------------------
# Test libraries
#------------------------------------------------------------------------------
Expand Down
21 changes: 19 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import datetime
import random

import HABApp
from HABApp.core.events import ValueUpdateEvent, ValueChangeEventFilter
from HABApp.mqtt.items import MqttItem
from HABApp.core.events import ValueChangeEvent, ValueChangeEventFilter, ValueUpdateEvent, ValueUpdateEventFilter


class ExampleMqttTestRule(HABApp.Rule):
Expand All @@ -44,7 +45,13 @@ class ExampleMqttTestRule(HABApp.Rule):
callback=self.publish_rand_value
)

self.listen_event('test/test', self.topic_updated, ValueChangeEventFilter())
# this will trigger every time a message is received under "test/test"
self.listen_event('test/test', self.topic_updated, ValueUpdateEventFilter())

# This will create an item which will store the payload of the topic so it can be accessed later.
self.item = MqttItem.get_create_item('test/value_stored')
# Since the payload is now stored we can trigger only if the value has changed
self.item.listen_event(self.item_topic_updated, ValueChangeEventFilter())

def publish_rand_value(self):
print('test mqtt_publish')
Expand All @@ -54,6 +61,10 @@ class ExampleMqttTestRule(HABApp.Rule):
assert isinstance(event, ValueUpdateEvent), type(event)
print( f'mqtt topic "test/test" updated to {event.value}')

def item_topic_updated(self, event: ValueChangeEvent):
print(self.item.value) # will output the current item value
print( f'mqtt topic "test/value_stored" changed from {event.old_value} to {event.value}')


ExampleMqttTestRule()
```
Expand Down Expand Up @@ -106,6 +117,12 @@ MyOpenhabRule()
```

# Changelog
#### 1.0.3 (09.08.2022)
- OpenHAB Thing can now be enabled/disabled with ``thing.set_enabled()``
- ClientID for MQTT should now be unique for every HABApp installation
- Reworked MultiModeItem, now a default value is possible when no mode is active
- Added some type hints and updated documentation

#### 1.0.2 (29.07.2022)
- Fixed setup issues
- Fixed unnecessary long tracebacks
Expand Down
2 changes: 1 addition & 1 deletion requirements_setup.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ujson >= 5.4, < 5.5
paho-mqtt >= 1.6, < 1.7

immutables == 0.18
eascheduler == 0.1.6
eascheduler == 0.1.7
easyconfig == 0.2.4
stack_data == 0.3.0

Expand Down
2 changes: 1 addition & 1 deletion requirements_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
# Packages to run source tests
# -----------------------------------------------------------------------------
pytest >= 7.1, < 8
pytest-asyncio >= 0.18.3, < 0.19
pytest-asyncio >= 0.19, < 0.20
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import time
from typing import Callable

from HABAppTests.test_rule._rest_patcher import RestPatcher
from HABAppTests.test_rule.test_case import TestResult, TestResultStatus


class TestCase:
def __init__(self, name: str, func: callable, args=[], kwargs={}):
def __init__(self, name: str, func: Callable, args=[], kwargs={}):
self.name = name
self.func = func
self.args = args
Expand Down
4 changes: 2 additions & 2 deletions run/conf_testing/lib/HABAppTests/test_rule/test_rule.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from typing import Dict
from typing import Dict, Callable
from typing import List

import HABApp
Expand Down Expand Up @@ -48,7 +48,7 @@ def set_up(self):
def tear_down(self):
pass

def add_test(self, name, func: callable, *args, **kwargs):
def add_test(self, name, func: Callable, *args, **kwargs):
tc = TestCase(name, func, args, kwargs)
assert tc.name not in self._tests
self._tests[tc.name] = tc
Expand Down
13 changes: 13 additions & 0 deletions run/conf_testing/rules/openhab/test_things.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
from HABAppTests import TestBaseRule
from HABAppTests.utils import find_astro_sun_thing
from HABApp.openhab.items import Thing


class OpenhabThings(TestBaseRule):

def __init__(self):
super().__init__()
self.add_test('ApiDoc', self.test_api)
self.add_test('Enable(API)', self.test_enabled_api)
self.add_test('Enable(Obj)', self.test_enabled_obj)

def test_api(self):
self.openhab.get_thing(find_astro_sun_thing())

def test_enabled_api(self):
uid = find_astro_sun_thing()
assert self.oh.set_thing_enabled(uid, False) == 200
assert self.oh.set_thing_enabled(uid, True) == 200

def test_enabled_obj(self):
thing = Thing.get_item(find_astro_sun_thing())
assert thing.set_enabled(False) == 200
assert thing.set_enabled(True) == 200


OpenhabThings()
2 changes: 1 addition & 1 deletion src/HABApp/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.0.2'
__version__ = '1.0.3'
2 changes: 1 addition & 1 deletion src/HABApp/config/logging/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def inject_log_buffer(cfg: dict, log: BufferedLogger):
q_handlers: List[HABAppQueueHandler] = []

for handler_name, buffered_handler_name in buffered_handlers.items():
q = SimpleQueue()
q: SimpleQueue = SimpleQueue()
handler_cfg[buffered_handler_name] = {'class': 'logging.handlers.QueueHandler', 'queue': q}

qh = HABAppQueueHandler(q, handler_name, f'LogBuffer{handler_name:s}')
Expand Down
5 changes: 4 additions & 1 deletion src/HABApp/config/models/mqtt.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import random
import string
import sys
from pathlib import Path
from typing import Optional, Tuple
Expand Down Expand Up @@ -25,7 +27,8 @@ class TLSSettings(BaseModel):


class Connection(BaseModel):
client_id: str = 'HABApp'
client_id: str = Field('HABApp-' + ''.join(random.choices(string.ascii_letters, k=13)),
description='ClientId that is used to uniquely identify this client on the mqtt broker.')
host: str = Field('', description='Connect to this host. Empty string ("") disables the connection.')
port: int = 1883
user: str = ''
Expand Down
2 changes: 1 addition & 1 deletion src/HABApp/core/asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def create_task(coro: _Coroutine, name: _Optional[str] = None) -> _Future:


def run_coro_from_thread(coro: _Coroutine[_Any, _Any, _CORO_RET], calling: _Callable) -> _CORO_RET:
# This function call is blocking so it can't be called in the async context
# This function call is blocking, so it can't be called in the async context
if async_context.get(None) is not None:
raise AsyncContextError(calling)

Expand Down
7 changes: 2 additions & 5 deletions src/HABApp/core/const/hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@
from typing import Callable as __Callable
from typing import Type as __Type

from .const import PYTHON_310 as __IS_GT_PYTHON_310
from .const import PYTHON_310 as __IS_GE_PYTHON_310

if __IS_GT_PYTHON_310:
if __IS_GE_PYTHON_310:
from typing import TypeAlias
else:
from typing import Final as TypeAlias


HINT_ANY_CLASS: TypeAlias = __Type[object]
HINT_FUNC_ASYNC: TypeAlias = __Callable[..., __Awaitable[__Any]]

HINT_EVENT_CALLBACK: TypeAlias = __Callable[[__Any], __Any]

HINT_SCHEDULER_CALLBACK: TypeAlias = __Callable[[], __Any]
8 changes: 4 additions & 4 deletions src/HABApp/core/files/folders/folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ def add_folder(prefix: str, folder: Path, priority: int) -> ConfiguredFolder:


def get_name(path: Path) -> str:
path = path.as_posix()
path_str = path.as_posix()
for prefix, cfg in sorted(FOLDERS.items(), key=lambda x: len(x[0]), reverse=True):
folder = cfg.folder.as_posix()
if path.startswith(folder):
return prefix + path[len(folder) + 1:]
if path_str.startswith(folder):
return prefix + path_str[len(folder) + 1:]

raise ValueError(f'Path "{path}" is not part of the configured folders!')
raise ValueError(f'Path "{path_str}" is not part of the configured folders!')


def get_path(name: str) -> Path:
Expand Down
4 changes: 2 additions & 2 deletions src/HABApp/core/internals/context/context.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Set, Optional
from typing import Set, Optional, Callable
from typing import TypeVar

from HABApp.core.errors import ContextBoundObjectIsAlreadyLinkedError, ContextBoundObjectIsAlreadyUnlinkedError
Expand Down Expand Up @@ -47,7 +47,7 @@ def link(self, obj: HINT_CONTEXT_BOUND_OBJ) -> HINT_CONTEXT_BOUND_OBJ:
obj._ctx_link(self)
return obj

def get_callback_name(self, callback: callable) -> Optional[str]:
def get_callback_name(self, callback: Callable) -> Optional[str]:
raise NotImplementedError()


Expand Down
Loading

0 comments on commit decea19

Please sign in to comment.