venv added, updated
This commit is contained in:
5
myenv/lib/python3.12/site-packages/openhab/__init__.py
Normal file
5
myenv/lib/python3.12/site-packages/openhab/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Module entry point."""
|
||||
|
||||
from .client import OpenHAB
|
||||
|
||||
__all__ = ['OpenHAB']
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
466
myenv/lib/python3.12/site-packages/openhab/client.py
Normal file
466
myenv/lib/python3.12/site-packages/openhab/client.py
Normal file
@@ -0,0 +1,466 @@
|
||||
"""python library for accessing the openHAB REST API."""
|
||||
|
||||
#
|
||||
# Georges Toth (c) 2016-present <georges@trypill.org>
|
||||
#
|
||||
# python-openhab is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# python-openhab is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with python-openhab. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import typing
|
||||
|
||||
import authlib.integrations.httpx_client
|
||||
import httpx
|
||||
|
||||
import openhab.items
|
||||
import openhab.rules
|
||||
|
||||
from .config import Oauth2Config, Oauth2Token
|
||||
|
||||
__author__ = 'Georges Toth <georges@trypill.org>'
|
||||
__license__ = 'AGPLv3+'
|
||||
|
||||
|
||||
class OpenHAB:
|
||||
"""openHAB REST API client."""
|
||||
|
||||
def __init__(self, base_url: str,
|
||||
username: typing.Optional[str] = None,
|
||||
password: typing.Optional[str] = None,
|
||||
http_auth: typing.Optional[httpx.Auth] = None,
|
||||
timeout: typing.Optional[float] = None,
|
||||
oauth2_config: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
||||
) -> None:
|
||||
"""Class constructor.
|
||||
|
||||
The format of the optional *oauth2_config* dictionary is as follows:
|
||||
```python
|
||||
{"client_id": "http://127.0.0.1/auth",
|
||||
"token_cache": "/<path>/<to>/.oauth2_token",
|
||||
"token":
|
||||
{"access_token": "adsafdasfasfsafasfsafasfasfasfsa....",
|
||||
"expires_in": 3600,
|
||||
"refresh_token": "312e21e21e32112",
|
||||
"scope": "admin",
|
||||
"token_type": "bearer",
|
||||
"user": {
|
||||
"name": "admin",
|
||||
"roles": [
|
||||
"administrator"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Args:
|
||||
base_url (str): The openHAB REST URL, e.g. http://example.com/rest
|
||||
username (str, optional): A optional username, used in conjunction with a optional
|
||||
provided password, in case openHAB requires authentication.
|
||||
password (str, optional): A optional password, used in conjunction with a optional
|
||||
provided username, in case openHAB requires authentication.
|
||||
http_auth (Auth, optional): An alternative to username/password pair, is to
|
||||
specify a custom http authentication object of type :class:`requests.Auth`.
|
||||
timeout (float, optional): An optional timeout for REST transactions
|
||||
oauth2_config: Optional OAuth2 configuration dictionary
|
||||
|
||||
Returns:
|
||||
OpenHAB: openHAB class instance.
|
||||
"""
|
||||
self.url_rest = base_url
|
||||
self.url_base = base_url.rsplit('/', 1)[0]
|
||||
|
||||
self.oauth2_config: typing.Optional[Oauth2Config] = None
|
||||
|
||||
if oauth2_config is not None:
|
||||
self.oauth2_config = Oauth2Config(**oauth2_config)
|
||||
|
||||
self.session = authlib.integrations.httpx_client.OAuth2Client(client_id=self.oauth2_config.client_id,
|
||||
token=self.oauth2_config.token.model_dump(),
|
||||
update_token=self._oauth2_token_updater,
|
||||
)
|
||||
|
||||
self.session.metadata['token_endpoint'] = f'{self.url_rest}/auth/token'
|
||||
|
||||
if not self.oauth2_config.token_cache.is_file():
|
||||
self._oauth2_token_updater(self.oauth2_config.token.model_dump())
|
||||
|
||||
else:
|
||||
self.session = httpx.Client(timeout=timeout)
|
||||
|
||||
if http_auth is not None:
|
||||
self.session.auth = http_auth
|
||||
elif not (username is None or password is None):
|
||||
self.session.auth = httpx.BasicAuth(username, password)
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
self._rules: typing.Optional[openhab.rules.Rules] = None
|
||||
|
||||
@property
|
||||
def rules(self) -> openhab.rules.Rules:
|
||||
"""Get object for managing rules."""
|
||||
if self._rules is None:
|
||||
self._rules = openhab.rules.Rules(self)
|
||||
|
||||
return self._rules
|
||||
|
||||
@staticmethod
|
||||
def _check_req_return(req: httpx.Response) -> None:
|
||||
"""Internal method for checking the return value of a REST HTTP request.
|
||||
|
||||
Args:
|
||||
req (requests.Response): A requests Response object.
|
||||
|
||||
Returns:
|
||||
None: Returns None if no error occurred; else raises an exception.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises a ValueError exception in case of a non-successful
|
||||
REST request.
|
||||
"""
|
||||
if not 200 <= req.status_code < 300:
|
||||
req.raise_for_status()
|
||||
|
||||
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
|
||||
JSON data.
|
||||
|
||||
Args:
|
||||
uri_path (str): The path to be used in the GET request.
|
||||
|
||||
Returns:
|
||||
dict: Returns a dict containing the data returned by the OpenHAB REST server.
|
||||
"""
|
||||
r = self.session.get(f'{self.url_rest}{uri_path}', params=params)
|
||||
self._check_req_return(r)
|
||||
return r.json()
|
||||
|
||||
def req_post(self,
|
||||
uri_path: str,
|
||||
data: typing.Optional[typing.Union[str, bytes, typing.Mapping[str, typing.Any], typing.Iterable[
|
||||
typing.Tuple[str, typing.Optional[str]]]]] = None,
|
||||
) -> None:
|
||||
"""Helper method for initiating a HTTP POST request.
|
||||
|
||||
Besides doing the actual request, it also checks the return value and returns the resulting decoded
|
||||
JSON data.
|
||||
|
||||
Args:
|
||||
uri_path (str): The path to be used in the POST request.
|
||||
data (dict, optional): A optional dict with data to be submitted as part of the POST request.
|
||||
|
||||
Returns:
|
||||
None: No data is returned.
|
||||
"""
|
||||
headers = self.session.headers
|
||||
headers['Content-Type'] = 'text/plain'
|
||||
|
||||
r = self.session.post(self.url_rest + uri_path, content=data, headers=headers)
|
||||
self._check_req_return(r)
|
||||
|
||||
def req_put(self,
|
||||
uri_path: str,
|
||||
data: typing.Optional[dict] = None,
|
||||
json_data: typing.Optional[dict] = None,
|
||||
headers: typing.Optional[dict] = None,
|
||||
) -> None:
|
||||
"""Helper method for initiating a HTTP PUT request.
|
||||
|
||||
Besides doing the actual request, it also checks the return value and returns the resulting decoded
|
||||
JSON data.
|
||||
|
||||
Args:
|
||||
uri_path (str): The path to be used in the PUT request.
|
||||
data (dict, optional): A optional dict with data to be submitted as part of the PUT request.
|
||||
json_data: Data to be submitted as json.
|
||||
headers: Specify optional custom headers.
|
||||
|
||||
Returns:
|
||||
None: No data is returned.
|
||||
"""
|
||||
if headers is None:
|
||||
headers = {'Content-Type': 'text/plain'}
|
||||
content = data
|
||||
data = None
|
||||
else:
|
||||
content = None
|
||||
|
||||
r = self.session.put(self.url_rest + uri_path, content=content, data=data, json=json_data, headers=headers)
|
||||
self._check_req_return(r)
|
||||
|
||||
# fetch all items
|
||||
def fetch_all_items(self) -> typing.Dict[str, openhab.items.Item]:
|
||||
"""Returns all items defined in openHAB.
|
||||
|
||||
Returns:
|
||||
dict: Returns a dict with item names as key and item class instances as value.
|
||||
"""
|
||||
items = {} # type: dict
|
||||
res = self.req_get('/items/')
|
||||
|
||||
for i in res:
|
||||
if i['name'] not in items:
|
||||
items[i['name']] = self.json_to_item(i)
|
||||
|
||||
return items
|
||||
|
||||
def get_item(self, name: str) -> openhab.items.Item:
|
||||
"""Returns an item with its state and type as fetched from openHAB.
|
||||
|
||||
Args:
|
||||
name (str): The name of the item to fetch from openHAB.
|
||||
|
||||
Returns:
|
||||
Item: A corresponding Item class instance with the state of the requested item.
|
||||
"""
|
||||
json_data = self.get_item_raw(name)
|
||||
|
||||
return self.json_to_item(json_data)
|
||||
|
||||
def json_to_item(self, json_data: dict) -> openhab.items.Item:
|
||||
"""This method takes as argument the RAW (JSON decoded) response for an openHAB item.
|
||||
|
||||
It checks of what type the item is and returns a class instance of the
|
||||
specific item filled with the item's state.
|
||||
|
||||
Args:
|
||||
json_data (dict): The JSON decoded data as returned by the openHAB server.
|
||||
|
||||
Returns:
|
||||
Item: A corresponding Item class instance with the state of the item.
|
||||
"""
|
||||
_type = json_data['type']
|
||||
|
||||
if _type == 'Group' and 'groupType' in json_data:
|
||||
_type = json_data['groupType']
|
||||
|
||||
if _type == 'Group' and 'groupType' not in json_data:
|
||||
return openhab.items.GroupItem(self, json_data)
|
||||
|
||||
if _type == 'String':
|
||||
return openhab.items.StringItem(self, json_data)
|
||||
|
||||
if _type == 'Switch':
|
||||
return openhab.items.SwitchItem(self, json_data)
|
||||
|
||||
if _type == 'DateTime':
|
||||
return openhab.items.DateTimeItem(self, json_data)
|
||||
|
||||
if _type == 'Contact':
|
||||
return openhab.items.ContactItem(self, json_data)
|
||||
|
||||
if _type.startswith('Number'):
|
||||
return openhab.items.NumberItem(self, json_data)
|
||||
|
||||
if _type == 'Dimmer':
|
||||
return openhab.items.DimmerItem(self, json_data)
|
||||
|
||||
if _type == 'Color':
|
||||
return openhab.items.ColorItem(self, json_data)
|
||||
|
||||
if _type == 'Rollershutter':
|
||||
return openhab.items.RollershutterItem(self, json_data)
|
||||
|
||||
if _type == 'Player':
|
||||
return openhab.items.PlayerItem(self, json_data)
|
||||
|
||||
return openhab.items.Item(self, json_data)
|
||||
|
||||
def get_item_raw(self, name: str) -> typing.Any:
|
||||
"""Private method for fetching a json configuration of an item.
|
||||
|
||||
Args:
|
||||
name (str): The item name to be fetched.
|
||||
|
||||
Returns:
|
||||
dict: A JSON decoded dict.
|
||||
"""
|
||||
return self.req_get(f'/items/{name}')
|
||||
|
||||
def logout(self) -> bool:
|
||||
"""OAuth2 session logout method.
|
||||
|
||||
Returns:
|
||||
True or False depending on if the logout did succeed.
|
||||
"""
|
||||
if self.oauth2_config is None or not isinstance(self.session, authlib.integrations.httpx_client.OAuth2Client):
|
||||
raise ValueError('You are trying to logout from a non-OAuth2 session. This is not supported!')
|
||||
|
||||
data = {'refresh_token': self.oauth2_config.token.refresh_token,
|
||||
'id': self.oauth2_config.client_id,
|
||||
}
|
||||
url_logout = f'{self.url_rest}/auth/logout'
|
||||
|
||||
res = self.session.post(url_logout, data=data)
|
||||
|
||||
return res.status_code == 200
|
||||
|
||||
def _oauth2_token_updater(self, token: typing.Dict[str, typing.Any],
|
||||
refresh_token: typing.Any = None,
|
||||
access_token: typing.Any = None) -> None:
|
||||
if self.oauth2_config is None:
|
||||
raise ValueError('OAuth2 configuration is not set; invalid action!')
|
||||
|
||||
self.oauth2_config.token = Oauth2Token(**token)
|
||||
|
||||
with self.oauth2_config.token_cache.open('w', encoding='utf-8') as fhdl:
|
||||
fhdl.write(self.oauth2_config.token.model_dump_json())
|
||||
|
||||
def create_or_update_item(self,
|
||||
name: str,
|
||||
_type: typing.Union[str, typing.Type[openhab.items.Item]],
|
||||
quantity_type: typing.Optional[str] = None,
|
||||
label: typing.Optional[str] = None,
|
||||
category: typing.Optional[str] = None,
|
||||
tags: typing.Optional[typing.List[str]] = None,
|
||||
group_names: typing.Optional[typing.List[str]] = None,
|
||||
group_type: typing.Optional[typing.Union[str, typing.Type[openhab.items.Item]]] = None,
|
||||
function_name: typing.Optional[str] = None,
|
||||
function_params: typing.Optional[typing.List[str]] = None,
|
||||
) -> None:
|
||||
"""Creates a new item in openHAB if there is no item with name 'name' yet.
|
||||
|
||||
If there is an item with 'name' already in openHAB, the item gets updated with the infos provided. be aware that not provided fields will be deleted in openHAB.
|
||||
Consider to get the existing item via 'getItem' and then read out existing fields to populate the parameters here.
|
||||
|
||||
Args:
|
||||
name: unique name of the item
|
||||
_type: the data_type used in openHAB (like Group, Number, Contact, DateTime, Rollershutter, Color, Dimmer, Switch, Player)
|
||||
server.
|
||||
To create groups use 'GroupItem'!
|
||||
quantity_type: optional quantity_type ( like Angle, Temperature, Illuminance (see https://www.openhab.org/docs/concepts/units-of-measurement.html))
|
||||
label: optional openHAB label (see https://www.openhab.org/docs/configuration/items.html#label)
|
||||
category: optional category. no documentation found
|
||||
tags: optional list of tags (see https://www.openhab.org/docs/configuration/items.html#tags)
|
||||
group_names: optional list of groups this item belongs to.
|
||||
group_type: Optional group_type (e.g. NumberItem, SwitchItem, etc).
|
||||
function_name: Optional function_name. no documentation found.
|
||||
Can be one of ['EQUALITY', 'AND', 'OR', 'NAND', 'NOR', 'AVG', 'SUM', 'MAX', 'MIN', 'COUNT', 'LATEST', 'EARLIEST']
|
||||
function_params: Optional list of function params (no documentation found), depending on function name.
|
||||
"""
|
||||
paramdict: typing.Dict[
|
||||
str, typing.Union[str, typing.List[str], typing.Dict[str, typing.Union[str, typing.List[str]]]]] = {}
|
||||
|
||||
if isinstance(_type, type):
|
||||
if issubclass(_type, openhab.items.Item):
|
||||
itemtypename = _type.TYPENAME
|
||||
else:
|
||||
raise ValueError(
|
||||
f'_type parameter must be a valid subclass of type *Item* or a string name of such a class; given value is "{str(_type)}"')
|
||||
else:
|
||||
itemtypename = _type
|
||||
|
||||
if quantity_type is None:
|
||||
paramdict['type'] = itemtypename
|
||||
else:
|
||||
paramdict['type'] = f'{itemtypename}:{quantity_type}'
|
||||
|
||||
paramdict['name'] = name
|
||||
|
||||
if label is not None:
|
||||
paramdict['label'] = label
|
||||
|
||||
if category is not None:
|
||||
paramdict['category'] = category
|
||||
|
||||
if tags is not None:
|
||||
paramdict['tags'] = tags
|
||||
|
||||
if group_names is not None:
|
||||
paramdict['groupNames'] = group_names
|
||||
|
||||
if group_type is not None:
|
||||
if isinstance(group_type, type):
|
||||
if issubclass(group_type, openhab.items.Item):
|
||||
paramdict['groupType'] = group_type.TYPENAME
|
||||
else:
|
||||
raise ValueError(
|
||||
f'group_type parameter must be a valid subclass of type *Item* or a string name of such a class; given value is "{str(group_type)}"')
|
||||
else:
|
||||
paramdict['groupType'] = group_type
|
||||
|
||||
if function_name is not None:
|
||||
if function_name not in (
|
||||
'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):
|
||||
raise ValueError(f'Group function "{function_name}" requires two arguments')
|
||||
|
||||
if function_name == 'COUNT' and (not function_params or len(function_params) != 1):
|
||||
raise ValueError(f'Group function "{function_name}" requires one arguments')
|
||||
|
||||
if function_params:
|
||||
paramdict['function'] = {'name': function_name, 'params': function_params}
|
||||
else:
|
||||
paramdict['function'] = {'name': function_name}
|
||||
|
||||
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']
|
||||
591
myenv/lib/python3.12/site-packages/openhab/command_types.py
Normal file
591
myenv/lib/python3.12/site-packages/openhab/command_types.py
Normal file
@@ -0,0 +1,591 @@
|
||||
"""python library for accessing the openHAB REST API."""
|
||||
|
||||
#
|
||||
# Georges Toth (c) 2016-present <georges@trypill.org>
|
||||
#
|
||||
# python-openhab is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# python-openhab is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with python-openhab. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# pylint: disable=bad-indentation
|
||||
|
||||
import abc
|
||||
import datetime
|
||||
import re
|
||||
import typing
|
||||
|
||||
import dateutil.parser
|
||||
|
||||
__author__ = 'Georges Toth <georges@trypill.org>'
|
||||
__license__ = 'AGPLv3+'
|
||||
|
||||
|
||||
class CommandType(metaclass=abc.ABCMeta):
|
||||
"""Base command type class."""
|
||||
|
||||
TYPENAME = ''
|
||||
SUPPORTED_TYPENAMES: typing.List[str] = []
|
||||
UNDEF = 'UNDEF'
|
||||
NULL = 'NULL'
|
||||
UNDEFINED_STATES = [UNDEF, NULL]
|
||||
|
||||
@classmethod
|
||||
def is_undefined(cls, value: typing.Any) -> bool:
|
||||
"""Return true if given value is an undefined value in openHAB (i.e. UNDEF/NULL)."""
|
||||
return value in CommandType.UNDEFINED_STATES
|
||||
|
||||
@classmethod
|
||||
def get_type_for(cls,
|
||||
typename: str,
|
||||
parent_cls: typing.Optional[typing.Type['CommandType']] = None,
|
||||
) -> typing.Union[typing.Type['CommandType'], None]:
|
||||
"""Get a class type for a given typename."""
|
||||
if parent_cls is None:
|
||||
parent_cls = CommandType
|
||||
for a_type in parent_cls.__subclasses__():
|
||||
if typename in a_type.SUPPORTED_TYPENAMES:
|
||||
return a_type
|
||||
|
||||
# maybe a subclass of a subclass
|
||||
result = a_type.get_type_for(typename, a_type)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def parse(cls, value: str) -> typing.Optional[typing.Any]:
|
||||
"""Parse a given value."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def validate(cls, value: typing.Any) -> None:
|
||||
"""Value validation method. As this is the base class which should not be used\
|
||||
directly, we throw a NotImplementedError exception.
|
||||
|
||||
Args:
|
||||
value (Object): The value to validate. The data_type of the value depends on the item
|
||||
data_type and is checked accordingly.
|
||||
Raises:
|
||||
NotImplementedError: Raises NotImplementedError as the base class should never
|
||||
be used directly.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class UndefType(CommandType):
|
||||
"""Undefined type."""
|
||||
TYPENAME = 'UnDef'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[str]:
|
||||
"""Parse a given value."""
|
||||
if value in UndefType.UNDEFINED_STATES:
|
||||
return None
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: str) -> None:
|
||||
"""Value validation method."""
|
||||
|
||||
|
||||
class GroupType(CommandType):
|
||||
"""Group type."""
|
||||
TYPENAME = 'Group'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[str]:
|
||||
"""Parse a given value."""
|
||||
if value in GroupType.UNDEFINED_STATES:
|
||||
return None
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: str) -> None:
|
||||
"""Value validation method."""
|
||||
|
||||
|
||||
class StringType(CommandType):
|
||||
"""StringType data_type class."""
|
||||
|
||||
TYPENAME = 'String'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[str]:
|
||||
"""Parse a given value."""
|
||||
if value in StringType.UNDEFINED_STATES:
|
||||
return None
|
||||
if not isinstance(value, str):
|
||||
raise ValueError
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: str) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are any of data_type string.
|
||||
|
||||
Args:
|
||||
value (str): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
StringType.parse(value)
|
||||
|
||||
|
||||
class OnOffType(StringType):
|
||||
"""OnOffType data_type class."""
|
||||
TYPENAME = 'OnOff'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
ON = 'ON'
|
||||
OFF = 'OFF'
|
||||
POSSIBLE_VALUES = [ON, OFF]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[str]:
|
||||
"""Parse a given value."""
|
||||
if value in OnOffType.UNDEFINED_STATES:
|
||||
return None
|
||||
if value not in OnOffType.POSSIBLE_VALUES:
|
||||
raise ValueError
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: str) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are ``ON`` and ``OFF``.
|
||||
|
||||
Args:
|
||||
value (str): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
super().validate(value)
|
||||
OnOffType.parse(value)
|
||||
|
||||
|
||||
class OpenCloseType(StringType):
|
||||
"""OpenCloseType data_type class."""
|
||||
TYPENAME = 'OpenClosed'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
OPEN = 'OPEN'
|
||||
CLOSED = 'CLOSED'
|
||||
POSSIBLE_VALUES = [OPEN, CLOSED]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[str]:
|
||||
"""Parse a given value."""
|
||||
if value in OpenCloseType.UNDEFINED_STATES:
|
||||
return None
|
||||
if value not in OpenCloseType.POSSIBLE_VALUES:
|
||||
raise ValueError
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: str) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are ``OPEN`` and ``CLOSED``.
|
||||
|
||||
Args:
|
||||
value (str): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
super().validate(value)
|
||||
OpenCloseType.parse(value)
|
||||
|
||||
|
||||
class ColorType(CommandType):
|
||||
"""ColorType data_type class."""
|
||||
TYPENAME = 'HSB'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[typing.Tuple[float, float, float]]:
|
||||
"""Parse a given value."""
|
||||
if value in ColorType.UNDEFINED_STATES:
|
||||
return None
|
||||
|
||||
if not isinstance(value, str):
|
||||
raise ValueError
|
||||
|
||||
str_split = value.split(',')
|
||||
if len(str_split) != 3:
|
||||
raise ValueError
|
||||
|
||||
hs, ss, bs = value.split(',', 3)
|
||||
h = float(hs)
|
||||
s = float(ss)
|
||||
b = float(bs)
|
||||
if not ((0 <= h <= 360) and (0 <= s <= 100) and (0 <= b <= 100)):
|
||||
raise ValueError
|
||||
return h, s, b
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: typing.Union[str, typing.Tuple[float, float, float]]) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are in format H,S,B.
|
||||
Value ranges:
|
||||
H(ue): 0-360
|
||||
S(aturation): 0-100
|
||||
B(rightness): 0-100
|
||||
|
||||
Args:
|
||||
value (str): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
str_value = str(value)
|
||||
elif isinstance(value, tuple) and len(value) == 3:
|
||||
str_value = f'{value[0]},{value[1]},{value[2]}'
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
ColorType.parse(str_value)
|
||||
|
||||
|
||||
class DecimalType(CommandType):
|
||||
"""DecimalType data_type class."""
|
||||
TYPENAME = 'Decimal'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME, 'Quantity']
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Union[None, typing.Tuple[typing.Union[int, float], str]]:
|
||||
"""Parse a given value."""
|
||||
if value in DecimalType.UNDEFINED_STATES:
|
||||
return None
|
||||
|
||||
m = re.match(r'(-?[0-9.]+(?:[eE]-?[0-9]+)?)\s?(.*)?$', value)
|
||||
if m:
|
||||
value_value = m.group(1)
|
||||
value_unit_of_measure = m.group(2)
|
||||
|
||||
try:
|
||||
if '.' in value:
|
||||
return_value: typing.Union[int, float] = float(value_value)
|
||||
else:
|
||||
return_value = int(value_value)
|
||||
except ArithmeticError as exc:
|
||||
raise ValueError(exc) from exc
|
||||
|
||||
return return_value, value_unit_of_measure
|
||||
|
||||
raise ValueError
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: typing.Union[int, float, typing.Tuple[typing.Union[int, float], str], str]) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are any of data_type:
|
||||
- ``int``
|
||||
- ``float``
|
||||
- a tuple of (``int`` or ``float``, ``str``) for numeric value, unit of measure
|
||||
- a ``str`` that can be parsed to one of the above by ``DecimalType.parse``
|
||||
|
||||
Args:
|
||||
value (int, float, tuple, str): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
DecimalType.parse(value)
|
||||
elif isinstance(value, tuple) and len(value) == 2:
|
||||
DecimalType.parse(f'{value[0]} {value[1]}')
|
||||
elif not isinstance(value, (int, float)):
|
||||
raise ValueError
|
||||
|
||||
|
||||
class PercentType(CommandType):
|
||||
"""PercentType data_type class."""
|
||||
TYPENAME = 'Percent'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[float]:
|
||||
"""Parse a given value."""
|
||||
if value in PercentType.UNDEFINED_STATES:
|
||||
return None
|
||||
try:
|
||||
f = float(value)
|
||||
if not 0 <= f <= 100:
|
||||
raise ValueError
|
||||
return f
|
||||
except Exception as e:
|
||||
raise ValueError(e) from e
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: typing.Union[float, int]) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are any of data_type ``float`` or ``int`` and must be greater of equal to 0
|
||||
and smaller or equal to 100.
|
||||
|
||||
Args:
|
||||
value (float): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
if not (isinstance(value, (float, int)) and 0 <= value <= 100):
|
||||
raise ValueError
|
||||
|
||||
|
||||
class IncreaseDecreaseType(StringType):
|
||||
"""IncreaseDecreaseType data_type class."""
|
||||
TYPENAME = 'IncreaseDecrease'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
|
||||
INCREASE = 'INCREASE'
|
||||
DECREASE = 'DECREASE'
|
||||
|
||||
POSSIBLE_VALUES = [INCREASE, DECREASE]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[str]:
|
||||
"""Parse a given value."""
|
||||
if value in IncreaseDecreaseType.UNDEFINED_STATES:
|
||||
return None
|
||||
if value not in IncreaseDecreaseType.POSSIBLE_VALUES:
|
||||
raise ValueError
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: str) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are ``INCREASE`` and ``DECREASE``.
|
||||
|
||||
Args:
|
||||
value (str): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
super().validate(value)
|
||||
IncreaseDecreaseType.parse(value)
|
||||
|
||||
|
||||
class DateTimeType(CommandType):
|
||||
"""DateTimeType data_type class."""
|
||||
TYPENAME = 'DateTime'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[datetime.datetime]:
|
||||
"""Parse a given value."""
|
||||
if value in DateTimeType.UNDEFINED_STATES:
|
||||
return None
|
||||
return dateutil.parser.parse(value)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: datetime.datetime) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are any of data_type ``datetime.datetime``.
|
||||
|
||||
Args:
|
||||
value (datetime.datetime): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
if not isinstance(value, datetime.datetime):
|
||||
raise ValueError
|
||||
|
||||
|
||||
class UpDownType(StringType):
|
||||
"""UpDownType data_type class."""
|
||||
|
||||
TYPENAME = 'UpDown'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
UP = 'UP'
|
||||
DOWN = 'DOWN'
|
||||
POSSIBLE_VALUES = [UP, DOWN]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[str]:
|
||||
"""Parse a given value."""
|
||||
if value in UpDownType.UNDEFINED_STATES:
|
||||
return None
|
||||
if value not in UpDownType.POSSIBLE_VALUES:
|
||||
raise ValueError
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: str) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are ``UP`` and ``DOWN``.
|
||||
|
||||
Args:
|
||||
value (str): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
super().validate(value)
|
||||
|
||||
UpDownType.parse(value)
|
||||
|
||||
|
||||
class StopMoveType(StringType):
|
||||
"""UpDownType data_type class."""
|
||||
|
||||
TYPENAME = 'StopMove'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
STOP = 'STOP'
|
||||
POSSIBLE_VALUES = [STOP]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[str]:
|
||||
"""Parse a given value."""
|
||||
if value in StopMoveType.UNDEFINED_STATES:
|
||||
return None
|
||||
if value not in StopMoveType.POSSIBLE_VALUES:
|
||||
raise ValueError
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: str) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are ``UP`` and ``DOWN``.
|
||||
|
||||
Args:
|
||||
value (str): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
super().validate(value)
|
||||
|
||||
StopMoveType.parse(value)
|
||||
|
||||
|
||||
class PlayPauseType(StringType):
|
||||
"""PlayPauseType data_type class."""
|
||||
|
||||
TYPENAME = 'PlayPause'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
PLAY = 'PLAY'
|
||||
PAUSE = 'PAUSE'
|
||||
POSSIBLE_VALUES = [PLAY, PAUSE]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[str]:
|
||||
"""Parse a given value."""
|
||||
if value in PlayPauseType.UNDEFINED_STATES:
|
||||
return None
|
||||
if value not in PlayPauseType.POSSIBLE_VALUES:
|
||||
raise ValueError
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: str) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are ``PLAY``, ``PAUSE``
|
||||
|
||||
Args:
|
||||
value (str): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
super().validate(value)
|
||||
|
||||
PlayPauseType.parse(value)
|
||||
|
||||
|
||||
class NextPrevious(StringType):
|
||||
"""NextPrevious data_type class."""
|
||||
|
||||
TYPENAME = 'NextPrevious'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
NEXT = 'NEXT'
|
||||
PREVIOUS = 'PREVIOUS'
|
||||
POSSIBLE_VALUES = [NEXT, PREVIOUS]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[str]:
|
||||
"""Parse a given value."""
|
||||
if value in NextPrevious.UNDEFINED_STATES:
|
||||
return None
|
||||
if value not in NextPrevious.POSSIBLE_VALUES:
|
||||
raise ValueError
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: str) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are ``PLAY``, ``PAUSE``
|
||||
|
||||
Args:
|
||||
value (str): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
super().validate(value)
|
||||
|
||||
NextPrevious.parse(value)
|
||||
|
||||
|
||||
class RewindFastforward(StringType):
|
||||
"""RewindFastforward data_type class."""
|
||||
|
||||
TYPENAME = 'RewindFastforward'
|
||||
SUPPORTED_TYPENAMES = [TYPENAME]
|
||||
REWIND = 'REWIND'
|
||||
FASTFORWARD = 'FASTFORWARD'
|
||||
POSSIBLE_VALUES = [REWIND, FASTFORWARD]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> typing.Optional[str]:
|
||||
"""Parse a given value."""
|
||||
if value in RewindFastforward.UNDEFINED_STATES:
|
||||
return None
|
||||
if value not in RewindFastforward.POSSIBLE_VALUES:
|
||||
raise ValueError
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value: str) -> None:
|
||||
"""Value validation method.
|
||||
|
||||
Valid values are ``REWIND``, ``FASTFORWARD``
|
||||
|
||||
Args:
|
||||
value (str): The value to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: Raises ValueError if an invalid value has been specified.
|
||||
"""
|
||||
super().validate(value)
|
||||
|
||||
RewindFastforward.parse(value)
|
||||
72
myenv/lib/python3.12/site-packages/openhab/config.py
Normal file
72
myenv/lib/python3.12/site-packages/openhab/config.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""python library for accessing the openHAB REST API."""
|
||||
|
||||
#
|
||||
# Georges Toth (c) 2016-present <georges@trypill.org>
|
||||
#
|
||||
# python-openhab is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# python-openhab is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with python-openhab. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import pathlib
|
||||
import time
|
||||
import typing
|
||||
|
||||
import pydantic
|
||||
|
||||
'''Considering a oauth2 token config is expected to look like the following:
|
||||
|
||||
```
|
||||
{"client_id": "http://127.0.0.1/auth",
|
||||
"token_cache": "/<path>/<to>/.oauth2_token",
|
||||
"token": {
|
||||
"access_token": "adsafdasfasfsafasfsafasfasfasfsa....",
|
||||
"expires_in": 3600,
|
||||
"refresh_token": "312e21e21e32112",
|
||||
"scope": "admin",
|
||||
"token_type": "bearer",
|
||||
"user": {
|
||||
"name": "admin",
|
||||
"roles": [
|
||||
"administrator"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
, the following classes model that structure for validation.
|
||||
'''
|
||||
|
||||
|
||||
class Oauth2User(pydantic.BaseModel):
|
||||
"""Nested user structure within an oauth2 token."""
|
||||
name: str
|
||||
roles: typing.List[str]
|
||||
|
||||
|
||||
class Oauth2Token(pydantic.BaseModel):
|
||||
"""Structure as returned by openHAB when generating a new oauth2 token."""
|
||||
access_token: str
|
||||
expires_in: int
|
||||
expires_at: float = time.time() - 10
|
||||
refresh_token: str
|
||||
scope: typing.Union[str, typing.List[str]] = 'admin'
|
||||
token_type: str
|
||||
user: Oauth2User
|
||||
|
||||
|
||||
class Oauth2Config(pydantic.BaseModel):
|
||||
"""Structure expected for a full oauth2 config."""
|
||||
client_id: str = 'http://127.0.0.1/auth'
|
||||
token_cache: pathlib.Path
|
||||
token: Oauth2Token
|
||||
9
myenv/lib/python3.12/site-packages/openhab/exceptions.py
Normal file
9
myenv/lib/python3.12/site-packages/openhab/exceptions.py
Normal file
@@ -0,0 +1,9 @@
|
||||
"""python-openhab exceptions."""
|
||||
|
||||
|
||||
class OpenHABException(Exception):
|
||||
"""Base of all python-openhab exceptions."""
|
||||
|
||||
|
||||
class InvalidReturnException(OpenHABException):
|
||||
"""The openHAB server returned an invalid or unparsable result."""
|
||||
672
myenv/lib/python3.12/site-packages/openhab/items.py
Normal file
672
myenv/lib/python3.12/site-packages/openhab/items.py
Normal file
@@ -0,0 +1,672 @@
|
||||
"""python library for accessing the openHAB REST API."""
|
||||
|
||||
#
|
||||
# Georges Toth (c) 2016-present <georges@trypill.org>
|
||||
#
|
||||
# python-openhab is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# python-openhab is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with python-openhab. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import re
|
||||
import typing
|
||||
|
||||
import dateutil.parser
|
||||
|
||||
import openhab.command_types
|
||||
import openhab.exceptions
|
||||
|
||||
__author__ = 'Georges Toth <georges@trypill.org>'
|
||||
__license__ = 'AGPLv3+'
|
||||
|
||||
|
||||
class Item:
|
||||
"""Base item class."""
|
||||
|
||||
types: typing.Sequence[typing.Type[openhab.command_types.CommandType]] = []
|
||||
state_types: typing.Sequence[typing.Type[openhab.command_types.CommandType]] = []
|
||||
command_event_types: typing.Sequence[typing.Type[openhab.command_types.CommandType]] = []
|
||||
state_event_types: typing.Sequence[typing.Type[openhab.command_types.CommandType]] = []
|
||||
state_changed_event_types: typing.Sequence[typing.Type[openhab.command_types.CommandType]] = []
|
||||
|
||||
TYPENAME = 'unknown'
|
||||
|
||||
def __init__(self, openhab_conn: 'openhab.client.OpenHAB', json_data: dict) -> None:
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
openhab_conn (openhab.OpenHAB): openHAB object.
|
||||
json_data (dic): A dict converted from the JSON data returned by the openHAB
|
||||
server.
|
||||
"""
|
||||
self.openhab = openhab_conn
|
||||
self.type_: typing.Optional[str] = None
|
||||
self.quantityType: typing.Optional[str] = None
|
||||
self.editable = None
|
||||
self.label = ''
|
||||
self.category = ''
|
||||
self.tags = ''
|
||||
self.groupNames = ''
|
||||
self.group = False
|
||||
self.name = ''
|
||||
self._state = None # type: typing.Optional[typing.Any]
|
||||
self._unitOfMeasure = ''
|
||||
self._raw_state = None # type: typing.Optional[typing.Any] # raw state as returned by the server
|
||||
self._members = {} # type: typing.Dict[str, typing.Any] # group members (key = item name), for none-group items it's empty
|
||||
self.function_name: typing.Optional[str] = None
|
||||
self.function_params: typing.Optional[typing.Sequence[str]] = None
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
self.init_from_json(json_data)
|
||||
|
||||
def init_from_json(self, json_data: dict) -> None:
|
||||
"""Initialize this object from a json configuration as fetched from openHAB.
|
||||
|
||||
Args:
|
||||
json_data (dict): A dict converted from the JSON data returned by the openHAB
|
||||
server.
|
||||
"""
|
||||
self.name = json_data['name']
|
||||
if json_data['type'] == 'Group':
|
||||
self.group = True
|
||||
if 'groupType' in json_data:
|
||||
self.type_ = json_data['groupType']
|
||||
|
||||
if 'function' in json_data:
|
||||
self.function_name = json_data['function']['name']
|
||||
|
||||
if 'params' in json_data['function']:
|
||||
self.function_params = json_data['function']['params']
|
||||
|
||||
# init members
|
||||
for i in json_data['members']:
|
||||
self.members[i['name']] = self.openhab.json_to_item(i)
|
||||
|
||||
else:
|
||||
self.type_ = json_data.get('type', None)
|
||||
|
||||
if self.type_ is None:
|
||||
raise openhab.exceptions.InvalidReturnException('Item did not return a type attribute.')
|
||||
|
||||
parts = self.type_.split(':')
|
||||
if len(parts) == 2:
|
||||
self.quantityType = parts[1]
|
||||
|
||||
if 'editable' in json_data:
|
||||
self.editable = json_data['editable']
|
||||
if 'label' in json_data:
|
||||
self.label = json_data['label']
|
||||
if 'category' in json_data:
|
||||
self.category = json_data['category']
|
||||
if 'tags' in json_data:
|
||||
self.tags = json_data['tags']
|
||||
if 'groupNames' in json_data:
|
||||
self.groupNames = json_data['groupNames']
|
||||
|
||||
self._raw_state = json_data['state']
|
||||
|
||||
if self.is_undefined(self._raw_state):
|
||||
self._state = None
|
||||
else:
|
||||
self._state, self._unitOfMeasure = self._parse_rest(self._raw_state)
|
||||
|
||||
@property
|
||||
def state(self) -> typing.Any:
|
||||
"""The state property represents the current state of the item.
|
||||
|
||||
The state is automatically refreshed from openHAB on reading it.
|
||||
Updating the value via this property send an update to the event bus.
|
||||
"""
|
||||
json_data = self.openhab.get_item_raw(self.name)
|
||||
self.init_from_json(json_data)
|
||||
|
||||
return self._state
|
||||
|
||||
@state.setter
|
||||
def state(self, value: typing.Any) -> None:
|
||||
self.update(value)
|
||||
|
||||
@property
|
||||
def unit_of_measure(self) -> str:
|
||||
"""Return the unit of measure. Returns an empty string if there is none defined."""
|
||||
return self._unitOfMeasure
|
||||
|
||||
@property
|
||||
def members(self) -> typing.Dict[str, typing.Any]:
|
||||
"""If item is a type of Group, it will return all member items for this group.
|
||||
|
||||
For none group item empty dictionary will be returned.
|
||||
|
||||
Returns:
|
||||
dict: Returns a dict with item names as key and `Item` class instances as value.
|
||||
|
||||
"""
|
||||
return self._members
|
||||
|
||||
def _validate_value(self, value: typing.Union[str, typing.Type[openhab.command_types.CommandType]]) -> None:
|
||||
"""Private method for verifying the new value before modifying the state of the item."""
|
||||
if self.type_ == 'String':
|
||||
if not isinstance(value, (str, bytes)):
|
||||
raise ValueError
|
||||
elif self.types:
|
||||
validation = False
|
||||
|
||||
for type_ in self.types:
|
||||
try:
|
||||
type_.validate(value)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
validation = True
|
||||
|
||||
if not validation:
|
||||
raise ValueError(f'Invalid value "{value}"')
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
def _parse_rest(self, value: str) -> typing.Tuple[str, str]:
|
||||
"""Parse a REST result into a native object."""
|
||||
return value, ''
|
||||
|
||||
def _rest_format(self, value: str) -> typing.Union[str, bytes]:
|
||||
"""Format a value before submitting to openHAB."""
|
||||
_value = value # type: typing.Union[str, bytes]
|
||||
|
||||
# Only ascii encoding is supported by default. If non-ascii characters were provided, convert them to bytes.
|
||||
try:
|
||||
_ = value.encode('ascii')
|
||||
except UnicodeError:
|
||||
_value = value.encode('utf-8')
|
||||
|
||||
return _value
|
||||
|
||||
def is_undefined(self, value: str) -> bool:
|
||||
"""Check if value is undefined."""
|
||||
for aStateType in self.state_types:
|
||||
if not aStateType.is_undefined(value):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation."""
|
||||
state = self._state
|
||||
if self._unitOfMeasure and not isinstance(self._state, tuple):
|
||||
state = f'{self._state} {self._unitOfMeasure}'
|
||||
return f'<{self.type_} - {self.name} : {state}>'
|
||||
|
||||
def _update(self, value: typing.Any) -> None:
|
||||
"""Updates the state of an item, input validation is expected to be already done.
|
||||
|
||||
Args:
|
||||
value (object): The value to update the item with. The type of the value depends
|
||||
on the item type and is checked accordingly.
|
||||
"""
|
||||
# noinspection PyTypeChecker
|
||||
self.openhab.req_put(f'/items/{self.name}/state', data=value)
|
||||
|
||||
def update(self, value: typing.Any) -> None:
|
||||
"""Updates the state of an item.
|
||||
|
||||
Args:
|
||||
value (object): The value to update the item with. The type of the value depends
|
||||
on the item type and is checked accordingly.
|
||||
"""
|
||||
self._validate_value(value)
|
||||
|
||||
v = self._rest_format(value)
|
||||
|
||||
self._state = value
|
||||
|
||||
self._update(v)
|
||||
|
||||
def command(self, value: typing.Any) -> None:
|
||||
"""Sends the given value as command to the event bus.
|
||||
|
||||
Args:
|
||||
value (object): The value to send as command to the event bus. The type of the
|
||||
value depends on the item type and is checked accordingly.
|
||||
"""
|
||||
self._validate_value(value)
|
||||
|
||||
v = self._rest_format(value)
|
||||
|
||||
self._state = value
|
||||
|
||||
self.openhab.req_post(f'/items/{self.name}', data=v)
|
||||
|
||||
def update_state_null(self) -> None:
|
||||
"""Update the state of the item to *NULL*."""
|
||||
self._update('NULL')
|
||||
|
||||
def update_state_undef(self) -> None:
|
||||
"""Update the state of the item to *UNDEF*."""
|
||||
self._update('UNDEF')
|
||||
|
||||
def is_state_null(self) -> bool:
|
||||
"""If the item state is None, use this method for checking if the remote value is NULL."""
|
||||
if self.state is None:
|
||||
# we need to query the current remote state as else this method will not work correctly if called after
|
||||
# either update_state method
|
||||
if self._raw_state is None:
|
||||
# This should never happen
|
||||
raise ValueError('Invalid internal (raw) state.')
|
||||
|
||||
return self._raw_state == 'NULL'
|
||||
|
||||
return False
|
||||
|
||||
def is_state_undef(self) -> bool:
|
||||
"""If the item state is None, use this method for checking if the remote value is UNDEF."""
|
||||
if self.state is None:
|
||||
# we need to query the current remote state as else this method will not work correctly if called after
|
||||
# either update_state method
|
||||
if self._raw_state is None:
|
||||
# This should never happen
|
||||
raise ValueError('Invalid internal (raw) state.')
|
||||
|
||||
return self._raw_state == 'UNDEF'
|
||||
|
||||
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."""
|
||||
|
||||
TYPENAME = 'Group'
|
||||
types: typing.List[typing.Type[openhab.command_types.CommandType]] = []
|
||||
state_types: typing.List[typing.Type[openhab.command_types.CommandType]] = []
|
||||
|
||||
|
||||
class StringItem(Item):
|
||||
"""String item type."""
|
||||
|
||||
TYPENAME = 'String'
|
||||
types = [openhab.command_types.StringType]
|
||||
state_types = types
|
||||
|
||||
|
||||
class DateTimeItem(Item):
|
||||
"""DateTime item type."""
|
||||
|
||||
TYPENAME = 'DateTime'
|
||||
types = [openhab.command_types.DateTimeType]
|
||||
state_types = types
|
||||
|
||||
def __gt__(self, other: datetime.datetime) -> bool:
|
||||
"""Greater than comparison."""
|
||||
if self._state is None or not isinstance(other, datetime.datetime):
|
||||
raise NotImplementedError('You can only compare two DateTimeItem objects.')
|
||||
|
||||
return self._state > other
|
||||
|
||||
def __ge__(self, other: datetime.datetime) -> bool:
|
||||
"""Greater or equal comparison."""
|
||||
if self._state is None or not isinstance(other, datetime.datetime):
|
||||
raise NotImplementedError('You can only compare two DateTimeItem objects.')
|
||||
|
||||
return self._state >= other
|
||||
|
||||
def __lt__(self, other: object) -> bool:
|
||||
"""Less than comparison."""
|
||||
if not isinstance(other, datetime.datetime):
|
||||
raise NotImplementedError('You can only compare two DateTimeItem objects.')
|
||||
|
||||
return not self.__gt__(other)
|
||||
|
||||
def __le__(self, other: object) -> bool:
|
||||
"""Less or equal comparison."""
|
||||
if self._state is None or not isinstance(other, datetime.datetime):
|
||||
raise NotImplementedError('You can only compare two DateTimeItem objects.')
|
||||
|
||||
return self._state <= other
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
"""Equality comparison."""
|
||||
if not isinstance(other, datetime.datetime):
|
||||
raise NotImplementedError('You can only compare two DateTimeItem objects.')
|
||||
|
||||
return self._state == other
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
"""Not equal comparison."""
|
||||
if not isinstance(other, datetime.datetime):
|
||||
raise NotImplementedError('You can only compare two DateTimeItem objects.')
|
||||
|
||||
return not self.__eq__(other)
|
||||
|
||||
def _parse_rest(self, value: str) -> typing.Tuple[datetime.datetime, str]: # type: ignore[override]
|
||||
"""Parse a REST result into a native object.
|
||||
|
||||
Args:
|
||||
value (str): A string argument to be converted into a datetime.datetime object.
|
||||
|
||||
Returns:
|
||||
datetime.datetime: The datetime.datetime object as converted from the string
|
||||
parameter.
|
||||
"""
|
||||
return dateutil.parser.parse(value), ''
|
||||
|
||||
def _rest_format(self, value: datetime.datetime) -> str: # type: ignore[override]
|
||||
"""Format a value before submitting to openHAB.
|
||||
|
||||
Args:
|
||||
value (datetime.datetime): A datetime.datetime argument to be converted
|
||||
into a string.
|
||||
|
||||
Returns:
|
||||
str: The string as converted from the datetime.datetime parameter.
|
||||
"""
|
||||
# openHAB supports only up to milliseconds as of this writing
|
||||
return value.isoformat(timespec='milliseconds')
|
||||
|
||||
|
||||
class PlayerItem(Item):
|
||||
"""PlayerItem item type."""
|
||||
|
||||
TYPENAME = 'Player'
|
||||
types = [openhab.command_types.PlayPauseType, openhab.command_types.NextPrevious, openhab.command_types.RewindFastforward]
|
||||
state_types = [openhab.command_types.PlayPauseType, openhab.command_types.RewindFastforward]
|
||||
|
||||
def play(self) -> None:
|
||||
"""Send the command PLAY."""
|
||||
self.command(openhab.command_types.PlayPauseType.PLAY)
|
||||
|
||||
def pause(self) -> None:
|
||||
"""Send the command PAUSE."""
|
||||
self.command(openhab.command_types.PlayPauseType.PAUSE)
|
||||
|
||||
def next(self) -> None:
|
||||
"""Send the command NEXT."""
|
||||
self.command(openhab.command_types.NextPrevious.NEXT)
|
||||
|
||||
def previous(self) -> None:
|
||||
"""Send the command PREVIOUS."""
|
||||
self.command(openhab.command_types.NextPrevious.PREVIOUS)
|
||||
|
||||
def fastforward(self) -> None:
|
||||
"""Send the command FASTFORWARD."""
|
||||
self.command(openhab.command_types.RewindFastforward.FASTFORWARD)
|
||||
|
||||
def rewind(self) -> None:
|
||||
"""Send the command REWIND."""
|
||||
self.command(openhab.command_types.RewindFastforward.REWIND)
|
||||
|
||||
|
||||
class SwitchItem(Item):
|
||||
"""SwitchItem item type."""
|
||||
|
||||
TYPENAME = 'Switch'
|
||||
types = [openhab.command_types.OnOffType]
|
||||
state_types = types
|
||||
|
||||
def on(self) -> None:
|
||||
"""Set the state of the switch to ON."""
|
||||
self.command(openhab.command_types.OnOffType.ON)
|
||||
|
||||
def off(self) -> None:
|
||||
"""Set the state of the switch to OFF."""
|
||||
self.command(openhab.command_types.OnOffType.OFF)
|
||||
|
||||
def toggle(self) -> None:
|
||||
"""Toggle the state of the switch to OFF to ON and vice versa."""
|
||||
if self.state == openhab.command_types.OnOffType.ON:
|
||||
self.off()
|
||||
elif self.state == openhab.command_types.OnOffType.OFF:
|
||||
self.on()
|
||||
|
||||
|
||||
class NumberItem(Item):
|
||||
"""NumberItem item type."""
|
||||
|
||||
TYPENAME = 'Number'
|
||||
types = [openhab.command_types.DecimalType]
|
||||
state_types = types
|
||||
|
||||
def _parse_rest(self, value: str) -> typing.Tuple[typing.Union[float, None], str]: # type: ignore[override]
|
||||
"""Parse a REST result into a native object.
|
||||
|
||||
Args:
|
||||
value (str): A string argument to be converted into a float object.
|
||||
|
||||
Returns:
|
||||
float: The float object as converted from the string parameter.
|
||||
str: The unit Of Measure or empty string
|
||||
"""
|
||||
if value in ('UNDEF', 'NULL'):
|
||||
return None, ''
|
||||
# m = re.match(r'''^(-?[0-9.]+)''', value)
|
||||
try:
|
||||
m = re.match(r'(-?[0-9.]+(?:[eE]-?[0-9]+)?)\s?(.*)?$', value)
|
||||
|
||||
if m:
|
||||
value = m.group(1)
|
||||
unit_of_measure = m.group(2)
|
||||
|
||||
return float(value), unit_of_measure
|
||||
|
||||
return float(value), ''
|
||||
|
||||
except (ArithmeticError, ValueError) as exc:
|
||||
self.logger.error('Error in parsing new value "%s" for "%s" - "%s"', value, self.name, exc)
|
||||
|
||||
raise ValueError(f'{self.__class__}: unable to parse value "{value}"')
|
||||
|
||||
def _rest_format(self, value: typing.Union[float, typing.Tuple[float, str], str]) -> typing.Union[str, bytes]:
|
||||
"""Format a value before submitting to openHAB.
|
||||
|
||||
Args:
|
||||
value: Either a float, a tuple of (float, str), or string; in the first two cases we have to cast it to a string.
|
||||
|
||||
Returns:
|
||||
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]}')
|
||||
if not isinstance(value, str):
|
||||
return super()._rest_format(f'{value:G}')
|
||||
return super()._rest_format(value)
|
||||
|
||||
|
||||
class ContactItem(Item):
|
||||
"""Contact item type."""
|
||||
|
||||
TYPENAME = 'Contact'
|
||||
types = [openhab.command_types.OpenCloseType]
|
||||
state_types = types
|
||||
|
||||
def command(self, *args: typing.Any, **kwargs: typing.Any) -> None:
|
||||
"""This overrides the `Item` command method.
|
||||
|
||||
Note: Commands are not accepted for items of type contact.
|
||||
"""
|
||||
raise ValueError(f'This item ({self.__class__}) only supports updates, not commands!')
|
||||
|
||||
def open(self) -> None:
|
||||
"""Set the state of the contact item to OPEN."""
|
||||
self.state = openhab.command_types.OpenCloseType.OPEN
|
||||
|
||||
def closed(self) -> None:
|
||||
"""Set the state of the contact item to CLOSED."""
|
||||
self.state = openhab.command_types.OpenCloseType.CLOSED
|
||||
|
||||
|
||||
class DimmerItem(Item):
|
||||
"""DimmerItem item type."""
|
||||
|
||||
TYPENAME = 'Dimmer'
|
||||
types = [openhab.command_types.OnOffType, openhab.command_types.PercentType, openhab.command_types.IncreaseDecreaseType]
|
||||
state_types = [openhab.command_types.PercentType]
|
||||
|
||||
def _parse_rest(self, value: str) -> typing.Tuple[float, str]: # type: ignore[override]
|
||||
"""Parse a REST result into a native object.
|
||||
|
||||
Args:
|
||||
value (str): A string argument to be converted into a int object.
|
||||
|
||||
Returns:
|
||||
float: The int object as converted from the string parameter.
|
||||
str: Possible UoM
|
||||
"""
|
||||
return float(value), ''
|
||||
|
||||
def _rest_format(self, value: typing.Union[str, int]) -> str:
|
||||
"""Format a value before submitting to OpenHAB.
|
||||
|
||||
Args:
|
||||
value: Either a string or an integer; in the latter case we have to cast it to a string.
|
||||
|
||||
Returns:
|
||||
str: The string as possibly converted from the parameter.
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
return str(value)
|
||||
|
||||
return value
|
||||
|
||||
def on(self) -> None:
|
||||
"""Set the state of the dimmer to ON."""
|
||||
self.command(openhab.command_types.OnOffType.ON)
|
||||
|
||||
def off(self) -> None:
|
||||
"""Set the state of the dimmer to OFF."""
|
||||
self.command(openhab.command_types.OnOffType.OFF)
|
||||
|
||||
def increase(self) -> None:
|
||||
"""Increase the state of the dimmer."""
|
||||
self.command(openhab.command_types.IncreaseDecreaseType.INCREASE)
|
||||
|
||||
def decrease(self) -> None:
|
||||
"""Decrease the state of the dimmer."""
|
||||
self.command(openhab.command_types.IncreaseDecreaseType.DECREASE)
|
||||
|
||||
|
||||
class ColorItem(DimmerItem):
|
||||
"""ColorItem item type."""
|
||||
|
||||
TYPENAME = 'Color'
|
||||
types = [openhab.command_types.OnOffType, openhab.command_types.PercentType, openhab.command_types.IncreaseDecreaseType, openhab.command_types.ColorType]
|
||||
state_types = [openhab.command_types.ColorType]
|
||||
|
||||
def _parse_rest(self, value: str) -> typing.Tuple[typing.Optional[typing.Tuple[float, float, float]], str]: # type: ignore[override]
|
||||
"""Parse a REST result into a native object.
|
||||
|
||||
Args:
|
||||
value (str): A string argument to be converted into a str object.
|
||||
|
||||
Returns:
|
||||
HSB components
|
||||
Optional UoM
|
||||
"""
|
||||
result = openhab.command_types.ColorType.parse(value)
|
||||
return result, ''
|
||||
|
||||
def _rest_format(self, value: typing.Union[typing.Tuple[int, int, float], str, int]) -> str:
|
||||
"""Format a value before submitting to openHAB.
|
||||
|
||||
Args:
|
||||
value: Either a string, an integer or a tuple of HSB components (int, int, float); in the latter two cases we have to cast it to a string.
|
||||
|
||||
Returns:
|
||||
str: The string as possibly converted from the parameter.
|
||||
"""
|
||||
if isinstance(value, tuple):
|
||||
if len(value) == 3:
|
||||
return f'{value[0]},{value[1]},{value[2]}'
|
||||
|
||||
if not isinstance(value, str):
|
||||
return str(value)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class RollershutterItem(Item):
|
||||
"""RollershutterItem item type."""
|
||||
|
||||
TYPENAME = 'Rollershutter'
|
||||
types = [openhab.command_types.UpDownType, openhab.command_types.PercentType, openhab.command_types.StopMoveType]
|
||||
state_types = [openhab.command_types.PercentType]
|
||||
|
||||
def _parse_rest(self, value: str) -> typing.Tuple[int, str]: # type: ignore[override]
|
||||
"""Parse a REST result into a native object.
|
||||
|
||||
Args:
|
||||
value (str): A string argument to be converted into a int object.
|
||||
|
||||
Returns:
|
||||
int: The int object as converted from the string parameter.
|
||||
str: Possible UoM
|
||||
"""
|
||||
return int(float(value)), ''
|
||||
|
||||
def _rest_format(self, value: typing.Union[str, int]) -> str:
|
||||
"""Format a value before submitting to openHAB.
|
||||
|
||||
Args:
|
||||
value: Either a string or an integer; in the latter case we have to cast it to a string.
|
||||
|
||||
Returns:
|
||||
str: The string as possibly converted from the parameter.
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
return str(value)
|
||||
|
||||
return value
|
||||
|
||||
def up(self) -> None:
|
||||
"""Set the state of the dimmer to ON."""
|
||||
self.command(openhab.command_types.UpDownType.UP)
|
||||
|
||||
def down(self) -> None:
|
||||
"""Set the state of the dimmer to OFF."""
|
||||
self.command(openhab.command_types.UpDownType.DOWN)
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Set the state of the dimmer to OFF."""
|
||||
self.command(openhab.command_types.StopMoveType.STOP)
|
||||
96
myenv/lib/python3.12/site-packages/openhab/oauth2_helper.py
Normal file
96
myenv/lib/python3.12/site-packages/openhab/oauth2_helper.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""OAuth2 helper method for generating and fetching an OAuth2 token."""
|
||||
|
||||
import typing
|
||||
|
||||
import bs4
|
||||
import httpx
|
||||
|
||||
|
||||
def get_oauth2_token(base_url: str,
|
||||
username: str,
|
||||
password: str,
|
||||
client_id: typing.Optional[str] = None,
|
||||
redirect_url: typing.Optional[str] = None,
|
||||
scope: typing.Optional[str] = None,
|
||||
) -> dict:
|
||||
"""Method for generating an OAuth2 token.
|
||||
|
||||
Args:
|
||||
base_url: openHAB base URL
|
||||
username: Admin account username
|
||||
password: Admin account password
|
||||
client_id: OAuth2 client ID; does not need to be specified
|
||||
redirect_url: OAuth2 redirect URL; does not need to be specified
|
||||
scope: Do not change unless you know what you are doing
|
||||
|
||||
Returns:
|
||||
*dict* with the generated OAuth2 token details
|
||||
"""
|
||||
if client_id is not None:
|
||||
oauth2_client_id = client_id
|
||||
else:
|
||||
oauth2_client_id = 'http://127.0.0.1/auth'
|
||||
|
||||
if redirect_url is not None:
|
||||
oauth2_redirect_url = redirect_url
|
||||
else:
|
||||
oauth2_redirect_url = 'http://127.0.0.1/auth'
|
||||
|
||||
if scope is not None:
|
||||
oauth2_scope = scope
|
||||
else:
|
||||
oauth2_scope = 'admin'
|
||||
|
||||
oauth2_auth_endpoint = f'{base_url}/rest/auth/token'
|
||||
url_generate_token = f'{base_url}/auth?response_type=code&redirect_uri={oauth2_redirect_url}&client_id={oauth2_client_id}&scope={oauth2_scope}'
|
||||
|
||||
res = httpx.get(url_generate_token, timeout=30)
|
||||
res.raise_for_status()
|
||||
|
||||
soup = bs4.BeautifulSoup(res.content, 'html.parser')
|
||||
submit_form = soup.find('form')
|
||||
|
||||
action = submit_form.attrs.get('action').lower()
|
||||
url_submit_generate_token = f'{base_url}{action}'
|
||||
|
||||
data = {}
|
||||
|
||||
for input_tag in submit_form.find_all('input'):
|
||||
input_name = input_tag.attrs.get('name')
|
||||
|
||||
if input_name is None:
|
||||
continue
|
||||
|
||||
input_value = input_tag.attrs.get('value', '')
|
||||
|
||||
data[input_name] = input_value
|
||||
|
||||
data['username'] = username
|
||||
data['password'] = password
|
||||
|
||||
res = httpx.post(url_submit_generate_token, data=data, timeout=30)
|
||||
if not 200 < res.status_code <= 302:
|
||||
res.raise_for_status()
|
||||
|
||||
if 'location' not in res.headers:
|
||||
raise KeyError('Token generation failed!')
|
||||
|
||||
oauth_redirect_location = res.headers['location']
|
||||
|
||||
if '?code=' not in oauth_redirect_location:
|
||||
raise ValueError('Token generation failed!')
|
||||
|
||||
oauth2_registration_code = oauth_redirect_location.split('?code=', 1)[1]
|
||||
|
||||
data = {'grant_type': 'authorization_code',
|
||||
'code': oauth2_registration_code,
|
||||
'redirect_uri': oauth2_redirect_url,
|
||||
'client_id': oauth2_client_id,
|
||||
'refresh_token': None,
|
||||
'code_verifier': None,
|
||||
}
|
||||
|
||||
res = httpx.post(oauth2_auth_endpoint, data=data, timeout=30)
|
||||
res.raise_for_status()
|
||||
|
||||
return res.json()
|
||||
0
myenv/lib/python3.12/site-packages/openhab/py.typed
Normal file
0
myenv/lib/python3.12/site-packages/openhab/py.typed
Normal file
46
myenv/lib/python3.12/site-packages/openhab/rules.py
Normal file
46
myenv/lib/python3.12/site-packages/openhab/rules.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""python library for accessing the openHAB REST API."""
|
||||
|
||||
#
|
||||
# Georges Toth (c) 2016-present <georges@trypill.org>
|
||||
#
|
||||
# python-openhab is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# python-openhab is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with python-openhab. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# pylint: disable=bad-indentation
|
||||
|
||||
import logging
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import openhab.client
|
||||
|
||||
__author__ = 'Georges Toth <georges@trypill.org>'
|
||||
__license__ = 'AGPLv3+'
|
||||
|
||||
|
||||
class Rules:
|
||||
"""Base rule class."""
|
||||
|
||||
def __init__(self, openhab_conn: 'openhab.client.OpenHAB') -> None:
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
openhab_conn (openhab.OpenHAB): openHAB object.
|
||||
"""
|
||||
self.openhab = openhab_conn
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
def get(self) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||
"""Get all rules."""
|
||||
return self.openhab.req_get('/rules')
|
||||
Reference in New Issue
Block a user