diff --git a/HABApp/__version__.py b/HABApp/__version__.py index 5e6db2a2..90fdbaa9 100644 --- a/HABApp/__version__.py +++ b/HABApp/__version__.py @@ -1 +1 @@ -__version__ = '0.15.4' +__version__ = '0.16.0' diff --git a/HABApp/openhab/connection_handler/func_async.py b/HABApp/openhab/connection_handler/func_async.py index 1c062850..1e0aa006 100644 --- a/HABApp/openhab/connection_handler/func_async.py +++ b/HABApp/openhab/connection_handler/func_async.py @@ -10,7 +10,12 @@ 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 +from .http_connection import delete, get, post, put, log, async_get_root, async_get_uuid + + +if typing.TYPE_CHECKING: + async_get_root = async_get_root + async_get_uuid = async_get_uuid def convert_to_oh_type(_in: Any) -> str: @@ -19,8 +24,6 @@ def convert_to_oh_type(_in: Any) -> str: # 2018-11-19T09:47:38.284000+0100 -> 2018-11-19T09:47:38.284+0100 out = _in.astimezone(None).strftime('%Y-%m-%dT%H:%M:%S.%f%z') return f'{out[:-8]}{out[-5:]}' - # elif isinstance(_in, HABApp.openhab.items.ColorItem): - # return f'{_in.hue:.1f},{_in.saturation:.1f},{_in.value:.1f}' elif isinstance(_in, BaseValueItem): return str(_in.value) elif isinstance(_in, (set, list, tuple, frozenset)): diff --git a/HABApp/openhab/connection_handler/http_connection.py b/HABApp/openhab/connection_handler/http_connection.py index 008d38a1..2223f61a 100644 --- a/HABApp/openhab/connection_handler/http_connection.py +++ b/HABApp/openhab/connection_handler/http_connection.py @@ -24,6 +24,8 @@ IS_ONLINE = False IS_READ_ONLY = False +IS_OH2 = False + HTTP_PREFIX: Optional[str] = None # HTTP options @@ -221,6 +223,7 @@ async def start_connection(): HABApp.CONFIG.openhab.connection.password ) + # todo: add possibility to configure line size with read_bufsize HTTP_SESSION = aiohttp.ClientSession( timeout=aiohttp.ClientTimeout(total=None), json_serialize=dump_json, @@ -235,14 +238,20 @@ async def start_sse_event_listener(): # cache so we don't have to look up every event call = ON_SSE_EVENT + options = {} + if HABApp.CONFIG.openhab.connection.user or HABApp.CONFIG.openhab.connection.password: + options['with_credentials'] = True + + event_prefix = 'openhab' if not IS_OH2 else 'smarthome' + async with sse_client.EventSource( - url= - f'{HTTP_PREFIX}/rest/' - "events?topics=smarthome/items/," # Item updates - "smarthome/channels/," # Channel update - "smarthome/things/*/status," # Thing status updates - "smarthome/things/*/statuschanged" # Thing status changes + url=f'{HTTP_PREFIX}/rest/events?topics=' + f'{event_prefix}/items/,' # Item updates + f'{event_prefix}/channels/,' # Channel update + f'{event_prefix}/things/*/status,' # Thing status updates + f'{event_prefix}/things/*/statuschanged' # Thing status changes , + option=options, session=HTTP_SESSION ) as event_source: async for event in event_source: @@ -283,8 +292,26 @@ async def async_get_uuid() -> str: return await resp.text(encoding='utf-8') +async def async_get_root() -> dict: + resp = await get('', log_404=False) + if resp.status == 404: + return {} + return await resp.json(encoding='utf-8') + + +def patch_for_oh2(reverse=False): + global IS_OH2 + + IS_OH2 = True + + # events are named different + HABApp.openhab.events.item_events.NAME_START = 16 if not reverse else 14 + HABApp.openhab.events.thing_events.NAME_START = 17 if not reverse else 15 + HABApp.openhab.events.channel_events.NAME_START = 19 if not reverse else 17 + + async def try_uuid(): - global FUT_UUID, IS_ONLINE, FUT_SSE + global FUT_UUID, FUT_SSE, IS_ONLINE # sleep before reconnect await CONNECT_WAIT.wait() @@ -292,6 +319,7 @@ async def try_uuid(): log.debug('Trying to connect to OpenHAB ...') try: uuid = await async_get_uuid() + root = await async_get_root() # this will only work on OH3 except Exception as e: if isinstance(e, (OpenhabDisconnectedError, OpenhabNotReadyYet)): log.info('... offline!') @@ -306,6 +334,12 @@ async def try_uuid(): else: log.info(f'Connected to OpenHAB instance {uuid}') + info = root.get('runtimeInfo') + if info is None: + patch_for_oh2() + else: + log.info(f'OpenHAB version {info["version"]} ({info["buildString"]})') + IS_ONLINE = True # start sse processing diff --git a/HABApp/openhab/connection_logic/plugin_things/items_file.py b/HABApp/openhab/connection_logic/plugin_things/items_file.py index 8931300c..67e6742d 100644 --- a/HABApp/openhab/connection_logic/plugin_things/items_file.py +++ b/HABApp/openhab/connection_logic/plugin_things/items_file.py @@ -97,7 +97,7 @@ def create_items_file(path: Path, items_dict: Dict[str, UserItem]): } grouped_items = {None: []} - for _name, _item in sorted(items_dict.items()): + for _name, _item in items_dict.items(): m = RE_GROUP_NAMES.match(_name) grp = grouped_items.setdefault(m.group(1) if m is not None else None, []) grp.append(_get_item_val_dict(field_fmt, _item)) @@ -105,7 +105,7 @@ def create_items_file(path: Path, items_dict: Dict[str, UserItem]): # aggregate single entry items to a block _aggr = [] for _name, _items in grouped_items.items(): - if len(_items) <= 1: + if len(_items) <= 1 and _name is not None: _aggr.append(_name) for _name in _aggr: grouped_items[None].extend(grouped_items[_name]) diff --git a/HABApp/openhab/events/channel_events.py b/HABApp/openhab/events/channel_events.py index 02305fb5..fcfed52e 100644 --- a/HABApp/openhab/events/channel_events.py +++ b/HABApp/openhab/events/channel_events.py @@ -1,5 +1,10 @@ from .base_event import OpenhabEvent +# smarthome/channels/NAME/triggered -> 19 +# openhab/channels/NAME/triggered -> 17 +# todo: revert this once we go OH3 only +NAME_START: int = 17 + class ChannelTriggeredEvent(OpenhabEvent): def __init__(self, name: str = '', event: str = '', channel: str = ''): @@ -11,7 +16,7 @@ def __init__(self, name: str = '', event: str = '', channel: str = ''): @classmethod def from_dict(cls, topic: str, payload: dict): - return cls(topic[19:-10], payload['event'], payload['channel']) + return cls(topic[NAME_START:-10], payload['event'], payload['channel']) def __repr__(self): return f'<{self.__class__.__name__} name: {self.name}, event: {self.event}>' diff --git a/HABApp/openhab/events/item_events.py b/HABApp/openhab/events/item_events.py index 2f45e273..8d7ee918 100644 --- a/HABApp/openhab/events/item_events.py +++ b/HABApp/openhab/events/item_events.py @@ -4,6 +4,11 @@ from ..map_values import map_openhab_values from .base_event import OpenhabEvent +# smarthome/items/NAME/state -> 16 +# openhab/items/NAME/state -> 14 +# todo: revert this once we go OH3 only +NAME_START: int = 14 + class ItemStateEvent(OpenhabEvent, HABApp.core.events.ValueUpdateEvent): def __init__(self, name: str = '', value: typing.Any = None): @@ -16,7 +21,7 @@ def __init__(self, name: str = '', value: typing.Any = None): @classmethod def from_dict(cls, topic: str, payload: dict): # smarthome/items/NAME/state - return cls(topic[16:-6], map_openhab_values(payload['type'], payload['value'])) + return cls(topic[NAME_START:-6], map_openhab_values(payload['type'], payload['value'])) def __repr__(self): return f'<{self.__class__.__name__} name: {self.name}, value: {self.value}>' @@ -34,7 +39,7 @@ def __init__(self, name: str = '', value: typing.Any = None, old_value: typing.A def from_dict(cls, topic: str, payload: dict): # smarthome/items/Ping/statechanged return cls( - topic[16:-13], + topic[NAME_START:-13], map_openhab_values(payload['type'], payload['value']), map_openhab_values(payload['oldType'], payload['oldValue']) ) @@ -53,7 +58,7 @@ def __init__(self, name: str = '', value: typing.Any = None): @classmethod def from_dict(cls, topic: str, payload: dict): # smarthome/items/NAME/command - return cls(topic[16:-8], map_openhab_values(payload['type'], payload['value'])) + return cls(topic[NAME_START:-8], map_openhab_values(payload['type'], payload['value'])) def __repr__(self): return f'<{self.__class__.__name__} name: {self.name}, value: {self.value}>' @@ -90,7 +95,7 @@ def from_dict(cls, topic: str, payload: dict): # 'payload': '[{"type":"Switch","name":"Test","tags":[],"groupNames":[]}, # {"type":"Contact","name":"Test","tags":[],"groupNames":[]}]', # 'type': 'ItemUpdatedEvent' - return cls(topic[16:-8], payload[0]['type']) + return cls(topic[NAME_START:-8], payload[0]['type']) def __repr__(self): return f'<{self.__class__.__name__} name: {self.name}, type: {self.type}>' @@ -105,7 +110,7 @@ def __init__(self, name: str = ''): @classmethod def from_dict(cls, topic: str, payload: dict): # smarthome/items/Test/removed - return cls(topic[16:-8]) + return cls(topic[NAME_START:-8]) def __repr__(self): return f'<{self.__class__.__name__} name: {self.name}>' @@ -122,7 +127,7 @@ def __init__(self, name: str = '', value: typing.Any = None): @classmethod def from_dict(cls, topic: str, payload: dict): # 'smarthome/items/NAME/statepredicted' - return cls(topic[16:-15], map_openhab_values(payload['predictedType'], payload['predictedValue'])) + return cls(topic[NAME_START:-15], map_openhab_values(payload['predictedType'], payload['predictedValue'])) def __repr__(self): return f'<{self.__class__.__name__} name: {self.name}, value: {self.value}>' diff --git a/HABApp/openhab/events/thing_events.py b/HABApp/openhab/events/thing_events.py index e0df4227..3c91a88f 100644 --- a/HABApp/openhab/events/thing_events.py +++ b/HABApp/openhab/events/thing_events.py @@ -2,6 +2,11 @@ from .base_event import OpenhabEvent +# smarthome/things/NAME/state -> 17 +# openhab/things/NAME/state -> 15 +# todo: revert this once we go OH3 only +NAME_START: int = 15 + class ThingStatusInfoEvent(OpenhabEvent): def __init__(self, name: str = '', status: str = '', detail: str = ''): @@ -14,7 +19,7 @@ def __init__(self, name: str = '', status: str = '', detail: str = ''): @classmethod def from_dict(cls, topic: str, payload: dict): # smarthome/things/chromecast:chromecast:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/status - return cls(name=topic[17:-7], status=payload['status'], detail=payload['statusDetail']) + return cls(name=topic[NAME_START:-7], status=payload['status'], detail=payload['statusDetail']) def __repr__(self): return f'<{self.__class__.__name__} name: {self.name}, status: {self.status}, detail: {self.detail}>' @@ -33,7 +38,7 @@ def __init__(self, name: str = '', status: str = '', detail: str = '', old_statu @classmethod def from_dict(cls, topic: str, payload: dict): # smarthome/things/chromecast:chromecast:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/statuschanged - name = topic[17:-14] + name = topic[NAME_START:-14] new, old = payload return cls( name=name, status=new['status'], detail=new['statusDetail'], @@ -56,7 +61,7 @@ def __init__(self, name: str = '', messages: typing.List[typing.Dict[str, str]] @classmethod def from_dict(cls, topic: str, payload: dict): # 'smarthome/things/zwave:device:controller:my_node/config/status' - return cls(name=topic[17:-14], messages=payload['configStatusMessages']) + return cls(name=topic[NAME_START:-14], messages=payload['configStatusMessages']) def __repr__(self): return f'<{self.__class__.__name__} name: {self.name}, messages: {self.messages}>' diff --git a/conf_testing/lib/HABAppTests/openhab_tmp_item.py b/conf_testing/lib/HABAppTests/openhab_tmp_item.py index eda108ed..20636291 100644 --- a/conf_testing/lib/HABAppTests/openhab_tmp_item.py +++ b/conf_testing/lib/HABAppTests/openhab_tmp_item.py @@ -12,7 +12,7 @@ def __init__(self, item_name, item_type): if self.item_name is None: self.item_name = get_random_name() - def __enter__(self) -> HABApp.core.items.Item: + def __enter__(self) -> HABApp.openhab.items.OpenhabItem: interface = HABApp.openhab.interface if not interface.item_exists(self.item_name): diff --git a/conf_testing/rules/test_utils.py b/conf_testing/rules/test_utils.py index 01d901a0..9f1d354e 100644 --- a/conf_testing/rules/test_utils.py +++ b/conf_testing/rules/test_utils.py @@ -32,6 +32,7 @@ def test_sw_mode(self): switch.off() waiter.wait_for_state('OFF') + assert switch.is_off() assert mode.enabled is False, mode.enabled mode.set_value('asdf') diff --git a/requirements.txt b/requirements.txt index 4a29b96f..7daa7166 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -aiohttp-sse-client==0.1.7 -aiohttp==3.6.2 +aiohttp-sse-client==0.2.0 +aiohttp==3.7.1 astral==2.2 bidict==0.21.2 easyco==0.2.2 @@ -10,9 +10,7 @@ stackprinter==0.2.4 tzlocal==2.1 voluptuous==0.12.0 watchdog==0.10.3 - -# This is optional -ujson==3.2.0 +ujson==4.0.1 # Backports dataclasses==0.7;python_version<"3.7" diff --git a/tests/test_openhab/test_events/test_from_dict.py b/tests/test_openhab/test_events/test_from_dict.py index 5b02e19a..9fc61408 100644 --- a/tests/test_openhab/test_events/test_from_dict.py +++ b/tests/test_openhab/test_events/test_from_dict.py @@ -7,7 +7,7 @@ def test_ItemStateEvent(): - event = get_event({'topic': 'smarthome/items/Ping/state', 'payload': '{"type":"String","value":"1"}', + event = get_event({'topic': 'openhab/items/Ping/state', 'payload': '{"type":"String","value":"1"}', 'type': 'ItemStateEvent'}) assert isinstance(event, ItemStateEvent) assert event.name == 'Ping' @@ -15,7 +15,7 @@ def test_ItemStateEvent(): def test_ItemCommandEvent(): - event = get_event({'topic': 'smarthome/items/Ping/command', 'payload': '{"type":"String","value":"1"}', + event = get_event({'topic': 'openhab/items/Ping/command', 'payload': '{"type":"String","value":"1"}', 'type': 'ItemCommandEvent'}) assert isinstance(event, ItemCommandEvent) assert event.name == 'Ping' @@ -23,7 +23,7 @@ def test_ItemCommandEvent(): def test_ItemAddedEvent1(): - event = get_event({'topic': 'smarthome/items/TestString/added', + event = get_event({'topic': 'openhab/items/TestString/added', 'payload': '{"type":"String","name":"TestString","tags":[],"groupNames":["TestGroup"]}', 'type': 'ItemAddedEvent'}) assert isinstance(event, ItemAddedEvent) @@ -33,7 +33,7 @@ def test_ItemAddedEvent1(): def test_ItemAddedEvent2(): event = get_event({ - 'topic': 'smarthome/items/TestColor_OFF/added', + 'topic': 'openhab/items/TestColor_OFF/added', 'payload': '{"type":"Color","name":"TestColor_OFF","tags":[],"groupNames":["TestGroup"]}', 'type': 'ItemAddedEvent' }) @@ -44,7 +44,7 @@ def test_ItemAddedEvent2(): def test_ItemUpdatedEvent(): event = get_event({ - 'topic': 'smarthome/items/NameUpdated/updated', + 'topic': 'openhab/items/NameUpdated/updated', 'payload': '[{"type":"Switch","name":"Test","tags":[],"groupNames":[]},' '{"type":"Contact","name":"Test","tags":[],"groupNames":[]}]', 'type': 'ItemUpdatedEvent' @@ -55,7 +55,7 @@ def test_ItemUpdatedEvent(): def test_ItemStateChangedEvent1(): - event = get_event({'topic': 'smarthome/items/Ping/statechanged', + event = get_event({'topic': 'openhab/items/Ping/statechanged', 'payload': '{"type":"String","value":"1","oldType":"UnDef","oldValue":"NULL"}', 'type': 'ItemStateChangedEvent'}) assert isinstance(event, ItemStateChangedEvent) @@ -65,7 +65,7 @@ def test_ItemStateChangedEvent1(): def test_ItemStatePredictedEvent(): - event = get_event({'topic': 'smarthome/items/Buero_Lampe_Vorne_W/statepredicted', + event = get_event({'topic': 'openhab/items/Buero_Lampe_Vorne_W/statepredicted', 'payload': '{"predictedType":"Percent","predictedValue":"10","isConfirmation":false}', 'type': 'ItemStatePredictedEvent'}) assert isinstance(event, ItemStatePredictedEvent) @@ -77,7 +77,7 @@ def test_ItemStateChangedEvent2(): UTC_OFFSET = datetime.datetime.now().astimezone(None).strftime('%z') _in = { - 'topic': 'smarthome/items/TestDateTimeTOGGLE/statechanged', + 'topic': 'openhab/items/TestDateTimeTOGGLE/statechanged', 'payload': f'{{"type":"DateTime","value":"2018-06-21T19:47:08.000{UTC_OFFSET}",' f'"oldType":"DateTime","oldValue":"2017-10-20T17:46:07.000{UTC_OFFSET}"}}', 'type': 'ItemStateChangedEvent'} @@ -91,7 +91,7 @@ def test_ItemStateChangedEvent2(): def test_GroupItemStateChangedEvent(): d = { - 'topic': 'smarthome/items/TestGroupAVG/TestNumber1/statechanged', + 'topic': 'openhab/items/TestGroupAVG/TestNumber1/statechanged', 'payload': '{"type":"Decimal","value":"16","oldType":"Decimal","oldValue":"15"}', 'type': 'GroupItemStateChangedEvent' } @@ -105,7 +105,7 @@ def test_GroupItemStateChangedEvent(): def test_ChannelTriggeredEvent(): d = { - "topic": "smarthome/channels/mihome:sensor_switch:00000000000000:button/triggered", + "topic": "openhab/channels/mihome:sensor_switch:00000000000000:button/triggered", "payload": "{\"event\":\"SHORT_PRESSED\",\"channel\":\"mihome:sensor_switch:11111111111111:button\"}", "type": "ChannelTriggeredEvent" } @@ -119,7 +119,7 @@ def test_ChannelTriggeredEvent(): def test_thing_info_events(): data = { - 'topic': 'smarthome/things/samsungtv:tv:mysamsungtv/status', + 'topic': 'openhab/things/samsungtv:tv:mysamsungtv/status', 'payload': '{"status":"ONLINE","statusDetail":"MyStatusDetail"}', 'type': 'ThingStatusInfoEvent' } @@ -130,7 +130,7 @@ def test_thing_info_events(): assert event.detail == 'MyStatusDetail' data = { - 'topic': 'smarthome/things/chromecast:chromecast:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/status', + 'topic': 'openhab/things/chromecast:chromecast:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/status', 'payload': '{"status":"ONLINE","statusDetail":"NONE"}', 'type': 'ThingStatusInfoEvent' } @@ -143,7 +143,7 @@ def test_thing_info_events(): def test_thing_info_changed_events(): data = { - 'topic': 'smarthome/things/samsungtv:tv:mysamsungtv/statuschanged', + 'topic': 'openhab/things/samsungtv:tv:mysamsungtv/statuschanged', 'payload': '[{"status":"OFFLINE","statusDetail":"NONE"},{"status":"ONLINE","statusDetail":"NONE"}]', 'type': 'ThingStatusInfoChangedEvent' } @@ -158,7 +158,7 @@ def test_thing_info_changed_events(): def test_thing_ConfigStatusInfoEvent(): data = { - 'topic': 'smarthome/things/zwave:device:controller:my_node/config/status', + 'topic': 'openhab/things/zwave:device:controller:my_node/config/status', 'payload': '{"configStatusMessages":[{"parameterName":"switchall_mode","type":"PENDING"}]}', 'type': 'ConfigStatusInfoEvent' } diff --git a/tests/test_openhab/test_events/test_from_dict_oh2.py b/tests/test_openhab/test_events/test_from_dict_oh2.py new file mode 100644 index 00000000..fc3f6954 --- /dev/null +++ b/tests/test_openhab/test_events/test_from_dict_oh2.py @@ -0,0 +1,177 @@ +import datetime +import pytest + +from HABApp.openhab.connection_handler.http_connection import patch_for_oh2 +from HABApp.openhab.events import ChannelTriggeredEvent, GroupItemStateChangedEvent, ItemAddedEvent, ItemCommandEvent, \ + ItemStateChangedEvent, ItemStateEvent, ItemStatePredictedEvent, ItemUpdatedEvent, ThingConfigStatusInfoEvent, \ + ThingStatusInfoChangedEvent, ThingStatusInfoEvent +from HABApp.openhab.map_events import get_event + + +@pytest.fixture +def oh2_event(): + patch_for_oh2() + yield None + patch_for_oh2(reverse=True) + + +def test_ItemStateEvent(oh2_event): + event = get_event({'topic': 'smarthome/items/Ping/state', 'payload': '{"type":"String","value":"1"}', + 'type': 'ItemStateEvent'}) + assert isinstance(event, ItemStateEvent) + assert event.name == 'Ping' + assert event.value == '1' + + +def test_ItemCommandEvent(oh2_event): + event = get_event({'topic': 'smarthome/items/Ping/command', 'payload': '{"type":"String","value":"1"}', + 'type': 'ItemCommandEvent'}) + assert isinstance(event, ItemCommandEvent) + assert event.name == 'Ping' + assert event.value == '1' + + +def test_ItemAddedEvent1(oh2_event): + event = get_event({'topic': 'smarthome/items/TestString/added', + 'payload': '{"type":"String","name":"TestString","tags":[],"groupNames":["TestGroup"]}', + 'type': 'ItemAddedEvent'}) + assert isinstance(event, ItemAddedEvent) + assert event.name == 'TestString' + assert event.type == 'String' + + +def test_ItemAddedEvent2(oh2_event): + event = get_event({ + 'topic': 'smarthome/items/TestColor_OFF/added', + 'payload': '{"type":"Color","name":"TestColor_OFF","tags":[],"groupNames":["TestGroup"]}', + 'type': 'ItemAddedEvent' + }) + assert isinstance(event, ItemAddedEvent) + assert event.name == 'TestColor_OFF' + assert event.type == 'Color' + + +def test_ItemUpdatedEvent(oh2_event): + event = get_event({ + 'topic': 'smarthome/items/NameUpdated/updated', + 'payload': '[{"type":"Switch","name":"Test","tags":[],"groupNames":[]},' + '{"type":"Contact","name":"Test","tags":[],"groupNames":[]}]', + 'type': 'ItemUpdatedEvent' + }) + assert isinstance(event, ItemUpdatedEvent) + assert event.name == 'NameUpdated' + assert event.type == 'Switch' + + +def test_ItemStateChangedEvent1(oh2_event): + event = get_event({'topic': 'smarthome/items/Ping/statechanged', + 'payload': '{"type":"String","value":"1","oldType":"UnDef","oldValue":"NULL"}', + 'type': 'ItemStateChangedEvent'}) + assert isinstance(event, ItemStateChangedEvent) + assert event.name == 'Ping' + assert event.value == '1' + assert event.old_value is None + + +def test_ItemStatePredictedEvent(oh2_event): + event = get_event({'topic': 'smarthome/items/Buero_Lampe_Vorne_W/statepredicted', + 'payload': '{"predictedType":"Percent","predictedValue":"10","isConfirmation":false}', + 'type': 'ItemStatePredictedEvent'}) + assert isinstance(event, ItemStatePredictedEvent) + assert event.name == 'Buero_Lampe_Vorne_W' + assert event.value.value == 10.0 + + +def test_ItemStateChangedEvent2(oh2_event): + UTC_OFFSET = datetime.datetime.now().astimezone(None).strftime('%z') + + _in = { + 'topic': 'smarthome/items/TestDateTimeTOGGLE/statechanged', + 'payload': f'{{"type":"DateTime","value":"2018-06-21T19:47:08.000{UTC_OFFSET}",' + f'"oldType":"DateTime","oldValue":"2017-10-20T17:46:07.000{UTC_OFFSET}"}}', + 'type': 'ItemStateChangedEvent'} + + event = get_event(_in) + + assert isinstance(event, ItemStateChangedEvent) + assert event.name == 'TestDateTimeTOGGLE' + assert datetime.datetime(2018, 6, 21, 19, 47, 8), event.value + + +def test_GroupItemStateChangedEvent(oh2_event): + d = { + 'topic': 'smarthome/items/TestGroupAVG/TestNumber1/statechanged', + 'payload': '{"type":"Decimal","value":"16","oldType":"Decimal","oldValue":"15"}', + 'type': 'GroupItemStateChangedEvent' + } + event = get_event(d) + assert isinstance(event, GroupItemStateChangedEvent) + assert event.name == 'TestGroupAVG' + assert event.item == 'TestNumber1' + assert event.value == 16 + assert event.old_value == 15 + + +def test_ChannelTriggeredEvent(oh2_event): + d = { + "topic": "smarthome/channels/mihome:sensor_switch:00000000000000:button/triggered", + "payload": "{\"event\":\"SHORT_PRESSED\",\"channel\":\"mihome:sensor_switch:11111111111111:button\"}", + "type": "ChannelTriggeredEvent" + } + + event = get_event(d) + assert isinstance(event, ChannelTriggeredEvent) + assert event.name == 'mihome:sensor_switch:00000000000000:button' + assert event.channel == 'mihome:sensor_switch:11111111111111:button' + assert event.event == 'SHORT_PRESSED' + + +def test_thing_info_events(oh2_event): + data = { + 'topic': 'smarthome/things/samsungtv:tv:mysamsungtv/status', + 'payload': '{"status":"ONLINE","statusDetail":"MyStatusDetail"}', + 'type': 'ThingStatusInfoEvent' + } + event = get_event(data) + assert isinstance(event, ThingStatusInfoEvent) + assert event.name == 'samsungtv:tv:mysamsungtv' + assert event.status == 'ONLINE' + assert event.detail == 'MyStatusDetail' + + data = { + 'topic': 'smarthome/things/chromecast:chromecast:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/status', + 'payload': '{"status":"ONLINE","statusDetail":"NONE"}', + 'type': 'ThingStatusInfoEvent' + } + event = get_event(data) + assert isinstance(event, ThingStatusInfoEvent) + assert event.name == 'chromecast:chromecast:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + assert event.status == 'ONLINE' + assert event.detail is None + + +def test_thing_info_changed_events(oh2_event): + data = { + 'topic': 'smarthome/things/samsungtv:tv:mysamsungtv/statuschanged', + 'payload': '[{"status":"OFFLINE","statusDetail":"NONE"},{"status":"ONLINE","statusDetail":"NONE"}]', + 'type': 'ThingStatusInfoChangedEvent' + } + event = get_event(data) + assert isinstance(event, ThingStatusInfoChangedEvent) + assert event.name == 'samsungtv:tv:mysamsungtv' + assert event.status == 'OFFLINE' + assert event.detail is None + assert event.old_status == 'ONLINE' + assert event.old_detail is None + + +def test_thing_ConfigStatusInfoEvent(oh2_event): + data = { + 'topic': 'smarthome/things/zwave:device:controller:my_node/config/status', + 'payload': '{"configStatusMessages":[{"parameterName":"switchall_mode","type":"PENDING"}]}', + 'type': 'ConfigStatusInfoEvent' + } + event = get_event(data) + assert isinstance(event, ThingConfigStatusInfoEvent) + assert event.name == 'zwave:device:controller:my_node' + assert event.messages == [{"parameterName": "switchall_mode", "type": "PENDING"}] diff --git a/tests/test_openhab/test_plugins/test_thing/test_file_writer.py b/tests/test_openhab/test_plugins/test_thing/test_file_writer.py index d6047973..0756b820 100644 --- a/tests/test_openhab/test_plugins/test_thing/test_file_writer.py +++ b/tests/test_openhab/test_plugins/test_thing/test_file_writer.py @@ -28,20 +28,24 @@ def test_creation(tmp_path_factory): UserItem('String', 'NewName', '', '', [], [], '', metadata=meta_auto), UserItem('String', 'NewName1', '', '', [], [], '', metadata=meta_auto), + + UserItem('String', 'SoloName', '', '', [], [], '', {}), ] t = MyStringIO() create_items_file(t, {k.name: k for k in objs}) - print('\n-' + t.text + '-') + print('\n' + '-' * 120 + '\n' + t.text + '-' * 120) + + expected = """String Test_zwave_o_1 {channel = "zwave:link:device" } +String Test_zwave_o_2 {channel = "zwave:link:device1", auto_update="False"} +String Test_zwave_no [tag1] +String Test_zwave_all "label1" (grp1) [tag1, tag2] - expected = """String NewName {auto_update="False"} +String NewName {auto_update="False"} String NewName1 {auto_update="False"} -String Test_zwave_all "label1" (grp1) [tag1, tag2] -String Test_zwave_no [tag1] -String Test_zwave_o_1 {channel = "zwave:link:device" } -String Test_zwave_o_2 {channel = "zwave:link:device1", auto_update="False"} +String SoloName """ assert expected == t.text