diff --git a/docs/index.md b/docs/index.md
index bde3b6c..3c837a6 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -13,7 +13,7 @@ work in progress.
- python :: dateutil
- python :: httpx
- python :: authlib
-- openHAB version 3
+- openHAB version 3 / 4
# Installation
@@ -29,6 +29,9 @@ Example usage of the library:
```python
+import datetime
+import json
+
from openhab import OpenHAB
base_url = 'http://localhost:8080/rest'
@@ -69,6 +72,22 @@ lights_group.on()
# send update to each member
for v in lights_group.members.values():
v.update('OFF')
+
+# start_time for fetching persistence data
+start_time = datetime.datetime.fromtimestamp(1695504300123 / 1000, tz=datetime.UTC)
+
+# fetch persistence data using the OpenHAB client object
+for k in openhab.get_item_persistence(knx_day_night.name,
+ page_length=20,
+ start_time=start_time
+ ):
+ print(json.dumps(k, indent=4))
+
+# fetch persistence data using the item directly
+for k in item.persistence(page_length=20,
+ start_time=start_time
+ ):
+ print(json.dumps(k, indent=4))
```
# Note on NULL and UNDEF
diff --git a/openhab/client.py b/openhab/client.py
index 5af42e1..1a59c7a 100644
--- a/openhab/client.py
+++ b/openhab/client.py
@@ -17,7 +17,7 @@
# along with python-openhab. If not, see .
#
-# pylint: disable=bad-indentation
+import datetime
import logging
import typing
@@ -133,7 +133,7 @@ def _check_req_return(req: httpx.Response) -> None:
if not 200 <= req.status_code < 300:
req.raise_for_status()
- def req_get(self, uri_path: str) -> typing.Any:
+ def req_get(self, uri_path: str, params: typing.Optional[typing.Union[typing.Dict[str, typing.Any], list, tuple]] = None) -> typing.Any:
"""Helper method for initiating a HTTP GET request.
Besides doing the actual request, it also checks the return value and returns the resulting decoded
@@ -145,7 +145,7 @@ def req_get(self, uri_path: str) -> typing.Any:
Returns:
dict: Returns a dict containing the data returned by the OpenHAB REST server.
"""
- r = self.session.get(self.url_rest + uri_path)
+ r = self.session.get(f'{self.url_rest}{uri_path}', params=params)
self._check_req_return(r)
return r.json()
@@ -395,7 +395,7 @@ def create_or_update_item(self,
if function_name is not None:
if function_name not in (
- 'EQUALITY', 'AND', 'OR', 'NAND', 'NOR', 'AVG', 'SUM', 'MAX', 'MIN', 'COUNT', 'LATEST', 'EARLIEST'):
+ 'EQUALITY', 'AND', 'OR', 'NAND', 'NOR', 'AVG', 'SUM', 'MAX', 'MIN', 'COUNT', 'LATEST', 'EARLIEST'):
raise ValueError(f'Invalid function name "{function_name}')
if function_name in ('AND', 'OR', 'NAND', 'NOR') and (not function_params or len(function_params) != 2):
@@ -412,3 +412,55 @@ def create_or_update_item(self,
self.logger.debug('About to create item with PUT request:\n%s', str(paramdict))
self.req_put(f'/items/{name}', json_data=paramdict, headers={'Content-Type': 'application/json'})
+
+ def get_item_persistence(self,
+ name: str,
+ service_id: typing.Optional[str] = None,
+ start_time: typing.Optional[datetime.datetime] = None,
+ end_time: typing.Optional[datetime.datetime] = None,
+ page: int = 0,
+ page_length: int = 0,
+ boundary: bool = False,
+ ) -> typing.Iterator[typing.Dict[str, typing.Union[str, int]]]:
+ """Method for fetching persistence data for a given item.
+
+ Args:
+ name: The item name persistence data should be fetched for.
+ service_id: ID of the persistence service. If not provided the default service will be used.
+ start_time: Start time of the data to return. Will default to 1 day before end_time.
+ end_time: End time of the data to return. Will default to current time.
+ page: Page number of data to return. Defaults to 0 if not provided.
+ page_length: The length of each page. Defaults to 0 which disabled paging.
+ boundary: Gets one value before and after the requested period.
+
+ Returns:
+ Iterator over dict values containing time and state value, e.g.
+ {"time": 1695588900122,
+ "state": "23"
+ }
+ """
+ params: typing.Dict[str, typing.Any] = {'boundary': str(boundary).lower(),
+ 'page': page,
+ 'pagelength': page_length,
+ }
+
+ if service_id is not None:
+ params['serviceId'] = service_id
+
+ if start_time is not None:
+ params['starttime'] = start_time.isoformat()
+
+ if end_time is not None:
+ params['endtime'] = end_time.isoformat()
+
+ if start_time == end_time:
+ raise ValueError('start_time must differ from end_time')
+
+ res = self.req_get(f'/persistence/items/{name}', params=params)
+
+ yield from res['data']
+
+ while page_length > 0 and int(res['datapoints']) > 0:
+ params['page'] += 1
+ res = self.req_get(f'/persistence/items/{name}', params=params)
+ yield from res['data']
diff --git a/openhab/items.py b/openhab/items.py
index 95a7ba9..1aab5ad 100644
--- a/openhab/items.py
+++ b/openhab/items.py
@@ -17,7 +17,6 @@
# along with python-openhab. If not, see .
#
-# pylint: disable=bad-indentation
import datetime
import logging
@@ -206,7 +205,7 @@ def __str__(self) -> str:
"""String representation."""
state = self._state
if self._unitOfMeasure and not isinstance(self._state, tuple):
- state = f'{self._state} {self._unitOfMeasure}'
+ state = f'{self._state} {self._unitOfMeasure}'
return f'<{self.type_} - {self.name} : {state}>'
def _update(self, value: typing.Any) -> None:
@@ -283,6 +282,39 @@ def is_state_undef(self) -> bool:
return False
+ def persistence(self,
+ service_id: typing.Optional[str] = None,
+ start_time: typing.Optional[datetime.datetime] = None,
+ end_time: typing.Optional[datetime.datetime] = None,
+ page: int = 0,
+ page_length: int = 0,
+ boundary: bool = False,
+ ) -> typing.Iterator[typing.Dict[str, typing.Union[str, int]]]:
+ """Method for fetching persistence data for a given item.
+
+ Args:
+ service_id: ID of the persistence service. If not provided the default service will be used.
+ start_time: Start time of the data to return. Will default to 1 day before end_time.
+ end_time: End time of the data to return. Will default to current time.
+ page: Page number of data to return. Defaults to 0 if not provided.
+ page_length: The length of each page. Defaults to 0 which disabled paging.
+ boundary: Gets one value before and after the requested period.
+
+ Returns:
+ Iterator over dict values containing time and state value, e.g.
+ {"time": 1695588900122,
+ "state": "23"
+ }
+ """
+ yield from self.openhab.get_item_persistence(name=self.name,
+ service_id=service_id,
+ start_time=start_time,
+ end_time=end_time,
+ page=page,
+ page_length=page_length,
+ boundary=boundary,
+ )
+
class GroupItem(Item):
"""String item type."""
@@ -476,9 +508,9 @@ def _rest_format(self, value: typing.Union[float, typing.Tuple[float, str], str]
str or bytes: A string or bytes as converted from the value parameter.
"""
if isinstance(value, tuple) and len(value) == 2:
- return super()._rest_format(f'{value[0]:G} {value[1]}')
+ return super()._rest_format(f'{value[0]:G} {value[1]}')
if not isinstance(value, str):
- return super()._rest_format(f'{value:G}')
+ return super()._rest_format(f'{value:G}')
return super()._rest_format(value)
diff --git a/test.py b/test.py
index e25f4f4..2e45813 100644
--- a/test.py
+++ b/test.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
#
# Georges Toth (c) 2016-present
@@ -20,6 +19,8 @@
import datetime
+import json
+
import openhab
base_url = 'http://localhost:8080/rest'
@@ -41,3 +42,19 @@
knx_day_night.off()
print(knx_day_night.state)
+
+# start_time for fetching persistence data
+start_time = datetime.datetime.fromtimestamp(1695504300123 / 1000, tz=datetime.UTC)
+
+# fetch persistence data using the OpenHAB client object
+for k in openhab.get_item_persistence(knx_day_night.name,
+ page_length=20,
+ start_time=start_time
+ ):
+ print(json.dumps(k, indent=4))
+
+# fetch persistence data using the item directly
+for k in knx_day_night.persistence(page_length=20,
+ start_time=start_time
+ ):
+ print(json.dumps(k, indent=4))