Skip to content

Commit

Permalink
0.15.3
Browse files Browse the repository at this point in the history
- Added possibility to set Metadata in ThingConfig
- empty channel configuration will be omitted
- added multiline support for the log_ functions
- Shutdown should not hang any more if not items exist on the oh side 
- Added test for item watch and bugfix
- Fixes for config entries with bitmasks and an entry without bitmask (#168)
  • Loading branch information
spacemanspiff2007 authored Oct 9, 2020
1 parent 20f6bbe commit b9ddb59
Show file tree
Hide file tree
Showing 27 changed files with 560 additions and 106 deletions.
2 changes: 1 addition & 1 deletion HABApp/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.15.2'
__version__ = '0.15.3'
2 changes: 1 addition & 1 deletion HABApp/config/_conf_openhab.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Ping(ConfigContainer):
enabled: bool = ConfigEntry(True, description='If enabled the configured item will show how long it takes to send '
'an update from HABApp and get the updated value back from openhab'
'in milliseconds')
item: str = ConfigEntry('HABApp_Ping', description='Name of the item')
item: str = ConfigEntry('HABApp_Ping', description='Name of the Numberitem')
interval: int = ConfigEntry(10, description='Seconds between two pings')


Expand Down
2 changes: 1 addition & 1 deletion HABApp/core/items/base_item_times.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def add_watch(self, secs: typing.Union[int, float]) -> BaseWatch:

# don't add the watch two times
for t in self.tasks:
if t._fut.secs == secs:
if not t._fut.is_canceled and t._fut.secs == secs:
return t
w = self.WATCH(self.name, secs)
self.tasks.append(w)
Expand Down
20 changes: 17 additions & 3 deletions HABApp/core/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,35 @@


def log_error(logger: logging.Logger, text: str):
logger.error(text)
if '\n' in text:
for line in text.splitlines():
logger.error(line)
else:
logger.error(text)
HABApp.core.EventBus.post_event(
_T_ERRORS, text
)


def log_warning(logger: logging.Logger, text: str):
logger.warning(text)
if '\n' in text:
for line in text.splitlines():
logger.warning(line)
else:
logger.warning(text)

HABApp.core.EventBus.post_event(
_T_WARNINGS, text
)


def log_info(logger: logging.Logger, text: str):
logger.info(text)
if '\n' in text:
for line in text.splitlines():
logger.info(line)
else:
logger.info(text)

HABApp.core.EventBus.post_event(
_T_INFOS, text
)
Expand Down
56 changes: 47 additions & 9 deletions HABApp/openhab/connection_handler/func_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
from HABApp.core.const.json import load_json
from HABApp.core.items import BaseValueItem
from HABApp.openhab.exceptions import OpenhabDisconnectedError, OpenhabNotReadyYet, ThingNotEditableError, \
ThingNotFoundError, ItemNotEditableError, ItemNotFoundError
ThingNotFoundError, ItemNotEditableError, ItemNotFoundError, MetadataNotEditableError
from HABApp.openhab.definitions.rest import ItemChannelLinkDefinition, LinkNotFoundError, OpenhabThingDefinition
from HABApp.openhab.definitions.rest.habapp_data import get_api_vals, load_habapp_meta
from .http_connection import delete, get, post, put, log


Expand Down Expand Up @@ -63,8 +64,8 @@ async def async_get_items(include_habapp_meta=False) -> Optional[List[Dict[str,
return None


async def async_get_item(item: str, include_habapp_meta=False) -> dict:
params = None if not include_habapp_meta else {'metadata': 'HABApp'}
async def async_get_item(item: str, metadata: Optional[str] = None) -> dict:
params = None if metadata is None else {'metadata': metadata}
ret = await get(f'items/{item:s}', params=params, log_404=False)
if ret.status == 404:
raise ItemNotFoundError.from_name(item)
Expand Down Expand Up @@ -162,6 +163,11 @@ async def async_remove_metadata(item: str, namespace: str):
ret = await delete(f'items/{item:s}/metadata/{namespace:s}')
if ret is None:
return False

if ret.status == 404:
raise ItemNotFoundError.from_name(item)
elif ret.status == 405:
raise MetadataNotEditableError.create_text(item, namespace)
return ret.status < 300


Expand All @@ -173,11 +179,12 @@ async def async_set_metadata(item: str, namespace: str, value: str, config: dict
ret = await put(f'items/{item:s}/metadata/{namespace:s}', json=payload)
if ret is None:
return False
return ret.status < 300


async def async_set_habapp_metadata(item: str, config: dict):
return await async_set_metadata(item, 'HABApp', ' ', config)
if ret.status == 404:
raise ItemNotFoundError.from_name(item)
elif ret.status == 405:
raise MetadataNotEditableError.create_text(item, namespace)
return ret.status < 300


async def async_set_thing_cfg(uid: str, cfg: typing.Dict[str, typing.Any]):
Expand Down Expand Up @@ -221,6 +228,14 @@ async def async_get_channel_links() -> List[Dict[str, str]]:
return await ret.json(encoding='utf-8')


async def async_get_channel_link_mode_auto() -> bool:
ret = await get('links/auto')
if ret.status >= 300:
return False
else:
return await ret.json(encoding='utf-8')


async def async_get_channel_link(channel_uid: str, item_name: str) -> ItemChannelLinkDefinition:
ret = await get(__get_link_url(channel_uid, item_name), log_404=False)
if ret.status == 404:
Expand All @@ -236,12 +251,35 @@ async def async_channel_link_exists(channel_uid: str, item_name: str) -> bool:
return ret.status == 200


async def async_create_channel_link(channel_uid: str, item_name: str, configuration: Dict[str, Any] = {}) -> bool:
async def async_create_channel_link(
channel_uid: str, item_name: str, configuration: Optional[Dict[str, Any]] = None) -> bool:

# if the passed item doesn't exist OpenHAB creates a new empty item item
# this is undesired and why we raise an Exception
if not await async_item_exists(item_name):
raise ItemNotFoundError.from_name(item_name)

ret = await put(__get_link_url(channel_uid, item_name), json={'configuration': configuration})
ret = await put(
__get_link_url(channel_uid, item_name),
json={'configuration': configuration} if configuration is not None else {}
)
if ret is None:
return False
return ret.status == 200


# ---------------------------------------------------------------------------------------------------------------------
# Funcs for handling HABApp Metadata
# ---------------------------------------------------------------------------------------------------------------------
async def async_remove_habapp_metadata(item: str):
return await async_remove_metadata(item, 'HABApp')


async def async_set_habapp_metadata(item: str, obj):
val, cfg = get_api_vals(obj)
return await async_set_metadata(item, 'HABApp', val, cfg)


async def async_get_item_with_habapp_meta(item: str) -> dict:
data = await async_get_item(item, metadata='HABApp')
return load_habapp_meta(data)
13 changes: 8 additions & 5 deletions HABApp/openhab/connection_handler/func_sync.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio
import datetime
from typing import Any, Optional, List
from typing import Any, Optional, List, Dict

import HABApp
import HABApp.core
Expand Down Expand Up @@ -108,16 +108,19 @@ def validate(_in):
return fut.result()


def get_item(item_name: str) -> OpenhabItemDefinition:
""" Return the complete OpenHAB item definition
def get_item(item_name: str, metadata: Optional[str] = None) -> OpenhabItemDefinition:
"""Return the complete OpenHAB item definition
:param item_name: name of the item or item
:param metadata: metadata to include (optional)
:return:
"""
if isinstance(item_name, HABApp.openhab.items.base_item.BaseValueItem):
item_name = item_name.name
assert isinstance(item_name, str), type(item_name)
assert metadata is None or isinstance(metadata, str), type(metadata)

fut = asyncio.run_coroutine_threadsafe(async_get_item(item_name), loop)
fut = asyncio.run_coroutine_threadsafe(async_get_item(item_name, metadata=metadata), loop)
data = fut.result()
return OpenhabItemDefinition.parse_obj(data)

Expand Down Expand Up @@ -245,7 +248,7 @@ def get_channel_link(channel_uid: str, item_name: str) -> ItemChannelLinkDefinit
return fut.result()


def create_channel_link(channel_uid: str, item_name: str, configuration: dict = {}) -> bool:
def create_channel_link(channel_uid: str, item_name: str, configuration: Optional[Dict[str, Any]] = None) -> bool:
"""creates a link between a (things) channel and an item
:param channel_uid: uid of the (thing) channel (usually something like AAAA:BBBBB:CCCCC:DDDD:0#SOME_NAME)
Expand Down
30 changes: 29 additions & 1 deletion HABApp/openhab/connection_logic/plugin_things/cfg_validator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import typing
from dataclasses import dataclass
from typing import Dict, List
from typing import Iterator, Optional, Union
Expand All @@ -23,11 +24,12 @@ class UserItem:
groups: List[str]
tags: List[str]
link: Optional[str]
metadata: Dict[str, Dict[str, Union[str, int, float]]]

def get_oh_cfg(self) -> Dict[str, Union[str, dict, list]]:
ret = {}
for k in self.__annotations__:
if k == 'link':
if k in ('link', 'metadata'):
continue

v = self.__dict__[k]
Expand All @@ -41,13 +43,19 @@ class InvalidItemNameError(Exception):
pass


class MetadataCfg(BaseModel):
value: str
config: Dict[str, typing.Any] = {}


class UserItemCfg(BaseModel):
type: str
name: str
label: str = ''
icon: str = ''
groups: List[str] = []
tags: List[str] = []
metadata: Optional[Dict[str, MetadataCfg]] = None

@validator('type', always=True)
def validate_item_type(cls, v):
Expand All @@ -62,16 +70,36 @@ def validate_item_type(cls, v):
def validate_make_str_builder(cls, v):
return StrBuilder(v)

@validator('metadata', pre=True)
def make_meta_cfg(cls, v):
if not isinstance(v, dict):
return v

for key, val in v.items():
if isinstance(val, str):
v[key] = {'value': val}
return v

def get_item(self, context: dict) -> UserItem:
v = {'link': None}
for k in self.__fields__:
val = self.__dict__[k]

# type is const
if k == 'type':
v[k] = val
continue

# metadata is nested
if k == 'metadata':
v[k] = {k: v.dict() for k, v in val.items()} if val is not None else {}
continue

# resolve str wildcards
if k in ('groups', 'tags'):
v[k] = [s.get_str(context) for s in val]
continue

v[k] = val.get_str(context)

# ensure a valid item name, otherwise the creation will definitely fail
Expand Down
Loading

0 comments on commit b9ddb59

Please sign in to comment.