pax_global_header 0000666 0000000 0000000 00000000064 14734113327 0014517 g ustar 00root root 0000000 0000000 52 comment=52983204b1abd8e55507833bdbcbd540d9068fa8
pyvlx-0.2.26/ 0000775 0000000 0000000 00000000000 14734113327 0012770 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/.coveragerc 0000664 0000000 0000000 00000000116 14734113327 0015107 0 ustar 00root root 0000000 0000000 [run]
source = pyvlx
omit =
[report]
exclude_lines =
if TYPE_CHECKING: pyvlx-0.2.26/.github/ 0000775 0000000 0000000 00000000000 14734113327 0014330 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/.github/dependabot.yml 0000664 0000000 0000000 00000000175 14734113327 0017163 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
pyvlx-0.2.26/.github/workflows/ 0000775 0000000 0000000 00000000000 14734113327 0016365 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/.github/workflows/ci.yml 0000664 0000000 0000000 00000007225 14734113327 0017511 0 ustar 00root root 0000000 0000000 name: CI
on:
push:
branches:
- '**' # run on all branches
pull_request:
branches:
- '**' # run on all branches
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Cache pip packages
uses: actions/cache@v4
env:
cache-name: cache-pypi-modules
with:
# pip cache files are stored in `~/.cache/pip` on Linux
path: ~/.cache/pip
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('requirements/production.txt', 'requirements/testing.txt') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
- name: Cache pre-commit packages
uses: actions/cache@v4
env:
cache-name: cache-pre-commit
with:
# pre-commit cache files are usually stored in `~/.cache/pre-commit` on Linux
path: ~/.cache/pre-commit
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('.pre-commit-config.yaml') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
- name: Install dependencies
run: pip install -r requirements/testing.txt
- name: Linter Pydocstyle
run: pydocstyle pyvlx test/*.py *.py examples/*.py
- name: Linter Flake8
run: flake8
- name: Linter Pylint
run: PYTHONPATH=. pylint --rcfile=.pylintrc pyvlx test/*.py *.py examples/*.py
- name: Mypy
run: mypy --install-types --non-interactive pyvlx
- name: Tests
run: PYTHONPATH=. pytest --cov pyvlx --cov-report xml
- name: Isort
run: isort --check-only test examples pyvlx
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.python-version }}
path: .coverage
include-hidden-files: true
coverage:
name: Process test coverage
runs-on: ubuntu-latest
needs: ["build"]
strategy:
matrix:
python-version: ["3.11"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Cache pip packages
uses: actions/cache@v4
env:
cache-name: cache-pypi-modules
with:
# pip cache files are stored in `~/.cache/pip` on Linux
path: ~/.cache/pip
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('requirements/production.txt', 'requirements/testing.txt') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
- name: Cache pre-commit packages
uses: actions/cache@v4
env:
cache-name: cache-pre-commit
with:
# pre-commit cache files are usually stored in `~/.cache/pre-commit` on Linux
path: ~/.cache/pre-commit
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('.pre-commit-config.yaml') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
- name: Install dependencies
run: |
pip install -r requirements/testing.txt
- name: Download all coverage artifacts
uses: actions/download-artifact@v4
- name: Create coverage report
run: |
coverage combine coverage*/.coverage*
coverage report --fail-under=79
coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
pyvlx-0.2.26/.gitignore 0000664 0000000 0000000 00000000125 14734113327 0014756 0 ustar 00root root 0000000 0000000 *.pyc
build/
dist/
pyvlx.egg-info/
.coverage
.cache
.DS_Store
.venv
venv
htmlcov
pyvlx-0.2.26/.isort.cfg 0000664 0000000 0000000 00000000054 14734113327 0014666 0 ustar 00root root 0000000 0000000 [settings]
multi_line_output=4
skip=old_api
pyvlx-0.2.26/.pylintrc 0000664 0000000 0000000 00000000567 14734113327 0014645 0 ustar 00root root 0000000 0000000 [MASTER]
reports=no
[MESSAGES CONTROL]
disable=E1101,C0111,duplicate-code,fixme,locally-disabled,too-few-public-methods,too-many-arguments,too-many-instance-attributes,consider-using-f-string
good-names=PAYLOAD_LEN,i,j
[REPORTS]
output-format=colorized
[FORMAT]
indent-string=' '
max-line-length=150
max-positional-arguments=7
[MAIN]
max-branches=20
max-statements=55
pyvlx-0.2.26/.vscode/ 0000775 0000000 0000000 00000000000 14734113327 0014331 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/.vscode/settings.json 0000664 0000000 0000000 00000000222 14734113327 0017060 0 ustar 00root root 0000000 0000000 {
"python.testing.pytestArgs": [
"test"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
} pyvlx-0.2.26/LICENSE 0000664 0000000 0000000 00000016743 14734113327 0014010 0 ustar 00root root 0000000 0000000 GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
pyvlx-0.2.26/Makefile 0000664 0000000 0000000 00000001635 14734113327 0014435 0 ustar 00root root 0000000 0000000
all:
@echo
@echo "Available targets"
@echo ""
@echo "build -- build python package"
@echo ""
@echo "pypi -- upload package to pypi"
@echo ""
@echo "test -- execute test suite"
@echo ""
@echo "pylint -- run pylint tests"
@echo ""
@echo "pydocstyle -- run pydocstyle tests"
@echo ""
@echo "coverage -- create coverage report"
@echo ""
test:
PYTHONPATH="${PYTHONPATH}:/" python3 -m unittest discover -s test -p "*_test.py" -b
build:
@python3 setup.py sdist
@python3 setup.py egg_info
pypi:
# python3 setup.py register -r pypi
#@python3 setup.py sdist upload -r pypi
@rm -f dist/*
@python setup.py sdist
@twine upload dist/*
pylint:
@pylint --rcfile=.pylintrc pyvlx test/*.py *.py examples/*.py
pydocstyle:
@pydocstyle pyvlx test/*.py test/*.py *.py examples/*.py
coverage:
py.test --cov-report html --cov pyvlx --verbose
.PHONY: test build
pyvlx-0.2.26/README.md 0000664 0000000 0000000 00000004147 14734113327 0014255 0 ustar 00root root 0000000 0000000 PyVLX - controling VELUX windows with Python
============================================
[](https://github.com/Julius2342/pyvlx/actions/workflows/ci.yml)
PyVLX uses the Velux KLF 200 interface to control io-Homecontrol devices, e.g. Velux Windows.
Installation
------------
PyVLX can be installed via:
```bash
pip3 install pyvlx
```
Home Assistant Plugin
---------------------
PyVLX is used within [Home Assistant](https://www.home-assistant.io/components/velux/). To enable it add the following lines to your ~/.homeassistant/configuration.yml:
```yaml
velux:
host: "192.168.0.0"
password: "1ADwl48dka"
```
*Please note that this uses the WiFi password, not the web login.*
For debugging frames add:
```yaml
logger:
default: warning
logs:
homeassistant.components.velux: debug
pyvlx: debug
```
Basic Operations
----------------
```python
"""Just a demo of the new PyVLX module."""
import asyncio
from pyvlx import PyVLX, Position
async def main(loop):
"""Demonstrate functionality of PyVLX."""
pyvlx = PyVLX('pyvlx.yaml', loop=loop)
# Alternative:
# pyvlx = PyVLX(host="192.168.2.127", password="velux123", loop=loop)
# Runing scenes:
await pyvlx.load_scenes()
await pyvlx.scenes["All Windows Closed"].run()
# Changing position of windows:
await pyvlx.load_nodes()
await pyvlx.nodes['Bath'].open()
await pyvlx.nodes['Bath'].close()
await pyvlx.nodes['Bath'].set_position(Position(position_percent=45))
# Read limits of windows
# limit = await pyvlx.nodes['Bath'].get_limitation()
# limit.min_value
# limit.max_value
# Changing of on-off switches:
# await pyvlx.nodes['CoffeeMaker'].set_on()
# await pyvlx.nodes['CoffeeMaker'].set_off()
# You can easily rename nodes:
# await pyvlx.nodes["Window 10"].rename("Window 11")
await pyvlx.disconnect()
if __name__ == '__main__':
# pylint: disable=invalid-name
LOOP = asyncio.get_event_loop()
LOOP.run_until_complete(main(LOOP))
# LOOP.run_forever()
LOOP.close()
```
pyvlx-0.2.26/examples/ 0000775 0000000 0000000 00000000000 14734113327 0014606 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/examples/demo.py 0000664 0000000 0000000 00000002036 14734113327 0016105 0 ustar 00root root 0000000 0000000 """Just a demo of the new PyVLX module."""
import asyncio
from pyvlx import Position, PyVLX
async def main(loop):
"""Demonstrate functionality of PyVLX."""
pyvlx = PyVLX('pyvlx.yaml', loop=loop)
# Alternative:
# pyvlx = PyVLX(host="192.168.2.127", password="velux123", loop=loop)
# Runing scenes:
await pyvlx.load_scenes()
await pyvlx.scenes["All Windows Closed"].run()
# Changing position of windows:
await pyvlx.load_nodes()
await pyvlx.nodes['Bath'].open()
await pyvlx.nodes['Bath'].close()
await pyvlx.nodes['Bath'].set_position(Position(position_percent=45))
# Changing of on-off switches:
# await pyvlx.nodes['CoffeeMaker'].set_on()
# await pyvlx.nodes['CoffeeMaker'].set_off()
# You can easily rename nodes:
# await pyvlx.nodes["Window 10"].rename("Window 11")
await pyvlx.disconnect()
if __name__ == '__main__':
# pylint: disable=invalid-name
LOOP = asyncio.get_event_loop()
LOOP.run_until_complete(main(LOOP))
# LOOP.run_forever()
LOOP.close()
pyvlx-0.2.26/examples/monitor.py 0000664 0000000 0000000 00000001514 14734113327 0016650 0 ustar 00root root 0000000 0000000 """Just a demo of the new PyVLX module."""
import asyncio
import logging
from pyvlx import PYVLXLOG, PyVLX
async def main(loop):
"""Log packets from Bus."""
# Setting debug
PYVLXLOG.setLevel(logging.DEBUG)
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)
PYVLXLOG.addHandler(stream_handler)
# Connecting to KLF 200
pyvlx = PyVLX('pyvlx.yaml', loop=loop)
await pyvlx.load_scenes()
await pyvlx.load_nodes()
# and wait, increase this timeout if you want to
# log for a longer time.:)
await asyncio.sleep(90)
# Cleanup, KLF 200 is terrible in handling lost connections
await pyvlx.disconnect()
if __name__ == '__main__':
# pylint: disable=invalid-name
LOOP = asyncio.get_event_loop()
LOOP.run_until_complete(main(LOOP))
LOOP.close()
pyvlx-0.2.26/mypy.ini 0000664 0000000 0000000 00000000216 14734113327 0014466 0 ustar 00root root 0000000 0000000 [mypy]
show_error_codes = True
warn_unused_ignores = True
disallow_untyped_defs = True
disallow_untyped_calls = True
check_untyped_defs = True pyvlx-0.2.26/old_api/ 0000775 0000000 0000000 00000000000 14734113327 0014377 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/old_api/examples/ 0000775 0000000 0000000 00000000000 14734113327 0016215 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/old_api/examples/example.py 0000664 0000000 0000000 00000001274 14734113327 0020226 0 ustar 00root root 0000000 0000000 """Example implementation for PyVLX."""
import asyncio
from pyvlx import PyVLX
async def main():
"""Load devices and scenes, run first scene."""
pyvlx = PyVLX('pyvlx.yaml')
# Alternative:
# pyvlx = PyVLX(host="192.168.2.127", password="velux123", timeout=60)
await pyvlx.load_devices()
print(pyvlx.devices[1])
print(pyvlx.devices['Fenster 4'])
await pyvlx.load_scenes()
print(pyvlx.scenes[0])
print(pyvlx.scenes['Bath Closed'])
# opening/ closing windows by running scenes, yay!
await pyvlx.scenes[1].run()
await pyvlx.disconnect()
# pylint: disable=invalid-name
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
pyvlx-0.2.26/old_api/pyvlx/ 0000775 0000000 0000000 00000000000 14734113327 0015561 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/old_api/pyvlx/__init__.py 0000664 0000000 0000000 00000000626 14734113327 0017676 0 ustar 00root root 0000000 0000000 """PyVLX - controling VELUX windows with Python."""
# flake8: noqa
from .pyvlx import PyVLX
from .window import Window
from .rollershutter import RollerShutter
from .blind import Blind
from .scene import Scene
from .scenes import Scenes
from .device import Device
from .devices import Devices
from .config import Config
from .interface import Interface
from .exception import PyVLXException, InvalidToken
pyvlx-0.2.26/old_api/pyvlx/blind.py 0000664 0000000 0000000 00000002131 14734113327 0017220 0 ustar 00root root 0000000 0000000 """Module for blinds."""
from .device import Device
class Blind(Device):
"""Class for blinds."""
def __init__(self, pyvlx, ident, name, subtype, typeid):
"""Initialize blind class."""
self.pyvlx = pyvlx
self.ident = ident
self.name = name
self.subtype = subtype
self.typeid = typeid
Device.__init__(self, pyvlx, ident, name)
@classmethod
def from_config(cls, pyvlx, item):
"""Read blind from config."""
name = item['name']
ident = item['id']
subtype = item['subtype']
typeid = item['typeId']
return cls(pyvlx, ident, name, subtype, typeid)
def __str__(self) -> str:
"""Return object as readable string."""
return '' \
.format(
self.name,
self.ident,
self.subtype,
self.typeid)
def __eq__(self, other):
"""Equal operator."""
return self.__dict__ == other.__dict__
pyvlx-0.2.26/old_api/pyvlx/config.py 0000664 0000000 0000000 00000002460 14734113327 0017402 0 ustar 00root root 0000000 0000000 """Module for configuration."""
import yaml
from .exception import PyVLXException
class Config:
"""Object for configuration."""
def __init__(self, pyvlx, path=None, host=None, password=None):
"""Initialize Config class."""
self.pyvlx = pyvlx
if path is not None:
self.read_config(path)
if host is not None:
self.host = host
if password is not None:
self.password = password
def read_config(self, path):
"""Read configuration file."""
self.pyvlx.logger.info('Reading config file: ', path)
try:
with open(path, 'r') as filehandle:
doc = yaml.load(filehandle)
if 'config' not in doc:
raise PyVLXException('no element config found in: {0}'.format(path))
if 'host' not in doc['config']:
raise PyVLXException('no element host found in: {0}'.format(path))
if 'password' not in doc['config']:
raise PyVLXException('no element password found in: {0}'.format(path))
self.host = doc['config']['host']
self.password = doc['config']['password']
except FileNotFoundError as ex:
raise PyVLXException('file does not exist: {0}'.format(ex))
pyvlx-0.2.26/old_api/pyvlx/device.py 0000664 0000000 0000000 00000000723 14734113327 0017374 0 ustar 00root root 0000000 0000000 """
Module for basic object for devices.
Device objectis an interface class and should
be derived by other objects like window openers
and roller shutters.
"""
class Device:
"""Class for device abstraction."""
def __init__(self, pyvlx, ident, name):
"""Initialize Switch class."""
self.pyvlx = pyvlx
self.ident = ident
self.name = name
def get_name(self):
"""Return name of object."""
return self.name
pyvlx-0.2.26/old_api/pyvlx/devices.py 0000664 0000000 0000000 00000005055 14734113327 0017562 0 ustar 00root root 0000000 0000000 """Module for storing devices."""
import json
from .device import Device
from .window import Window
from .rollershutter import RollerShutter
from .blind import Blind
from .exception import PyVLXException
class Devices:
"""Object for storing devices."""
def __init__(self, pyvlx):
"""Initialize Devices class."""
self.pyvlx = pyvlx
self.__devices = []
def __iter__(self):
"""Iterator."""
yield from self.__devices
def __getitem__(self, key):
"""Return device by name or by index."""
for device in self.__devices:
if device.name == key:
return device
if isinstance(key, int):
return self.__devices[key]
raise KeyError
def __len__(self):
"""Return number of devices."""
return len(self.__devices)
def add(self, device):
"""Add device."""
if not isinstance(device, Device):
raise TypeError()
self.__devices.append(device)
async def load(self):
"""Load devices from KLF 200."""
json_response = await self.pyvlx.interface.api_call('products', 'get')
self.data_import(json_response)
def data_import(self, json_response):
"""Import data from json response."""
if 'data' not in json_response:
raise PyVLXException('no element data found: {0}'.format(
json.dumps(json_response)))
data = json_response['data']
for item in data:
if 'category' not in item:
raise PyVLXException('no element category: {0}'.format(
json.dumps(item)))
category = item['category']
if category == 'Window opener':
self.load_window_opener(item)
elif category in ['Roller shutter', 'Dual Shutter']:
self.load_roller_shutter(item)
elif category in ['Blind']:
self.load_blind(item)
else:
self.pyvlx.logger.warning(
'WARNING: Could not parse product: %s', category)
def load_window_opener(self, item):
"""Load window opener from JSON."""
window = Window.from_config(self.pyvlx, item)
self.add(window)
def load_roller_shutter(self, item):
"""Load roller shutter from JSON."""
rollershutter = RollerShutter.from_config(self.pyvlx, item)
self.add(rollershutter)
def load_blind(self, item):
"""Load blind from JSON."""
blind = Blind.from_config(self.pyvlx, item)
self.add(blind)
pyvlx-0.2.26/old_api/pyvlx/exception.py 0000664 0000000 0000000 00000001360 14734113327 0020131 0 ustar 00root root 0000000 0000000 """Module for exceptions."""
class PyVLXException(Exception):
"""Exception class for PyVLX library."""
def __init__(self, description):
"""Initialize exception with the given error message."""
super(PyVLXException, self).__init__()
self.description = description
def __str__(self) -> str:
"""Return object as readable string."""
return '' \
.format(self.description)
class InvalidToken(PyVLXException):
"""KLF 200 token invalid or expired."""
def __init__(self, error_code):
"""Initialize exception with the given error message."""
super(InvalidToken, self).__init__("Invalid Token")
self.error_code = error_code
pyvlx-0.2.26/old_api/pyvlx/interface.py 0000664 0000000 0000000 00000012270 14734113327 0020075 0 ustar 00root root 0000000 0000000 """Module for interface to KLF 200."""
import json
import asyncio
import aiohttp
import async_timeout
from .exception import PyVLXException, InvalidToken
class Interface:
"""Interface to KLF 200."""
def __init__(self, config, timeout=10):
"""Initialize interface class."""
self.config = config
self.token = None
self.timeout = timeout
async def api_call(self, verb, action, params=None, add_authorization_token=True, retry=False):
"""Send api call."""
if add_authorization_token and not self.token:
await self.refresh_token()
try:
return await self._api_call_impl(verb, action, params, add_authorization_token)
except InvalidToken:
if not retry and add_authorization_token:
await self.refresh_token()
# Recursive call of api_call
return await self.api_call(verb, action, params, add_authorization_token, True)
raise
async def _api_call_impl(self, verb, action, params=None, add_authorization_token=True):
url = self.create_api_url(self.config.host, verb)
body = self.create_body(action, params)
headers = self.create_headers(add_authorization_token, self.token)
return await self._do_http_request(url, body, headers)
async def _do_http_request(self, url, body, headers):
try:
return await self._do_http_request_impl(url, body, headers)
except asyncio.TimeoutError:
raise PyVLXException("Request timeout when talking to VELUX API")
except aiohttp.ClientError:
raise PyVLXException("HTTP error when talking to VELUX API")
except OSError:
raise PyVLXException("OS error when talking to VELUX API")
async def _do_http_request_impl(self, url, body, headers):
print(url, body, headers)
async with aiohttp.ClientSession() as session:
with async_timeout.timeout(self.timeout):
async with session.post(url, data=json.dumps(body), headers=headers) as response:
response = await response.text()
response = self.fix_response(response)
print(response)
json_response = json.loads(response)
self.evaluate_response(json_response)
# print(json.dumps(json_response, indent=4, sort_keys=True))
return json_response
async def refresh_token(self):
"""Refresh API token from KLF 200."""
json_response = await self.api_call('auth', 'login', {'password': self.config.password}, add_authorization_token=False)
if 'token' not in json_response:
raise PyVLXException('no element token found in response: {0}'.format(json.dumps(json_response)))
self.token = json_response['token']
async def disconnect(self):
"""Disconnect from KLF 200."""
await self.api_call('auth', 'logout', {}, add_authorization_token=True)
self.token = None
@staticmethod
def create_api_url(host, verb):
"""Return full rest url."""
return 'http://{0}/api/v1/{1}'.format(host, verb)
@staticmethod
def create_headers(add_authorization_token, token=None):
"""Create http header for rest request."""
headers = {}
headers['Content-Type'] = 'application/json'
if add_authorization_token:
headers['Authorization'] = 'Bearer ' + token
return headers
@staticmethod
def create_body(action, params):
"""Create http body for rest request."""
body = {}
body['action'] = action
if params is not None:
body['params'] = params
return body
@staticmethod
def evaluate_response(json_response):
"""Evaluate rest response."""
if 'errors' in json_response and json_response['errors']:
Interface.evaluate_errors(json_response)
elif 'result' not in json_response:
raise PyVLXException('no element result found in response: {0}'.format(json.dumps(json_response)))
elif not json_response['result']:
raise PyVLXException('Request failed {0}'.format(json.dumps(json_response)))
@staticmethod
def evaluate_errors(json_response):
"""Evaluate rest errors."""
if 'errors' not in json_response or \
not isinstance(json_response['errors'], list) or \
not json_response['errors'] or \
not isinstance(json_response['errors'][0], int):
raise PyVLXException('Could not evaluate errors {0}'.format(json.dumps(json_response)))
# unclear if response may contain more errors than one. Taking the first.
first_error = json_response['errors'][0]
if first_error in [402, 403, 405, 406]:
raise InvalidToken(first_error)
raise PyVLXException('Unknown error code {0}'.format(first_error))
@staticmethod
def fix_response(response):
"""Fix broken rest reponses."""
# WTF: For whatever reason, the KLF 200 sometimes puts an ')]}',' in front of the response ...
index = response.find('{')
if index > 0:
return response[index:]
return response
pyvlx-0.2.26/old_api/pyvlx/pyvlx.py 0000664 0000000 0000000 00000002145 14734113327 0017317 0 ustar 00root root 0000000 0000000 """
Module for PyVLX object.
PyVLX is an asynchronous library for connecting to
a VELUX KLF 200 device for controlling window openers
and roller shutters.
"""
import logging
from .config import Config
from .interface import Interface
from .devices import Devices
from .scenes import Scenes
class PyVLX:
"""Class for PyVLX."""
def __init__(self, path=None, host=None, password=None, timeout=10):
"""Initialize PyVLX class."""
self.logger = logging.getLogger('pyvlx.log')
self.config = Config(self, path, host, password)
self.interface = Interface(self.config, timeout)
self.devices = Devices(self)
self.scenes = Scenes(self)
async def connect(self):
"""Connect to KLF 200."""
await self.interface.refresh_token()
async def disconnect(self):
"""Disconnect from KLF 200."""
await self.interface.disconnect()
async def load_devices(self):
"""Load devices from KLF 200."""
await self.devices.load()
async def load_scenes(self):
"""Load scenes from KLF 200."""
await self.scenes.load()
pyvlx-0.2.26/old_api/pyvlx/rollershutter.py 0000664 0000000 0000000 00000002215 14734113327 0021051 0 ustar 00root root 0000000 0000000 """Module for roller shutters."""
from .device import Device
class RollerShutter(Device):
"""Class for roller shutters."""
def __init__(self, pyvlx, ident, name, subtype, typeid):
"""Initialize roller shutter class."""
self.pyvlx = pyvlx
self.ident = ident
self.name = name
self.subtype = subtype
self.typeid = typeid
Device.__init__(self, pyvlx, ident, name)
@classmethod
def from_config(cls, pyvlx, item):
"""Read roller shutter from config."""
name = item['name']
ident = item['id']
subtype = item['subtype']
typeid = item['typeId']
return cls(pyvlx, ident, name, subtype, typeid)
def __str__(self) -> str:
"""Return object as readable string."""
return '' \
.format(
self.name,
self.ident,
self.subtype,
self.typeid)
def __eq__(self, other):
"""Equal operator."""
return self.__dict__ == other.__dict__
pyvlx-0.2.26/old_api/pyvlx/scene.py 0000664 0000000 0000000 00000001715 14734113327 0017234 0 ustar 00root root 0000000 0000000 """Module for scene."""
class Scene:
"""Object for scene."""
def __init__(self, pyvlx, ident, name):
"""Initialize Scene object."""
self.pyvlx = pyvlx
self.ident = ident
self.name = name
@classmethod
def from_config(cls, pyvlx, item):
"""Read scene from configuration."""
name = item['name']
ident = item['id']
return cls(pyvlx, ident, name)
async def run(self):
"""Run scene."""
await self.pyvlx.interface.api_call('scenes', 'run', {'id': self.ident})
def get_name(self):
"""Return name of object."""
return self.name
def __str__(self) -> str:
"""Return object as readable string."""
return '' \
.format(
self.name,
self.ident)
def __eq__(self, other):
"""Equal operator."""
return self.__dict__ == other.__dict__
pyvlx-0.2.26/old_api/pyvlx/scenes.py 0000664 0000000 0000000 00000003042 14734113327 0017412 0 ustar 00root root 0000000 0000000 """Module for storing scenes."""
import json
from .scene import Scene
from .exception import PyVLXException
class Scenes:
"""Object for storing scenes."""
def __init__(self, pyvlx):
"""Initialize Scenes class."""
self.pyvlx = pyvlx
self.__scenes = []
def __iter__(self):
"""Iterator."""
yield from self.__scenes
def __getitem__(self, key):
"""Return scene by name or by index."""
for scene in self.__scenes:
if scene.name == key:
return scene
if isinstance(key, int):
return self.__scenes[key]
raise KeyError
def __len__(self):
"""Return number of scenes."""
return len(self.__scenes)
def add(self, scene):
"""Add scene."""
if not isinstance(scene, Scene):
raise TypeError()
self.__scenes.append(scene)
async def load(self):
"""Load scenes from KLF 200."""
json_response = await self.pyvlx.interface.api_call('scenes', 'get')
self.data_import(json_response)
def data_import(self, json_response):
"""Import scenes from JSON response."""
if 'data' not in json_response:
raise PyVLXException('no element data found: {0}'.format(
json.dumps(json_response)))
data = json_response['data']
for item in data:
self.load_scene(item)
def load_scene(self, item):
"""Load scene from json."""
scene = Scene.from_config(self.pyvlx, item)
self.add(scene)
pyvlx-0.2.26/old_api/pyvlx/window.py 0000664 0000000 0000000 00000002173 14734113327 0017445 0 ustar 00root root 0000000 0000000 """Module for window openers."""
from .device import Device
class Window(Device):
"""Class for window openers."""
def __init__(self, pyvlx, ident, name, subtype, typeid):
"""Initialize Window class."""
self.pyvlx = pyvlx
self.ident = ident
self.name = name
self.subtype = subtype
self.typeid = typeid
Device.__init__(self, pyvlx, ident, name)
@classmethod
def from_config(cls, pyvlx, item):
"""Read window opener from configuration."""
name = item['name']
ident = item['id']
subtype = item['subtype']
typeid = item['typeId']
return cls(pyvlx, ident, name, subtype, typeid)
def __str__(self) -> str:
"""Return object as readable string."""
return '' \
.format(
self.name,
self.ident,
self.subtype,
self.typeid)
def __eq__(self, other):
"""Equal operator."""
return self.__dict__ == other.__dict__
pyvlx-0.2.26/pyvlx.yaml 0000664 0000000 0000000 00000000074 14734113327 0015037 0 ustar 00root root 0000000 0000000
config:
host: "192.168.2.102"
password: "velux123"
pyvlx-0.2.26/pyvlx/ 0000775 0000000 0000000 00000000000 14734113327 0014152 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/pyvlx/__init__.py 0000664 0000000 0000000 00000001351 14734113327 0016263 0 ustar 00root root 0000000 0000000 """Module for accessing KLF 200 gateway with python."""
from .discovery import VeluxDiscovery
from .exception import PyVLXException
from .klf200gateway import Klf200Gateway
from .lightening_device import Light, LighteningDevice
from .log import PYVLXLOG
from .node import Node
from .nodes import Nodes
from .on_off_switch import OnOffSwitch
from .opening_device import (
Awning, Blade, Blind, GarageDoor, Gate, OpeningDevice, RollerShutter,
Window)
from .parameter import (
CurrentIntensity, CurrentPosition, Intensity, Parameter, Position,
SwitchParameter, SwitchParameterOff, SwitchParameterOn, UnknownIntensity,
UnknownPosition)
# flake8: noqa
from .pyvlx import PyVLX
from .scene import Scene
from .scenes import Scenes
pyvlx-0.2.26/pyvlx/api/ 0000775 0000000 0000000 00000000000 14734113327 0014723 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/pyvlx/api/__init__.py 0000664 0000000 0000000 00000001626 14734113327 0017041 0 ustar 00root root 0000000 0000000 """Module for all KLF 200 API frames."""
# flake8: noqa
from .activate_scene import ActivateScene
from .api_event import ApiEvent
from .command_send import CommandSend
from .factory_default import FactoryDefault
from .get_all_nodes_information import GetAllNodesInformation
from .get_local_time import (
FrameGetLocalTimeConfirmation, FrameGetLocalTimeRequest, GetLocalTime)
from .get_network_setup import GetNetworkSetup
from .get_node_information import GetNodeInformation
from .get_protocol_version import GetProtocolVersion
from .get_scene_list import GetSceneList
from .get_state import GetState
from .get_version import GetVersion
from .house_status_monitor import (
HouseStatusMonitorDisable, HouseStatusMonitorEnable)
from .leave_learn_state import LeaveLearnState
from .password_enter import PasswordEnter
from .reboot import Reboot
from .set_node_name import SetNodeName
from .set_utc import SetUTC
pyvlx-0.2.26/pyvlx/api/activate_scene.py 0000664 0000000 0000000 00000004603 14734113327 0020255 0 ustar 00root root 0000000 0000000 """Module for retrieving scene list from API."""
from typing import TYPE_CHECKING, Optional
from .api_event import ApiEvent
from .frames import (
ActivateSceneConfirmationStatus, FrameActivateSceneConfirmation,
FrameActivateSceneRequest, FrameBase,
FrameCommandRemainingTimeNotification, FrameCommandRunStatusNotification,
FrameSessionFinishedNotification)
from .session_id import get_new_session_id
if TYPE_CHECKING:
from pyvlx import PyVLX
class ActivateScene(ApiEvent):
"""Class for activating scene via API."""
def __init__(
self, pyvlx: "PyVLX", scene_id: int, wait_for_completion: bool = True, timeout_in_seconds: int = 60
):
"""Initialize SceneList class."""
super().__init__(pyvlx=pyvlx, timeout_in_seconds=timeout_in_seconds)
self.success = False
self.scene_id = scene_id
self.wait_for_completion = wait_for_completion
self.session_id: Optional[int] = None
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if (
isinstance(frame, FrameActivateSceneConfirmation)
and frame.session_id == self.session_id
):
if frame.status == ActivateSceneConfirmationStatus.ACCEPTED:
self.success = True
return not self.wait_for_completion
if (
isinstance(frame, FrameCommandRemainingTimeNotification)
and frame.session_id == self.session_id
):
# Ignoring FrameCommandRemainingTimeNotification
return False
if (
isinstance(frame, FrameCommandRunStatusNotification)
and frame.session_id == self.session_id
):
# At the moment I don't reall understand what the FrameCommandRunStatusNotification is good for.
# Ignoring these packets for now
return False
if (
isinstance(frame, FrameSessionFinishedNotification)
and frame.session_id == self.session_id
):
return True
return False
def request_frame(self) -> FrameActivateSceneRequest:
"""Construct initiating frame."""
self.session_id = get_new_session_id()
return FrameActivateSceneRequest(
scene_id=self.scene_id, session_id=self.session_id
)
pyvlx-0.2.26/pyvlx/api/api_event.py 0000664 0000000 0000000 00000005421 14734113327 0017251 0 ustar 00root root 0000000 0000000 """Base class for waiting for a specific answer frame from velux ap.."""
import asyncio
from typing import TYPE_CHECKING, Optional
from .frames import FrameBase
if TYPE_CHECKING:
from pyvlx import PyVLX
class ApiEvent:
"""Base class for waiting a specific frame from API connection."""
def __init__(self, pyvlx: "PyVLX", timeout_in_seconds: int = 10):
"""Initialize ApiEvent."""
self.pyvlx = pyvlx
self.response_received_or_timeout = asyncio.Event()
self.success = False
self.timeout_in_seconds = timeout_in_seconds
self.timeout_callback = None
self.timeout_handle: Optional[asyncio.TimerHandle] = None
async def do_api_call(self) -> None:
"""Start. Sending and waiting for answer."""
# We check for connection before entering the semaphore section
# because otherwise we might try to connect, which calls this, and we get stuck on
# the semaphore.
await self.pyvlx.check_connected()
if self.pyvlx.get_connected():
async with self.pyvlx.api_call_semaphore:
self.pyvlx.connection.register_frame_received_cb(self.response_rec_callback)
await self.send_frame()
await self.start_timeout()
await self.response_received_or_timeout.wait()
self.response_received_or_timeout.clear()
await self.stop_timeout()
self.pyvlx.connection.unregister_frame_received_cb(self.response_rec_callback)
else:
self.success = False
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
raise NotImplementedError("handle_frame has to be implemented")
async def send_frame(self) -> None:
"""Send frame to API connection."""
await self.pyvlx.send_frame(self.request_frame())
def request_frame(self) -> FrameBase:
"""Construct initiating frame."""
raise NotImplementedError("send_frame has to be implemented")
async def response_rec_callback(self, frame: FrameBase) -> None:
"""Handle frame. Callback from internal api connection."""
if await self.handle_frame(frame):
self.response_received_or_timeout.set()
def timeout(self) -> None:
"""Handle timeout for not having received expected frame."""
self.response_received_or_timeout.set()
async def start_timeout(self) -> None:
"""Start timeout."""
self.timeout_handle = self.pyvlx.connection.loop.call_later(
self.timeout_in_seconds, self.timeout
)
async def stop_timeout(self) -> None:
"""Stop timeout."""
if self.timeout_handle is not None:
self.timeout_handle.cancel()
pyvlx-0.2.26/pyvlx/api/command_send.py 0000664 0000000 0000000 00000006072 14734113327 0017731 0 ustar 00root root 0000000 0000000 """Module for retrieving scene list from API."""
from typing import TYPE_CHECKING, Any, Optional
from ..exception import PyVLXException
from ..parameter import Parameter
from .api_event import ApiEvent
from .frames import (
CommandSendConfirmationStatus, FrameBase,
FrameCommandRemainingTimeNotification, FrameCommandRunStatusNotification,
FrameCommandSendConfirmation, FrameCommandSendRequest,
FrameSessionFinishedNotification)
from .session_id import get_new_session_id
if TYPE_CHECKING:
from pyvlx import PyVLX
class CommandSend(ApiEvent):
"""Class for sending command to API."""
def __init__(
self,
pyvlx: "PyVLX",
node_id: int,
parameter: Parameter,
active_parameter: int = 0,
wait_for_completion: bool = True,
timeout_in_seconds: int = 2,
**functional_parameter: Any
):
"""Initialize SceneList class."""
super().__init__(pyvlx=pyvlx, timeout_in_seconds=timeout_in_seconds)
self.success = False
self.node_id = node_id
self.parameter = parameter
self.active_parameter = active_parameter
self.functional_parameter = functional_parameter
self.wait_for_completion = wait_for_completion
self.session_id: Optional[int] = None
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if (
isinstance(frame, FrameCommandSendConfirmation)
and frame.session_id == self.session_id
):
if frame.status == CommandSendConfirmationStatus.ACCEPTED:
self.success = True
return not self.wait_for_completion
if (
isinstance(frame, FrameCommandRemainingTimeNotification)
and frame.session_id == self.session_id
):
# Ignoring FrameCommandRemainingTimeNotification
return False
if (
isinstance(frame, FrameCommandRunStatusNotification)
and frame.session_id == self.session_id
):
# At the moment I don't reall understand what the FrameCommandRunStatusNotification is good for.
# Ignoring these packets for now
return False
if (
isinstance(frame, FrameSessionFinishedNotification)
and frame.session_id == self.session_id
):
return True
return False
async def send(self) -> None:
"""Send frame to KLF200."""
await self.do_api_call()
if not self.success:
raise PyVLXException("Unable to send command")
def request_frame(self) -> FrameCommandSendRequest:
"""Construct initiating frame."""
self.session_id = get_new_session_id()
return FrameCommandSendRequest(
node_ids=[self.node_id],
parameter=self.parameter,
active_parameter=self.active_parameter,
session_id=self.session_id,
**self.functional_parameter
)
pyvlx-0.2.26/pyvlx/api/factory_default.py 0000664 0000000 0000000 00000002104 14734113327 0020445 0 ustar 00root root 0000000 0000000 """Module for handling the FactoryDefault to API."""
from typing import TYPE_CHECKING
from pyvlx.log import PYVLXLOG
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameGatewayFactoryDefaultConfirmation,
FrameGatewayFactoryDefaultRequest)
if TYPE_CHECKING:
from pyvlx import PyVLX
class FactoryDefault(ApiEvent):
"""Class for handling Factory reset API."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize facotry default class."""
super().__init__(pyvlx=pyvlx)
self.pyvlx = pyvlx
self.success = False
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if isinstance(frame, FrameGatewayFactoryDefaultConfirmation):
PYVLXLOG.warning("KLF200 is factory resetting")
self.success = True
return True
return False
def request_frame(self) -> FrameGatewayFactoryDefaultRequest:
"""Construct initiating frame."""
return FrameGatewayFactoryDefaultRequest()
pyvlx-0.2.26/pyvlx/api/frame_creation.py 0000664 0000000 0000000 00000021307 14734113327 0020256 0 ustar 00root root 0000000 0000000 """Helper module for creating a frame out of raw data."""
from typing import Optional
from pyvlx.const import Command
from pyvlx.log import PYVLXLOG
from .frames import (
FrameActivateSceneConfirmation, FrameActivateSceneRequest,
FrameActivationLogUpdatedNotification, FrameBase,
FrameCommandRemainingTimeNotification, FrameCommandRunStatusNotification,
FrameCommandSendConfirmation, FrameCommandSendRequest,
FrameDiscoverNodesConfirmation, FrameDiscoverNodesNotification,
FrameDiscoverNodesRequest, FrameErrorNotification,
FrameGatewayFactoryDefaultConfirmation, FrameGatewayFactoryDefaultRequest,
FrameGatewayRebootConfirmation, FrameGatewayRebootRequest,
FrameGetAllNodesInformationConfirmation,
FrameGetAllNodesInformationFinishedNotification,
FrameGetAllNodesInformationNotification,
FrameGetAllNodesInformationRequest, FrameGetLimitationStatus,
FrameGetLimitationStatusConfirmation, FrameGetLimitationStatusNotification,
FrameGetLocalTimeConfirmation, FrameGetLocalTimeRequest,
FrameGetNetworkSetupConfirmation, FrameGetNetworkSetupRequest,
FrameGetNodeInformationConfirmation, FrameGetNodeInformationNotification,
FrameGetNodeInformationRequest, FrameGetProtocolVersionConfirmation,
FrameGetProtocolVersionRequest, FrameGetSceneListConfirmation,
FrameGetSceneListNotification, FrameGetSceneListRequest,
FrameGetStateConfirmation, FrameGetStateRequest,
FrameGetVersionConfirmation, FrameGetVersionRequest,
FrameHouseStatusMonitorDisableConfirmation,
FrameHouseStatusMonitorDisableRequest,
FrameHouseStatusMonitorEnableConfirmation,
FrameHouseStatusMonitorEnableRequest, FrameLeaveLearnStateConfirmation,
FrameLeaveLearnStateRequest, FrameNodeInformationChangedNotification,
FrameNodeStatePositionChangedNotification, FramePasswordChangeConfirmation,
FramePasswordChangeNotification, FramePasswordChangeRequest,
FramePasswordEnterConfirmation, FramePasswordEnterRequest,
FrameSessionFinishedNotification, FrameSetNodeNameConfirmation,
FrameSetNodeNameRequest, FrameSetUTCConfirmation, FrameSetUTCRequest,
FrameStatusRequestConfirmation, FrameStatusRequestNotification,
FrameStatusRequestRequest, extract_from_frame)
def frame_from_raw(raw: bytes) -> Optional[FrameBase]:
"""Create and return frame from raw bytes."""
command, payload = extract_from_frame(raw)
frame = create_frame(command)
if frame is None:
PYVLXLOG.warning(
"Command %s not implemented, raw: %s",
command,
":".join("{:02x}".format(c) for c in raw),
)
return None
frame.validate_payload_len(payload)
frame.from_payload(payload)
return frame
def create_frame(command: Command) -> Optional[FrameBase]:
"""Create and return empty Frame from Command."""
# pylint: disable=too-many-branches,too-many-return-statements,too-many-statements
if command == Command.GW_ERROR_NTF:
return FrameErrorNotification()
if command == Command.GW_COMMAND_SEND_REQ:
return FrameCommandSendRequest()
if command == Command.GW_COMMAND_SEND_CFM:
return FrameCommandSendConfirmation()
if command == Command.GW_COMMAND_RUN_STATUS_NTF:
return FrameCommandRunStatusNotification()
if command == Command.GW_COMMAND_REMAINING_TIME_NTF:
return FrameCommandRemainingTimeNotification()
if command == Command.GW_SESSION_FINISHED_NTF:
return FrameSessionFinishedNotification()
if command == Command.GW_PASSWORD_ENTER_REQ:
return FramePasswordEnterRequest()
if command == Command.GW_PASSWORD_ENTER_CFM:
return FramePasswordEnterConfirmation()
if command == Command.GW_PASSWORD_CHANGE_REQ:
return FramePasswordChangeRequest()
if command == Command.GW_PASSWORD_CHANGE_CFM:
return FramePasswordChangeConfirmation()
if command == Command.GW_PASSWORD_CHANGE_NTF:
return FramePasswordChangeNotification()
if command == Command.GW_REBOOT_REQ:
return FrameGatewayRebootRequest()
if command == Command.GW_REBOOT_CFM:
return FrameGatewayRebootConfirmation()
if command == Command.GW_SET_FACTORY_DEFAULT_REQ:
return FrameGatewayFactoryDefaultRequest()
if command == Command.GW_SET_FACTORY_DEFAULT_CFM:
return FrameGatewayFactoryDefaultConfirmation()
if command == Command.GW_GET_LOCAL_TIME_REQ:
return FrameGetLocalTimeRequest()
if command == Command.GW_GET_LOCAL_TIME_CFM:
return FrameGetLocalTimeConfirmation()
if command == Command.GW_CS_DISCOVER_NODES_REQ:
return FrameDiscoverNodesRequest()
if command == Command.GW_CS_DISCOVER_NODES_CFM:
return FrameDiscoverNodesConfirmation()
if command == Command.GW_CS_DISCOVER_NODES_NTF:
return FrameDiscoverNodesNotification()
if command == Command.GW_GET_SCENE_LIST_REQ:
return FrameGetSceneListRequest()
if command == Command.GW_GET_SCENE_LIST_CFM:
return FrameGetSceneListConfirmation()
if command == Command.GW_GET_SCENE_LIST_NTF:
return FrameGetSceneListNotification()
if command == Command.GW_GET_NODE_INFORMATION_REQ:
return FrameGetNodeInformationRequest()
if command == Command.GW_GET_NODE_INFORMATION_CFM:
return FrameGetNodeInformationConfirmation()
if command == Command.GW_GET_NODE_INFORMATION_NTF:
return FrameGetNodeInformationNotification()
if command == Command.GW_GET_ALL_NODES_INFORMATION_REQ:
return FrameGetAllNodesInformationRequest()
if command == Command.GW_GET_ALL_NODES_INFORMATION_CFM:
return FrameGetAllNodesInformationConfirmation()
if command == Command.GW_GET_ALL_NODES_INFORMATION_NTF:
return FrameGetAllNodesInformationNotification()
if command == Command.GW_GET_ALL_NODES_INFORMATION_FINISHED_NTF:
return FrameGetAllNodesInformationFinishedNotification()
if command == Command.GW_ACTIVATE_SCENE_REQ:
return FrameActivateSceneRequest()
if command == Command.GW_ACTIVATE_SCENE_CFM:
return FrameActivateSceneConfirmation()
if command == Command.GW_GET_VERSION_REQ:
return FrameGetVersionRequest()
if command == Command.GW_GET_VERSION_CFM:
return FrameGetVersionConfirmation()
if command == Command.GW_GET_PROTOCOL_VERSION_REQ:
return FrameGetProtocolVersionRequest()
if command == Command.GW_GET_PROTOCOL_VERSION_CFM:
return FrameGetProtocolVersionConfirmation()
if command == Command.GW_SET_NODE_NAME_REQ:
return FrameSetNodeNameRequest()
if command == Command.GW_SET_NODE_NAME_CFM:
return FrameSetNodeNameConfirmation()
if command == Command.GW_NODE_INFORMATION_CHANGED_NTF:
return FrameNodeInformationChangedNotification()
if command == Command.GW_GET_STATE_REQ:
return FrameGetStateRequest()
if command == Command.GW_GET_STATE_CFM:
return FrameGetStateConfirmation()
if command == Command.GW_GET_LIMITATION_STATUS_REQ:
return FrameGetLimitationStatus()
if command == Command.GW_GET_LIMITATION_STATUS_CFM:
return FrameGetLimitationStatusConfirmation()
if command == Command.GW_LIMITATION_STATUS_NTF:
return FrameGetLimitationStatusNotification()
if command == Command.GW_GET_NETWORK_SETUP_REQ:
return FrameGetNetworkSetupRequest()
if command == Command.GW_GET_NETWORK_SETUP_CFM:
return FrameGetNetworkSetupConfirmation()
if command == Command.GW_SET_UTC_REQ:
return FrameSetUTCRequest()
if command == Command.GW_SET_UTC_CFM:
return FrameSetUTCConfirmation()
if command == Command.GW_ACTIVATION_LOG_UPDATED_NTF:
return FrameActivationLogUpdatedNotification()
if command == Command.GW_HOUSE_STATUS_MONITOR_ENABLE_REQ:
return FrameHouseStatusMonitorEnableRequest()
if command == Command.GW_HOUSE_STATUS_MONITOR_ENABLE_CFM:
return FrameHouseStatusMonitorEnableConfirmation()
if command == Command.GW_HOUSE_STATUS_MONITOR_DISABLE_REQ:
return FrameHouseStatusMonitorDisableRequest()
if command == Command.GW_HOUSE_STATUS_MONITOR_DISABLE_CFM:
return FrameHouseStatusMonitorDisableConfirmation()
if command == Command.GW_NODE_STATE_POSITION_CHANGED_NTF:
return FrameNodeStatePositionChangedNotification()
if command == Command.GW_LEAVE_LEARN_STATE_CFM:
return FrameLeaveLearnStateConfirmation()
if command == Command.GW_LEAVE_LEARN_STATE_REQ:
return FrameLeaveLearnStateRequest()
if command == Command.GW_STATUS_REQUEST_REQ:
return FrameStatusRequestRequest()
if command == Command.GW_STATUS_REQUEST_CFM:
return FrameStatusRequestConfirmation()
if command == Command.GW_STATUS_REQUEST_NTF:
return FrameStatusRequestNotification()
return None
pyvlx-0.2.26/pyvlx/api/frames/ 0000775 0000000 0000000 00000000000 14734113327 0016200 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/pyvlx/api/frames/__init__.py 0000664 0000000 0000000 00000007067 14734113327 0020323 0 ustar 00root root 0000000 0000000 """Module for all KLF 200 API frames."""
from .alias_array import AliasArray
# flake8: noqa
from .frame import FrameBase
from .frame_activate_scene import (
ActivateSceneConfirmationStatus, FrameActivateSceneConfirmation,
FrameActivateSceneRequest)
from .frame_activation_log_updated import FrameActivationLogUpdatedNotification
from .frame_command_send import (
CommandSendConfirmationStatus, FrameCommandRemainingTimeNotification,
FrameCommandRunStatusNotification, FrameCommandSendConfirmation,
FrameCommandSendRequest, FrameSessionFinishedNotification)
from .frame_discover_nodes import (
FrameDiscoverNodesConfirmation, FrameDiscoverNodesNotification,
FrameDiscoverNodesRequest)
from .frame_error_notification import ErrorType, FrameErrorNotification
from .frame_facory_default import (
FrameGatewayFactoryDefaultConfirmation, FrameGatewayFactoryDefaultRequest)
from .frame_get_all_nodes_information import (
FrameGetAllNodesInformationConfirmation,
FrameGetAllNodesInformationFinishedNotification,
FrameGetAllNodesInformationNotification,
FrameGetAllNodesInformationRequest)
from .frame_get_limitation import (
FrameGetLimitationStatus, FrameGetLimitationStatusConfirmation,
FrameGetLimitationStatusNotification)
from .frame_get_local_time import (
FrameGetLocalTimeConfirmation, FrameGetLocalTimeRequest)
from .frame_get_network_setup import (
DHCPParameter, FrameGetNetworkSetupConfirmation,
FrameGetNetworkSetupRequest)
from .frame_get_node_information import (
FrameGetNodeInformationConfirmation, FrameGetNodeInformationNotification,
FrameGetNodeInformationRequest)
from .frame_get_protocol_version import (
FrameGetProtocolVersionConfirmation, FrameGetProtocolVersionRequest)
from .frame_get_scene_list import (
FrameGetSceneListConfirmation, FrameGetSceneListNotification,
FrameGetSceneListRequest)
from .frame_get_state import (
FrameGetStateConfirmation, FrameGetStateRequest, GatewayState,
GatewaySubState)
from .frame_get_version import (
FrameGetVersionConfirmation, FrameGetVersionRequest)
from .frame_helper import calc_crc, extract_from_frame
from .frame_house_status_monitor_disable_cfm import (
FrameHouseStatusMonitorDisableConfirmation)
from .frame_house_status_monitor_disable_req import (
FrameHouseStatusMonitorDisableRequest)
from .frame_house_status_monitor_enable_cfm import (
FrameHouseStatusMonitorEnableConfirmation)
from .frame_house_status_monitor_enable_req import (
FrameHouseStatusMonitorEnableRequest)
from .frame_leave_learn_state import (
FrameLeaveLearnStateConfirmation, FrameLeaveLearnStateRequest,
LeaveLearnStateConfirmationStatus)
from .frame_node_information_changed import (
FrameNodeInformationChangedNotification)
from .frame_node_state_position_changed_notification import (
FrameNodeStatePositionChangedNotification)
from .frame_password_change import (
FramePasswordChangeConfirmation, FramePasswordChangeNotification,
FramePasswordChangeRequest, PasswordChangeConfirmationStatus)
from .frame_password_enter import (
FramePasswordEnterConfirmation, FramePasswordEnterRequest,
PasswordEnterConfirmationStatus)
from .frame_reboot import (
FrameGatewayRebootConfirmation, FrameGatewayRebootRequest)
from .frame_set_node_name import (
FrameSetNodeNameConfirmation, FrameSetNodeNameRequest,
SetNodeNameConfirmationStatus)
from .frame_set_utc import FrameSetUTCConfirmation, FrameSetUTCRequest
from .frame_status_request import (
FrameStatusRequestConfirmation, FrameStatusRequestNotification,
FrameStatusRequestRequest)
pyvlx-0.2.26/pyvlx/api/frames/alias_array.py 0000664 0000000 0000000 00000003055 14734113327 0021044 0 ustar 00root root 0000000 0000000 """Module for storing alias array."""
from typing import List, Optional, Tuple
from pyvlx.exception import PyVLXException
class AliasArray:
"""Object for storing alias array."""
def __init__(self, raw: Optional[bytes] = None):
"""Initialize alias array."""
self.alias_array_: List[Tuple[bytes, bytes]] = []
if raw is not None:
self.parse_raw(raw)
def __str__(self) -> str:
"""Return human readable string."""
return ", ".join(
"{:02x}{:02x}={:02x}{:02x}".format(c[0][0], c[0][1], c[1][0], c[1][1])
for c in self.alias_array_
)
def __bytes__(self) -> bytes:
"""Get raw bytes of alias array."""
ret = bytes([len(self.alias_array_)])
for alias in self.alias_array_:
ret += alias[0] + alias[1]
ret += bytes((5 - len(self.alias_array_)) * 4)
return ret
def parse_raw(self, raw: bytes) -> None:
"""Parse alias array from raw bytes."""
if not isinstance(raw, bytes):
raise PyVLXException("AliasArray::invalid_type_if_raw", type_raw=type(raw))
if len(raw) != 21:
raise PyVLXException("AliasArray::invalid_size", size=len(raw))
nbr_of_alias = raw[0]
if nbr_of_alias > 5:
raise PyVLXException(
"AliasArray::invalid_nbr_of_alias", nbr_of_alias=nbr_of_alias
)
for i in range(0, nbr_of_alias):
self.alias_array_.append(
(raw[i * 4 + 1 : i * 4 + 3], raw[i * 4 + 3 : i * 4 + 5])
)
pyvlx-0.2.26/pyvlx/api/frames/frame.py 0000664 0000000 0000000 00000003320 14734113327 0017642 0 ustar 00root root 0000000 0000000 """Module for Frames."""
import struct
from pyvlx.const import Command
from pyvlx.exception import PyVLXException
from .frame_helper import calc_crc
class FrameBase:
"""Class for Base Frame."""
def __init__(self, command: Command):
"""Initialize Base Frame."""
self.command = command
def __bytes__(self) -> bytes:
"""Get raw bytes of Frame."""
payload = self.get_payload()
self.validate_payload_len(payload)
return self.build_frame(self.command, payload)
def validate_payload_len(self, payload: bytes) -> None:
"""Validate payload len."""
if not hasattr(self, "PAYLOAD_LEN"):
# No fixed payload len, e.g. within FrameGetSceneListNotification
return
# pylint: disable=no-member
if len(payload) != self.PAYLOAD_LEN:
raise PyVLXException(
"Invalid payload len",
expected_len=self.PAYLOAD_LEN,
current_len=len(payload),
frame_type=type(self).__name__,
)
def get_payload(self) -> bytes:
"""Return Payload."""
return b""
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
def __str__(self) -> str:
"""Return human readable string."""
return "<{}/>".format(type(self).__name__)
@staticmethod
def build_frame(command: Command, payload: bytes) -> bytes:
"""Build raw bytes from command and payload."""
packet_length = 2 + len(payload) + 1
ret = struct.pack("BB", 0, packet_length)
ret += struct.pack(">H", command.value)
ret += payload
ret += struct.pack("B", calc_crc(ret))
return ret
pyvlx-0.2.26/pyvlx/api/frames/frame_activate_scene.py 0000664 0000000 0000000 00000006041 14734113327 0022702 0 ustar 00root root 0000000 0000000 """Module for sending command to gw."""
from enum import Enum
from typing import Optional
from pyvlx.const import Command, Originator, Priority, Velocity
from .frame import FrameBase
class FrameActivateSceneRequest(FrameBase):
"""Frame for sending command to gw."""
PAYLOAD_LEN = 6
def __init__(
self,
scene_id: Optional[int] = None,
session_id: Optional[int] = None,
originator: Originator = Originator.USER,
velocity: Velocity = Velocity.DEFAULT,
):
"""Init Frame."""
super().__init__(Command.GW_ACTIVATE_SCENE_REQ)
self.scene_id = scene_id
self.session_id = session_id
self.originator = originator
self.priority = Priority.USER_LEVEL_2
self.velocity = velocity
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.session_id is not None
assert self.scene_id is not None
ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
ret += bytes([self.originator.value])
ret += bytes([self.priority.value])
ret += bytes([self.scene_id])
ret += bytes([self.velocity.value])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.session_id = payload[0] * 256 + payload[1]
self.originator = Originator(payload[2])
self.priority = Priority(payload[3])
self.scene_id = payload[4]
self.velocity = Velocity(payload[5])
def __str__(self) -> str:
"""Return human readable string."""
return '<{} scene_id="{}" session_id="{}" originator="{}" velocity="{}"/>'.format(
type(self).__name__, self.scene_id, self.session_id, self.originator, self.velocity
)
class ActivateSceneConfirmationStatus(Enum):
"""Enum class for status of command send confirmation."""
ACCEPTED = 0
ERROR_INVALID_PARAMETER = 1
ERROR_REQUEST_REJECTED = 2
class FrameActivateSceneConfirmation(FrameBase):
"""Frame for confirmation of command send frame."""
PAYLOAD_LEN = 3
def __init__(self, session_id: Optional[int] = None, status: Optional[ActivateSceneConfirmationStatus] = None):
"""Init Frame."""
super().__init__(Command.GW_ACTIVATE_SCENE_CFM)
self.session_id = session_id
self.status = status
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.status is not None
assert self.session_id is not None
ret = bytes([self.status.value])
ret += bytes([self.session_id >> 8 & 255, self.session_id & 255])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.status = ActivateSceneConfirmationStatus(payload[0])
self.session_id = payload[1] * 256 + payload[2]
def __str__(self) -> str:
"""Return human readable string."""
return '<{} session_id="{}" status="{}"/>'.format(
type(self).__name__, self.session_id, self.status
)
pyvlx-0.2.26/pyvlx/api/frames/frame_activation_log_updated.py 0000664 0000000 0000000 00000000525 14734113327 0024436 0 ustar 00root root 0000000 0000000 """Module for error notification."""
from pyvlx.const import Command
from .frame import FrameBase
class FrameActivationLogUpdatedNotification(FrameBase):
"""Frame for error notification."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_ACTIVATION_LOG_UPDATED_NTF)
pyvlx-0.2.26/pyvlx/api/frames/frame_command_send.py 0000664 0000000 0000000 00000023655 14734113327 0022366 0 ustar 00root root 0000000 0000000 """Module for sending command to gw."""
from enum import Enum
from typing import List, Optional
from pyvlx.const import Command, Originator, Priority
from pyvlx.exception import PyVLXException
from pyvlx.parameter import Parameter, Position
from .frame import FrameBase
class FrameCommandSendRequest(FrameBase):
"""Frame for sending command to gw."""
PAYLOAD_LEN = 66
def __init__(
self,
node_ids: Optional[List[int]] = None,
parameter: Parameter = Parameter(),
active_parameter: int = 0,
session_id: Optional[int] = None,
originator: Originator = Originator.USER,
**functional_parameter: bytes
):
"""Init Frame."""
super().__init__(Command.GW_COMMAND_SEND_REQ)
self.node_ids = node_ids if node_ids is not None else []
self.parameter = parameter
self.active_parameter = active_parameter
self.fpi1 = 0
self.fpi2 = 0
self.functional_parameter = {}
self.session_id = session_id
self.originator = originator
self.priority = Priority.USER_LEVEL_2
"""Set the functional parameter indicator bytes in order to show which functional
parameters are included in the frame. Functional parameter dictionary will be checked
for keys 'fp1' to 'fp16' to set the appropriate indicator and the corresponding
self.functional_parameter."""
for i in range(1, 17):
key = "fp%s" % (i)
if key in functional_parameter:
self.functional_parameter[key] = functional_parameter[key]
if i < 9:
self.fpi1 += 2 ** (8 - i)
if i >= 9:
self.fpi2 += 2 ** (16 - i)
else:
self.functional_parameter[key] = bytes(2)
def get_payload(self) -> bytes:
"""Return Payload."""
# Session id
assert self.session_id is not None
ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
ret += bytes([self.originator.value])
ret += bytes([self.priority.value])
ret += bytes(
[self.active_parameter]
) # ParameterActive pointing to main parameter (MP)
# FPI 1+2
ret += bytes([self.fpi1])
ret += bytes([self.fpi2])
# Main parameter + functional parameter fp1 to fp3
ret += bytes(self.parameter)
ret += bytes(self.functional_parameter["fp1"])
ret += bytes(self.functional_parameter["fp2"])
ret += bytes(self.functional_parameter["fp3"])
# Functional parameter fp4 to fp16
ret += bytes(26)
# Nodes array: Number of nodes + node array + padding
ret += bytes([len(self.node_ids)]) # index array count
ret += bytes(self.node_ids) + bytes(20 - len(self.node_ids))
# Priority Level Lock
ret += bytes([0])
# Priority Level information 1+2
ret += bytes([0, 0])
# Locktime
ret += bytes([0])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.session_id = payload[0] * 256 + payload[1]
self.originator = Originator(payload[2])
self.priority = Priority(payload[3])
len_node_ids = payload[41]
if len_node_ids > 20:
raise PyVLXException("command_send_request_wrong_node_length")
self.node_ids = []
for i in range(len_node_ids):
self.node_ids.append(payload[42] + i)
self.parameter = Parameter(payload[7:9])
def __str__(self) -> str:
"""Return human readable string."""
functional_parameter = ""
for key, value in self.functional_parameter.items():
functional_parameter += "%s: %s, " % (
str(key),
Position(Parameter(bytes(value))),
)
return (
'<{} node_ids="{}" active_parameter="{}" parameter="{}" functional_parameter="{}" '
'session_id="{}" originator="{}"/>'.format(
type(self).__name__, self.node_ids, self.active_parameter,
self.parameter, functional_parameter,
self.session_id, self.originator,
)
)
class CommandSendConfirmationStatus(Enum):
"""Enum class for status of command send confirmation."""
REJECTED = 0
ACCEPTED = 1
class FrameCommandSendConfirmation(FrameBase):
"""Frame for confirmation of command send frame."""
PAYLOAD_LEN = 3
def __init__(self, session_id: Optional[int] = None, status: Optional[CommandSendConfirmationStatus] = None):
"""Init Frame."""
super().__init__(Command.GW_COMMAND_SEND_CFM)
self.session_id = session_id
self.status = status
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.session_id is not None
assert self.status is not None
ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
ret += bytes([self.status.value])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.session_id = payload[0] * 256 + payload[1]
self.status = CommandSendConfirmationStatus(payload[2])
def __str__(self) -> str:
"""Return human readable string."""
return '<{} session_id="{}" status="{}"/>'.format(
type(self).__name__, self.session_id, self.status
)
class FrameCommandRunStatusNotification(FrameBase):
"""Frame for run status notification in scope of command send frame."""
PAYLOAD_LEN = 13
def __init__(
self,
session_id: Optional[int] = None,
status_id: Optional[int] = None,
index_id: Optional[int] = None,
node_parameter: Optional[int] = None,
parameter_value: Optional[int] = None,
):
"""Init Frame."""
super().__init__(Command.GW_COMMAND_RUN_STATUS_NTF)
self.session_id = session_id
self.status_id = status_id
self.index_id = index_id
self.node_parameter = node_parameter
self.parameter_value = parameter_value
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.session_id is not None
ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
assert self.status_id is not None
ret += bytes([self.status_id])
assert self.index_id is not None
ret += bytes([self.index_id])
assert self.node_parameter is not None
ret += bytes([self.node_parameter])
assert self.parameter_value is not None
ret += bytes([self.parameter_value >> 8 & 255, self.parameter_value & 255])
# XXX: Missing implementation of run_status, status_reply and information_code
ret += bytes(6)
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.session_id = payload[0] * 256 + payload[1]
self.status_id = payload[2]
self.index_id = payload[3]
self.node_parameter = payload[4]
self.parameter_value = payload[5] * 256 + payload[6]
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} session_id="{}" status_id="{}" '
'index_id="{}" node_parameter="{}" parameter_value="{}"/>'.format(
type(self).__name__, self.session_id,
self.status_id, self.index_id,
self.node_parameter, self.parameter_value
)
)
class FrameCommandRemainingTimeNotification(FrameBase):
"""Frame for notification of remaining time in scope of command send frame."""
PAYLOAD_LEN = 6
def __init__(self, session_id: Optional[int] = None, index_id: Optional[int] = None, node_parameter: Optional[int] = None, seconds: int = 0):
"""Init Frame."""
super().__init__(Command.GW_COMMAND_REMAINING_TIME_NTF)
self.session_id = session_id
self.index_id = index_id
self.node_parameter = node_parameter
self.seconds = seconds
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.session_id is not None
ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
assert self.index_id is not None
ret += bytes([self.index_id])
assert self.node_parameter is not None
ret += bytes([self.node_parameter])
ret += bytes([self.seconds >> 8 & 255, self.seconds & 255])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.session_id = payload[0] * 256 + payload[1]
self.index_id = payload[2]
self.node_parameter = payload[3]
self.seconds = payload[4] * 256 + payload[5]
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} session_id="{}" index_id="{}" '
'node_parameter="{}" seconds="{}"/>'.format(
type(self).__name__, self.session_id,
self.index_id, self.node_parameter, self.seconds
)
)
class FrameSessionFinishedNotification(FrameBase):
"""Frame for notification of session finishid in scope of command send frame."""
PAYLOAD_LEN = 2
def __init__(self, session_id: Optional[int] = None):
"""Init Frame."""
super().__init__(Command.GW_SESSION_FINISHED_NTF)
self.session_id = session_id
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.session_id is not None
ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.session_id = payload[0] * 256 + payload[1]
def __str__(self) -> str:
"""Return human readable string."""
return '<{} session_id="{}"/>'.format(
type(self).__name__, self.session_id
)
pyvlx-0.2.26/pyvlx/api/frames/frame_discover_nodes.py 0000664 0000000 0000000 00000003411 14734113327 0022731 0 ustar 00root root 0000000 0000000 """Module for discover nodes requests."""
from pyvlx.const import Command, NodeType
from .frame import FrameBase
class FrameDiscoverNodesRequest(FrameBase):
"""Frame for discover nodes request."""
PAYLOAD_LEN = 1
def __init__(self, node_type: NodeType = NodeType.NO_TYPE):
"""Init Frame."""
super().__init__(Command.GW_CS_DISCOVER_NODES_REQ)
self.node_type = node_type
def get_payload(self) -> bytes:
"""Return Payload."""
ret = bytes([self.node_type.value])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.node_type = NodeType(payload[0])
def __str__(self) -> str:
"""Return human readable string."""
return '<{} node_type="{}"/>'.format(type(self).__name__, self.node_type)
class FrameDiscoverNodesConfirmation(FrameBase):
"""Frame for discover nodes confirmation."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_CS_DISCOVER_NODES_CFM)
class FrameDiscoverNodesNotification(FrameBase):
"""Frame for discover nodes notification."""
PAYLOAD_LEN = 131
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_CS_DISCOVER_NODES_NTF)
self.payload = b"\0" * 131
def get_payload(self) -> bytes:
"""Return Payload."""
return self.payload
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.payload = payload
def __str__(self) -> str:
"""Return human readable string."""
return '<{} payload="{}"/>'.format(
type(self).__name__,
':'.join('{:02x}'.format(c) for c in self.payload)
)
pyvlx-0.2.26/pyvlx/api/frames/frame_error_notification.py 0000664 0000000 0000000 00000002110 14734113327 0023615 0 ustar 00root root 0000000 0000000 """Module for error notification."""
from enum import Enum
from pyvlx.const import Command
from .frame import FrameBase
class ErrorType(Enum):
# pylint: disable=invalid-name
"""Enum class for error types."""
NotFurtherDefined = 0
UnknownCommand = 1
ErrorOnFrameStructure = 2
BusBusy = 7
BadSystemTableIndex = 8
NotAuthenticated = 12
class FrameErrorNotification(FrameBase):
"""Frame for error notification."""
PAYLOAD_LEN = 1
def __init__(self, error_type: ErrorType = ErrorType.NotFurtherDefined):
"""Init Frame."""
super().__init__(Command.GW_ERROR_NTF)
self.error_type = error_type
def get_payload(self) -> bytes:
"""Return Payload."""
ret = bytes([self.error_type.value])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.error_type = ErrorType(payload[0])
def __str__(self) -> str:
"""Return human readable string."""
return '<{} error_type="{}"/>'.format(type(self).__name__, self.error_type)
pyvlx-0.2.26/pyvlx/api/frames/frame_facory_default.py 0000664 0000000 0000000 00000001512 14734113327 0022712 0 ustar 00root root 0000000 0000000 """Module for reboot frame classes."""
from pyvlx.const import Command
from .frame import FrameBase
class FrameGatewayFactoryDefaultRequest(FrameBase):
"""Frame for requesting factory reset."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_SET_FACTORY_DEFAULT_REQ)
def __str__(self) -> str:
"""Return human readable string."""
return '<{}/>'.format(type(self).__name__)
class FrameGatewayFactoryDefaultConfirmation(FrameBase):
"""Frame for response for factory reset."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_SET_FACTORY_DEFAULT_CFM)
def __str__(self) -> str:
"""Return human readable string."""
return '<{}/>'.format(type(self).__name__)
pyvlx-0.2.26/pyvlx/api/frames/frame_get_all_nodes_information.py 0000664 0000000 0000000 00000017742 14734113327 0025143 0 ustar 00root root 0000000 0000000 """Module for get all node information from gateway."""
import struct
from datetime import datetime
from enum import Enum
from typing import Optional
from pyvlx.const import (
Command, NodeTypeWithSubtype, NodeVariation, OperatingState, Velocity)
from pyvlx.exception import PyVLXException
from pyvlx.parameter import Parameter
from pyvlx.string_helper import bytes_to_string, string_to_bytes
from .alias_array import AliasArray
from .frame import FrameBase
class FrameGetAllNodesInformationRequest(FrameBase):
"""Frame for get node information request."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_GET_ALL_NODES_INFORMATION_REQ)
class AllNodesInformationStatus(Enum):
# pylint: disable=invalid-name
"""Enum for node information status."""
OK = 0
Error_System_Table_Empty = 1
class FrameGetAllNodesInformationConfirmation(FrameBase):
"""Frame for confirmation for node information request."""
PAYLOAD_LEN = 2
def __init__(
self,
status: AllNodesInformationStatus = AllNodesInformationStatus.OK,
number_of_nodes: int = 0,
):
"""Init Frame."""
super().__init__(Command.GW_GET_ALL_NODES_INFORMATION_CFM)
self.status = status
self.number_of_nodes = number_of_nodes
def get_payload(self) -> bytes:
"""Return Payload."""
return bytes([self.status.value, self.number_of_nodes])
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.status = AllNodesInformationStatus(payload[0])
self.number_of_nodes = payload[1]
def __str__(self) -> str:
"""Return human readable string."""
return '<{} status="{}" number_of_nodes="{}"/>'.format(
type(self).__name__, self.status, self.number_of_nodes
)
class FrameGetAllNodesInformationNotification(FrameBase):
"""Frame for notification of all nodes information request."""
PAYLOAD_LEN = 124
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_GET_ALL_NODES_INFORMATION_NTF)
self.node_id = 0
self.order = 0
self.placement = 0
self.name = ""
self.velocity = Velocity.DEFAULT
self.node_type = NodeTypeWithSubtype.NO_TYPE
self.product_group = 0
self.product_type = 0
self.node_variation = NodeVariation.NOT_SET
self.power_mode = 0
self.build_number = 0
self._serial_number = bytes(8)
self.state = OperatingState.UNKNOWN
self.current_position = Parameter()
self.target = Parameter()
self.current_position_fp1 = Parameter()
self.current_position_fp2 = Parameter()
self.current_position_fp3 = Parameter()
self.current_position_fp4 = Parameter()
self.remaining_time = 0
self.timestamp = 0
self.alias_array = AliasArray()
@property
def serial_number(self) -> Optional[str]:
"""Property for serial number in a human readable way."""
if self._serial_number == bytes(8):
return None
return ":".join("{:02x}".format(c) for c in self._serial_number)
@serial_number.setter
def serial_number(self, serial_number: str) -> None:
"""Set serial number."""
if serial_number is None:
self._serial_number = bytes(8)
return
self._serial_number = b""
for elem in serial_number.split(":"):
self._serial_number += bytes.fromhex(elem)
if len(self._serial_number) != 8:
raise PyVLXException("could_not_parse_serial_number")
def get_payload(self) -> bytes:
"""Return Payload."""
payload = bytes()
payload += bytes([self.node_id])
payload += bytes([self.order >> 8 & 255, self.order & 255])
payload += bytes([self.placement])
payload += bytes(string_to_bytes(self.name, 64))
payload += bytes([self.velocity.value])
payload += bytes([self.node_type.value >> 8 & 255, self.node_type.value & 255])
payload += bytes([self.product_group])
payload += bytes([self.product_type])
payload += bytes([self.node_variation.value])
payload += bytes([self.power_mode])
payload += bytes([self.build_number])
payload += bytes(self._serial_number)
payload += bytes([self.state.value])
payload += bytes(self.current_position.raw)
payload += bytes(self.target.raw)
payload += bytes(self.current_position_fp1.raw)
payload += bytes(self.current_position_fp2.raw)
payload += bytes(self.current_position_fp3.raw)
payload += bytes(self.current_position_fp4.raw)
payload += bytes([self.remaining_time >> 8 & 255, self.remaining_time & 255])
payload += struct.pack(">I", self.timestamp)
payload += bytes(self.alias_array)
return payload
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.node_id = payload[0]
self.order = payload[1] * 256 + payload[2]
self.placement = payload[3]
self.name = bytes_to_string(payload[4:68])
self.velocity = Velocity(payload[68])
self.node_type = NodeTypeWithSubtype(payload[69] * 256 + payload[70])
self.product_group = payload[71]
self.product_type = payload[72]
self.node_variation = NodeVariation(payload[73])
self.power_mode = payload[74]
self.build_number = payload[75]
self._serial_number = payload[76:84]
self.state = OperatingState(payload[84])
self.current_position = Parameter(payload[85:87])
self.target = Parameter(payload[87:89])
self.current_position_fp1 = Parameter(payload[89:91])
self.current_position_fp2 = Parameter(payload[91:93])
self.current_position_fp3 = Parameter(payload[93:95])
self.current_position_fp4 = Parameter(payload[95:97])
self.remaining_time = payload[97] * 256 + payload[98]
self.timestamp = struct.unpack(">I", payload[99:103])[0]
self.alias_array = AliasArray(payload[103:125])
@property
def timestamp_formatted(self) -> str:
"""Return time as human readable string."""
return datetime.fromtimestamp(self.timestamp).strftime("%Y-%m-%d %H:%M:%S")
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} node_id="{}" order="{}" '
'placement="{}" name="{}" velocity="{}" node_type="{}" product_group="{}" '
'product_type="{}" node_variation="{}" power_mode="{}" build_number="{}" '
'serial_number="{}" state="{}" current_position="{}" '
'target="{}" current_position_fp1="{}" current_position_fp2="{}" '
'current_position_fp3="{}" current_position_fp4="{}" '
'remaining_time="{}" time="{}" alias_array="{}"/>'.format(
type(self).__name__,
self.node_id,
self.order,
self.placement,
self.name,
self.velocity,
self.node_type,
self.product_group,
self.product_type,
self.node_variation,
self.power_mode,
self.build_number,
self.serial_number,
self.state.name,
self.current_position,
self.target,
self.current_position_fp1,
self.current_position_fp2,
self.current_position_fp3,
self.current_position_fp4,
self.remaining_time,
self.timestamp_formatted,
self.alias_array,
)
)
class FrameGetAllNodesInformationFinishedNotification(FrameBase):
"""Frame for notification of all nodes information finished notification."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_GET_ALL_NODES_INFORMATION_FINISHED_NTF)
pyvlx-0.2.26/pyvlx/api/frames/frame_get_limitation.py 0000664 0000000 0000000 00000010761 14734113327 0022741 0 ustar 00root root 0000000 0000000
"""Module for get local time classes."""
from typing import List, Optional
from pyvlx.const import Command, LimitationType, Originator, Priority
from .frame import FrameBase
class FrameGetLimitationStatus(FrameBase):
"""Frame for requesting limitation status."""
PAYLOAD_LEN = 25
def __init__(self,
node_ids: Optional[List[int]] = None,
session_id: Optional[int] = None,
limitation_type: LimitationType = LimitationType.MIN_LIMITATION):
"""Init Frame."""
super().__init__(Command.GW_GET_LIMITATION_STATUS_REQ)
self.session_id = session_id
self.originator = Originator.USER
self.priority = Priority.USER_LEVEL_2
self.node_ids = node_ids if node_ids is not None else []
self.parameter_id = 0 # Main Parameter
self.limitations_type = limitation_type
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.session_id is not None
ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
ret += bytes([len(self.node_ids)]) # index array count
ret += bytes(self.node_ids) + bytes(20 - len(self.node_ids))
ret += bytes([self.parameter_id])
ret += bytes([self.limitations_type.value])
return ret
def __str__(self) -> str:
"""Return human readable string."""
return f'<{type(self).__name__} node_ids="{self.node_ids}" ' \
f'session_id="{self.session_id}" originator="{self.originator}" />'
class FrameGetLimitationStatusConfirmation(FrameBase):
"""Frame for response for get limitation requests."""
PAYLOAD_LEN = 3
def __init__(self, session_id: Optional[int] = None, data: Optional[int] = None):
"""Init Frame."""
super().__init__(Command.GW_GET_LIMITATION_STATUS_CFM)
self.session_id = session_id
self.data = data
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.session_id is not None
ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
assert self.data is not None
ret += bytes([self.data])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.session_id = payload[0] * 256 + payload[1]
self.data = payload[2]
def __str__(self) -> str:
"""Return human readable string."""
return '<{} session_id="{}" status="{}"/>'.format(
type(self).__name__, self.session_id, self.data
)
class FrameGetLimitationStatusNotification(FrameBase):
"""Frame for notification of note information request."""
PAYLOAD_LEN = 10
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_LIMITATION_STATUS_NTF)
self.session_id: Optional[int] = None
self.node_id = 0
self.parameter_id = 0
self.min_value: Optional[bytes] = None
self.max_value: Optional[bytes] = None
self.limit_originator: Optional[Originator] = None
self.limit_time: Optional[int] = None
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.session_id is not None
assert self.min_value is not None
assert self.max_value is not None
assert self.limit_originator is not None
assert self.limit_time is not None
payload = bytes([self.session_id >> 8 & 255, self.session_id & 255])
payload += bytes([self.node_id])
payload += bytes([self.parameter_id])
payload += self.min_value
payload += self.max_value
payload += bytes([self.limit_originator.value])
payload += bytes([self.limit_time])
return payload
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.session_id = payload[0] * 256 + payload[1]
self.node_id = payload[2]
self.parameter_id = payload[3]
self.min_value = payload[4:5]
self.max_value = payload[6:7]
self.limit_originator = Originator(payload[8])
self.limit_time = payload[9]
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} node_id="{}" session_id="{}" min_value="{!r}" '
'max_value="{!r}" originator="{}" limit_time="{}"/>'.format(
type(self).__name__, self.node_id, self.session_id,
self.min_value, self.max_value, self.limit_originator,
self.limit_time
)
)
pyvlx-0.2.26/pyvlx/api/frames/frame_get_local_time.py 0000664 0000000 0000000 00000002015 14734113327 0022671 0 ustar 00root root 0000000 0000000 """Module for get local time classes."""
from pyvlx.const import Command
from pyvlx.dataobjects import DtoLocalTime
from .frame import FrameBase
class FrameGetLocalTimeRequest(FrameBase):
"""Frame for requesting local time."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_GET_LOCAL_TIME_REQ)
class FrameGetLocalTimeConfirmation(FrameBase):
"""Frame for response for get local time requests."""
PAYLOAD_LEN = 15
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_GET_LOCAL_TIME_CFM)
self.time = DtoLocalTime()
def get_payload(self) -> bytes:
"""Return Payload."""
return self.time.to_payload()
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.time.from_payload(payload)
def __str__(self) -> str:
"""Return human readable string."""
return '<{0}>{1}{0}>'.format(type(self).__name__, self.time)
pyvlx-0.2.26/pyvlx/api/frames/frame_get_network_setup.py 0000664 0000000 0000000 00000004056 14734113327 0023501 0 ustar 00root root 0000000 0000000 """Frames for receiving network setup from gateway."""
from pyvlx.const import Command, DHCPParameter
from .frame import FrameBase
class FrameGetNetworkSetupRequest(FrameBase):
"""Frame for requesting network setup."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_GET_NETWORK_SETUP_REQ)
class FrameGetNetworkSetupConfirmation(FrameBase):
"""Frame for confirmation for get network setup requests."""
PAYLOAD_LEN = 13
def __init__(self, ipaddress: bytes = bytes(4), netmask: bytes = bytes(4), gateway: bytes = bytes(4),
dhcp: DHCPParameter = DHCPParameter.DISABLE):
"""Init Frame."""
super().__init__(Command.GW_GET_NETWORK_SETUP_CFM)
self._ipaddress = ipaddress
self._netmask = netmask
self._gateway = gateway
self.dhcp = dhcp
@property
def ipaddress(self) -> str:
"""Return ipaddress as human readable string."""
return ".".join(str(c) for c in self._ipaddress)
@property
def netmask(self) -> str:
"""Return ipaddress as human readable string."""
return ".".join(str(c) for c in self._netmask)
@property
def gateway(self) -> str:
"""Return ipaddress as human readable string."""
return ".".join(str(c) for c in self._gateway)
def get_payload(self) -> bytes:
"""Return Payload."""
payload = self._ipaddress
payload += self._netmask
payload += self._gateway
payload += bytes(self.dhcp.value)
return payload
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self._ipaddress = payload[0:4]
self._netmask = payload[4:8]
self._gateway = payload[8:12]
self.dhcp = DHCPParameter(payload[12])
def __str__(self) -> str:
"""Return human readable string."""
return '<{} ipaddress="{}" netmask="{}" gateway="{}" dhcp="{}"/>'.format(
type(self).__name__, self.ipaddress, self.netmask, self.gateway, self.dhcp)
pyvlx-0.2.26/pyvlx/api/frames/frame_get_node_information.py 0000664 0000000 0000000 00000020370 14734113327 0024117 0 ustar 00root root 0000000 0000000 """Module for get node information from gateway."""
import struct
from datetime import datetime
from enum import Enum
from typing import Optional
from pyvlx.const import (
Command, NodeTypeWithSubtype, NodeVariation, OperatingState, Velocity)
from pyvlx.exception import PyVLXException
from pyvlx.parameter import Parameter
from pyvlx.string_helper import bytes_to_string, string_to_bytes
from .alias_array import AliasArray
from .frame import FrameBase
class FrameGetNodeInformationRequest(FrameBase):
"""Frame for get node information request."""
PAYLOAD_LEN = 1
def __init__(self, node_id: Optional[int] = None):
"""Init Frame."""
super().__init__(Command.GW_GET_NODE_INFORMATION_REQ)
self.node_id = node_id
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.node_id is not None
return bytes([self.node_id])
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.node_id = payload[0]
def __str__(self) -> str:
"""Return human readable string."""
return '<{} node_id="{}"/>'.format(type(self).__name__, self.node_id)
class NodeInformationStatus(Enum):
# pylint: disable=invalid-name
"""Enum for node information status."""
OK = 0
Error_Request_Rejected = 1
Error_Invalid_Node_Index = 2
class FrameGetNodeInformationConfirmation(FrameBase):
"""Frame for confirmation for node information request."""
PAYLOAD_LEN = 2
def __init__(self, status: NodeInformationStatus = NodeInformationStatus.OK, node_id: Optional[int] = None):
"""Init Frame."""
super().__init__(Command.GW_GET_NODE_INFORMATION_CFM)
self.status = status
self.node_id = node_id
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.node_id is not None
return bytes([self.status.value, self.node_id])
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.status = NodeInformationStatus(payload[0])
self.node_id = payload[1]
def __str__(self) -> str:
"""Return human readable string."""
return '<{} node_id="{}" status="{}"/>'.format(
type(self).__name__, self.node_id, self.status
)
class FrameGetNodeInformationNotification(FrameBase):
"""Frame for notification of node information request."""
PAYLOAD_LEN = 124
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_GET_NODE_INFORMATION_NTF)
self.node_id = 0
self.order = 0
self.placement = 0
self.name = ""
self.velocity = Velocity.DEFAULT
self.node_type = NodeTypeWithSubtype.NO_TYPE
self.product_group = 0
self.product_type = 0
self.node_variation = NodeVariation.NOT_SET
self.power_mode = 0
self.build_number = 0
self._serial_number = bytes(8)
self.state = OperatingState.UNKNOWN
self.current_position = Parameter()
self.target = Parameter()
self.current_position_fp1 = Parameter()
self.current_position_fp2 = Parameter()
self.current_position_fp3 = Parameter()
self.current_position_fp4 = Parameter()
self.remaining_time = 0
self.timestamp = 0
self.alias_array = AliasArray()
@property
def serial_number(self) -> Optional[str]:
"""Property for serial number in a human readable way."""
if self._serial_number == bytes(8):
return None
return ":".join("{:02x}".format(c) for c in self._serial_number)
@serial_number.setter
def serial_number(self, serial_number: Optional[str]) -> None:
"""Set serial number."""
if serial_number is None:
self._serial_number = bytes(8)
return
self._serial_number = b""
for elem in serial_number.split(":"):
self._serial_number += bytes.fromhex(elem)
if len(self._serial_number) != 8:
raise PyVLXException("could_not_parse_serial_number")
def get_payload(self) -> bytes:
"""Return Payload."""
payload = bytes()
payload += bytes([self.node_id])
payload += bytes([self.order >> 8 & 255, self.order & 255])
payload += bytes([self.placement])
payload += bytes(string_to_bytes(self.name, 64))
payload += bytes([self.velocity.value])
payload += bytes([self.node_type.value >> 8 & 255, self.node_type.value & 255])
payload += bytes([self.product_group])
payload += bytes([self.product_type])
payload += bytes([self.node_variation.value])
payload += bytes([self.power_mode])
payload += bytes(
[self.build_number]
) # <-- hey @VELUX: your documentation is wrong here
payload += bytes(self._serial_number)
payload += bytes([self.state.value])
payload += bytes(self.current_position.raw)
payload += bytes(self.target.raw)
payload += bytes(self.current_position_fp1.raw)
payload += bytes(self.current_position_fp2.raw)
payload += bytes(self.current_position_fp3.raw)
payload += bytes(self.current_position_fp4.raw)
payload += bytes([self.remaining_time >> 8 & 255, self.remaining_time & 255])
payload += struct.pack(">I", self.timestamp)
payload += bytes(self.alias_array)
return payload
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.node_id = payload[0]
self.order = payload[1] * 256 + payload[2]
self.placement = payload[3]
self.name = bytes_to_string(payload[4:68])
self.velocity = Velocity(payload[68])
self.node_type = NodeTypeWithSubtype(payload[69] * 256 + payload[70])
self.product_group = payload[71]
self.product_type = payload[72]
self.node_variation = NodeVariation(payload[73])
self.power_mode = payload[74]
self.build_number = payload[
75
] # <-- hey @VELUX: your documentation is wrong here
self._serial_number = payload[76:84]
self.state = OperatingState(payload[84])
self.current_position = Parameter(payload[85:87])
self.target = Parameter(payload[87:89])
self.current_position_fp1 = Parameter(payload[89:91])
self.current_position_fp2 = Parameter(payload[91:93])
self.current_position_fp3 = Parameter(payload[93:95])
self.current_position_fp4 = Parameter(payload[95:97])
self.remaining_time = payload[97] * 256 + payload[98]
self.timestamp = struct.unpack(">I", payload[99:103])[0]
self.alias_array = AliasArray(payload[103:125])
@property
def timestamp_formatted(self) -> str:
"""Return time as human readable string."""
return datetime.fromtimestamp(self.timestamp).strftime("%Y-%m-%d %H:%M:%S")
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} node_id="{}" order="{}" '
'placement="{}" name="{}" velocity="{}" node_type="{}" product_group="{}" '
'product_type="{}" node_variation="{}" power_mode="{}" build_number="{}" '
'serial_number="{}" state="{}" current_position="{}" '
'target="{}" current_position_fp1="{}" current_position_fp2="{}" '
'current_position_fp3="{}" current_position_fp4="{}" '
'remaining_time="{}" time="{}" alias_array="{}"/>'.format(
type(self).__name__,
self.node_id,
self.order,
self.placement,
self.name,
self.velocity,
self.node_type,
self.product_group,
self.product_type,
self.node_variation,
self.power_mode,
self.build_number,
self.serial_number,
self.state.name,
self.current_position,
self.target,
self.current_position_fp1,
self.current_position_fp2,
self.current_position_fp3,
self.current_position_fp4,
self.remaining_time,
self.timestamp_formatted,
self.alias_array,
)
)
pyvlx-0.2.26/pyvlx/api/frames/frame_get_protocol_version.py 0000664 0000000 0000000 00000003072 14734113327 0024173 0 ustar 00root root 0000000 0000000 """Module for get version frame classes."""
from pyvlx.const import Command
from .frame import FrameBase
class FrameGetProtocolVersionRequest(FrameBase):
"""Frame for requesting protocol version."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_GET_PROTOCOL_VERSION_REQ)
class FrameGetProtocolVersionConfirmation(FrameBase):
"""Frame for response for get protocol version requests."""
PAYLOAD_LEN = 4
def __init__(self, major_version: int = 0, minor_version: int = 0):
"""Init Frame."""
super().__init__(Command.GW_GET_PROTOCOL_VERSION_CFM)
self.major_version = major_version
self.minor_version = minor_version
@property
def version(self) -> str:
"""Return formatted protocol version."""
return "{}.{}".format(self.major_version, self.minor_version)
def get_payload(self) -> bytes:
"""Return Payload."""
return bytes(
[
self.major_version >> 8 & 255,
self.major_version & 255,
self.minor_version >> 8 & 255,
self.minor_version & 255,
]
)
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.major_version = payload[0] * 256 + payload[1]
self.minor_version = payload[2] * 256 + payload[3]
def __str__(self) -> str:
"""Return human readable string."""
return '<{} version="{}"/>'.format(
type(self).__name__, self.version
)
pyvlx-0.2.26/pyvlx/api/frames/frame_get_scene_list.py 0000664 0000000 0000000 00000005046 14734113327 0022720 0 ustar 00root root 0000000 0000000 """Module for get scene list frame classes."""
from typing import List, Tuple
from pyvlx.const import Command
from pyvlx.exception import PyVLXException
from pyvlx.string_helper import bytes_to_string, string_to_bytes
from .frame import FrameBase
class FrameGetSceneListRequest(FrameBase):
"""Frame for get scene list request."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_GET_SCENE_LIST_REQ)
class FrameGetSceneListConfirmation(FrameBase):
"""Frame for confirmation for scene list request."""
PAYLOAD_LEN = 1
def __init__(self, count_scenes: int = 0):
"""Init Frame."""
super().__init__(Command.GW_GET_SCENE_LIST_CFM)
self.count_scenes = count_scenes
def get_payload(self) -> bytes:
"""Return Payload."""
return bytes([self.count_scenes])
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.count_scenes = payload[0]
def __str__(self) -> str:
"""Return human readable string."""
return '<{} count_scenes="{}"/>'.format(
type(self).__name__, self.count_scenes
)
class FrameGetSceneListNotification(FrameBase):
"""Frame for scene list notification."""
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_GET_SCENE_LIST_NTF)
self.scenes: List[Tuple[int, str]] = []
self.remaining_scenes = 0
def get_payload(self) -> bytes:
"""Return Payload."""
ret = bytes([len(self.scenes)])
for number, name in self.scenes:
ret += bytes([number])
ret += string_to_bytes(name, 64)
ret += bytes([self.remaining_scenes])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
number_of_objects = payload[0]
self.remaining_scenes = payload[-1]
predicted_len = number_of_objects * 65 + 2
if len(payload) != predicted_len:
raise PyVLXException("scene_list_notification_wrong_length")
self.scenes = []
for i in range(number_of_objects):
scene = payload[(i * 65 + 1) : (i * 65 + 66)]
number = scene[0]
name = bytes_to_string(scene[1:])
self.scenes.append((number, name))
def __str__(self) -> str:
"""Return human readable string."""
return '<{} scenes="{}" remaining_scenes="{}">'.format(
type(self).__name__, self.scenes, self.remaining_scenes
)
pyvlx-0.2.26/pyvlx/api/frames/frame_get_state.py 0000664 0000000 0000000 00000002746 14734113327 0021714 0 ustar 00root root 0000000 0000000 """Frames for receiving state from gateway."""
from pyvlx.const import Command, GatewayState, GatewaySubState
from .frame import FrameBase
class FrameGetStateRequest(FrameBase):
"""Frame for requesting state."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_GET_STATE_REQ)
class FrameGetStateConfirmation(FrameBase):
"""Frame for confirmation for get state requests."""
PAYLOAD_LEN = 6
def __init__(
self,
gateway_state: GatewayState = GatewayState.TEST_MODE,
gateway_sub_state: GatewaySubState = GatewaySubState.IDLE,
):
"""Init Frame."""
super().__init__(Command.GW_GET_STATE_CFM)
self.gateway_state = gateway_state
self.gateway_sub_state = gateway_sub_state
def get_payload(self) -> bytes:
"""Return Payload."""
payload = bytes([self.gateway_state.value, self.gateway_sub_state.value])
payload += bytes(4) # State date, reserved for future use
return payload
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.gateway_state = GatewayState(payload[0])
self.gateway_sub_state = GatewaySubState(payload[1])
def __str__(self) -> str:
"""Return human readable string."""
return '<{} gateway_state="{}" gateway_sub_state="{}"/>'.format(
type(self).__name__, self.gateway_state, self.gateway_sub_state
)
pyvlx-0.2.26/pyvlx/api/frames/frame_get_version.py 0000664 0000000 0000000 00000004550 14734113327 0022254 0 ustar 00root root 0000000 0000000 """Module for get version frame classes."""
from typing import Union
from pyvlx.const import Command
from .frame import FrameBase
class FrameGetVersionRequest(FrameBase):
"""Frame for requesting version."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_GET_VERSION_REQ)
class FrameGetVersionConfirmation(FrameBase):
"""Frame for response for get version requests."""
PAYLOAD_LEN = 9
def __init__(self, software_version: Union[bytes, str] = bytes(6), hardware_version: int = 0):
"""Init Frame."""
super().__init__(Command.GW_GET_VERSION_CFM)
if isinstance(software_version, str):
software_version = bytes(int(c) for c in software_version.split("."))
self._software_version = software_version
self.hardware_version = hardware_version
self.product_group = 14
self.product_type = 3
@property
def version(self) -> str:
"""Return formatted version."""
return "{}: Software version: {}, hardware version: {}".format(
self.product, self.software_version, self.hardware_version
)
@property
def software_version(self) -> str:
"""Return software version as human readable string."""
return ".".join(str(c) for c in self._software_version)
@property
def product(self) -> str:
"""Return product as human readable string."""
if self.product_group == 14 and self.product_type == 3:
return "KLF 200"
return "Unknown Product: {}:{}".format(self.product_group, self.product_type)
def get_payload(self) -> bytes:
"""Return Payload."""
ret = self._software_version
ret += bytes([self.hardware_version, self.product_group, self.product_type])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self._software_version = payload[0:6]
self.hardware_version = payload[6]
self.product_group = payload[7]
self.product_type = payload[8]
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} software_version="{}" hardware_version="{}" product="{}"/>'.format(
type(self).__name__, self.software_version, self.hardware_version, self.product
)
)
pyvlx-0.2.26/pyvlx/api/frames/frame_helper.py 0000664 0000000 0000000 00000002375 14734113327 0021212 0 ustar 00root root 0000000 0000000 """Helper module for SLIP Frames."""
from typing import Tuple
from pyvlx.const import Command
from pyvlx.exception import PyVLXException
def calc_crc(raw: bytes) -> int:
"""Calculate cyclic redundancy check (CRC)."""
crc = 0
for sym in raw:
crc = crc ^ int(sym)
return crc
def extract_from_frame(data: bytes) -> Tuple[Command, bytes]:
"""Extract payload and command from frame."""
if len(data) <= 4:
raise PyVLXException("could_not_extract_from_frame_too_short", data=data)
length = data[0] * 256 + data[1] - 1
if len(data) != length + 3:
raise PyVLXException(
"could_not_extract_from_frame_invalid_length",
data=data,
current_length=len(data),
expected_length=length + 3,
)
if calc_crc(data[:-1]) != data[-1]:
raise PyVLXException(
"could_not_extract_from_frame_invalid_crc",
data=data,
expected_crc=calc_crc(data[:-1]),
current_crc=data[-1],
)
payload = data[4:-1]
try:
command = Command(data[2] * 256 + data[3])
except ValueError as type_error:
raise PyVLXException("could_not_extract_from_frame_command", data=data) from type_error
return command, payload
pyvlx-0.2.26/pyvlx/api/frames/frame_house_status_monitor_disable_cfm.py 0000664 0000000 0000000 00000000634 14734113327 0026534 0 ustar 00root root 0000000 0000000 """Module for confirmation for disabling the house status monitor."""
from pyvlx.const import Command
from .frame import FrameBase
class FrameHouseStatusMonitorDisableConfirmation(FrameBase):
"""Frame for requesting enabling the house status monitor."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_HOUSE_STATUS_MONITOR_DISABLE_CFM)
pyvlx-0.2.26/pyvlx/api/frames/frame_house_status_monitor_disable_req.py 0000664 0000000 0000000 00000000621 14734113327 0026552 0 ustar 00root root 0000000 0000000 """Module for requesting disabling the house status monitor."""
from pyvlx.const import Command
from .frame import FrameBase
class FrameHouseStatusMonitorDisableRequest(FrameBase):
"""Frame for requesting disabling the house status monitor."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_HOUSE_STATUS_MONITOR_DISABLE_REQ)
pyvlx-0.2.26/pyvlx/api/frames/frame_house_status_monitor_enable_cfm.py 0000664 0000000 0000000 00000000636 14734113327 0026361 0 ustar 00root root 0000000 0000000 """Module for confirmation for enabling the house status monitor."""
from pyvlx.const import Command
from .frame import FrameBase
class FrameHouseStatusMonitorEnableConfirmation(FrameBase):
"""Frame for confirmation for enabling the house status monitor."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_HOUSE_STATUS_MONITOR_ENABLE_CFM)
pyvlx-0.2.26/pyvlx/api/frames/frame_house_status_monitor_enable_req.py 0000664 0000000 0000000 00000000615 14734113327 0026400 0 ustar 00root root 0000000 0000000 """Module for requesting enabling the house status monitor."""
from pyvlx.const import Command
from .frame import FrameBase
class FrameHouseStatusMonitorEnableRequest(FrameBase):
"""Frame for requesting enabling the house status monitor."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_HOUSE_STATUS_MONITOR_ENABLE_REQ)
pyvlx-0.2.26/pyvlx/api/frames/frame_leave_learn_state.py 0000664 0000000 0000000 00000002350 14734113327 0023401 0 ustar 00root root 0000000 0000000 """Module for leave learn state frame classes."""
from pyvlx.const import Command, LeaveLearnStateConfirmationStatus
from .frame import FrameBase
class FrameLeaveLearnStateRequest(FrameBase):
"""Frame for leaving learn state request."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_LEAVE_LEARN_STATE_REQ)
def __str__(self) -> str:
"""Return human readable string."""
return '<{}/>'.format(type(self).__name__)
class FrameLeaveLearnStateConfirmation(FrameBase):
"""Frame for confirmation for leaving learn State."""
PAYLOAD_LEN = 1
def __init__(self, status: int = 0):
"""Init Frame."""
super().__init__(Command.GW_LEAVE_LEARN_STATE_CFM)
self.status = LeaveLearnStateConfirmationStatus(status)
def get_payload(self) -> bytes:
"""Return Payload."""
return bytes([self.status.value])
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.status = LeaveLearnStateConfirmationStatus(payload[0])
def __str__(self) -> str:
"""Return human readable string."""
return '<{} status="{}"/>'.format(type(self).__name__, self.status)
pyvlx-0.2.26/pyvlx/api/frames/frame_node_information_changed.py 0000664 0000000 0000000 00000003572 14734113327 0024736 0 ustar 00root root 0000000 0000000 """Module for requesting change of node name."""
from typing import Optional
from pyvlx.const import Command, NodeVariation
from pyvlx.string_helper import bytes_to_string, string_to_bytes
from .frame import FrameBase
class FrameNodeInformationChangedNotification(FrameBase):
"""Frame for notification for set node name."""
PAYLOAD_LEN = 69
def __init__(
self,
node_id: int = 0,
name: Optional[str] = None,
order: int = 0,
placement: int = 0,
node_variation: NodeVariation = NodeVariation.NOT_SET,
):
"""Init Frame."""
super().__init__(Command.GW_NODE_INFORMATION_CHANGED_NTF)
self.node_id = node_id
self.name = name
self.order = order
self.placement = placement
self.node_variation = node_variation
def get_payload(self) -> bytes:
"""Return Payload."""
payload = bytes([self.node_id])
assert self.name is not None
payload += string_to_bytes(self.name, 64)
payload += bytes([self.order >> 8 & 255, self.order & 255])
payload += bytes([self.placement])
payload += bytes([self.node_variation.value])
return payload
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.node_id = payload[0]
self.name = bytes_to_string(payload[1:65])
self.order = payload[65] * 256 + payload[66]
self.placement = payload[67]
self.node_variation = NodeVariation(payload[68])
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} node_id="{}" name="{}" order="{}" '
'placement="{}" node_variation="{}"/>'.format(
type(self).__name__, self.node_id, self.name,
self.order, self.placement, self.node_variation
)
)
pyvlx-0.2.26/pyvlx/api/frames/frame_node_state_position_changed_notification.py 0000664 0000000 0000000 00000006370 14734113327 0030222 0 ustar 00root root 0000000 0000000 """Module for get node information from gateway."""
import struct
from datetime import datetime
from pyvlx.const import Command, OperatingState
from pyvlx.parameter import Parameter
from .frame import FrameBase
class FrameNodeStatePositionChangedNotification(FrameBase):
"""Frame for notification of note information request."""
PAYLOAD_LEN = 20
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_NODE_STATE_POSITION_CHANGED_NTF)
self.node_id = 0
self.state: OperatingState = OperatingState.NON_EXECUTING
self.current_position = Parameter()
self.target = Parameter()
self.current_position_fp1 = Parameter()
self.current_position_fp2 = Parameter()
self.current_position_fp3 = Parameter()
self.current_position_fp4 = Parameter()
self.remaining_time = 0
self.timestamp = 0
def get_payload(self) -> bytes:
"""Return Payload."""
payload = bytes([self.node_id])
payload += bytes([self.state.value])
payload += bytes(self.current_position.raw)
payload += bytes(self.target.raw)
payload += bytes(self.current_position_fp1.raw)
payload += bytes(self.current_position_fp2.raw)
payload += bytes(self.current_position_fp3.raw)
payload += bytes(self.current_position_fp4.raw)
payload += bytes([self.remaining_time >> 8 & 255, self.remaining_time & 255])
payload += struct.pack(">I", self.timestamp)
return payload
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.node_id = payload[0]
self.state = OperatingState(payload[1])
self.current_position = Parameter(payload[2:4])
self.target = Parameter(payload[4:6])
self.current_position_fp1 = Parameter(payload[6:8])
self.current_position_fp2 = Parameter(payload[8:10])
self.current_position_fp3 = Parameter(payload[10:12])
self.current_position_fp4 = Parameter(payload[12:14])
self.remaining_time = payload[14] * 256 + payload[15]
# @VELUX: looks like your timestamp is wrong. Looks like
# you are only transmitting the two lower bytes.
self.timestamp = struct.unpack(">I", payload[16:20])[0]
@property
def timestamp_formatted(self) -> str:
"""Return time as human readable string."""
return datetime.fromtimestamp(self.timestamp).strftime("%Y-%m-%d %H:%M:%S")
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} node_id="{}" '
'state="{}" current_position="{}" '
'target="{}" current_position_fp1="{}" current_position_fp2="{}" '
'current_position_fp3="{}" current_position_fp4="{}" '
'remaining_time="{}" time="{}"/>'.format(
type(self).__name__,
self.node_id,
self.state.name,
self.current_position,
self.target,
self.current_position_fp1,
self.current_position_fp2,
self.current_position_fp3,
self.current_position_fp4,
self.remaining_time,
self.timestamp_formatted,
)
)
pyvlx-0.2.26/pyvlx/api/frames/frame_password_change.py 0000664 0000000 0000000 00000007714 14734113327 0023104 0 ustar 00root root 0000000 0000000 """Module for password enter frame classes."""
from enum import Enum
from typing import Optional
from pyvlx.const import Command
from pyvlx.exception import PyVLXException
from pyvlx.string_helper import bytes_to_string, string_to_bytes
from .frame import FrameBase
class FramePasswordChangeRequest(FrameBase):
"""Frame for sending password enter request."""
MAX_SIZE = 32
PAYLOAD_LEN = 64
def __init__(self, currentpassword: Optional[str] = None, newpassword: Optional[str] = None):
"""Init Frame."""
super().__init__(Command.GW_PASSWORD_CHANGE_REQ)
self.currentpassword = currentpassword
self.newpassword = newpassword
def get_payload(self) -> bytes:
"""Return Payload."""
if self.currentpassword is None:
raise PyVLXException("currentpassword is none")
if self.newpassword is None:
raise PyVLXException("newpassword is none")
if len(self.currentpassword) > self.MAX_SIZE:
raise PyVLXException("currentpassword is too long")
if len(self.newpassword) > self.MAX_SIZE:
raise PyVLXException("newpassword is too long")
return string_to_bytes(self.currentpassword,
self.MAX_SIZE)+string_to_bytes(self.newpassword, self.MAX_SIZE)
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.currentpassword = bytes_to_string(payload[0:32])
self.newpassword = bytes_to_string(payload[32:])
def __str__(self) -> str:
"""Return human readable string."""
currentpassword_esc = (
None if self.currentpassword is None else "{}****".format(self.currentpassword[:2])
)
newpassword_esc = (
None if self.newpassword is None else "{}****".format(self.newpassword[:2])
)
return ('<{} currentpassword="{}" newpassword="{}"/>'
.format(type(self).__name__, currentpassword_esc, newpassword_esc))
class PasswordChangeConfirmationStatus(Enum):
"""Enum class for status of password change confirmation."""
SUCCESSFUL = 0
FAILED = 1
class FramePasswordChangeConfirmation(FrameBase):
"""Frame for confirmation for sent password."""
PAYLOAD_LEN = 1
def __init__(self, status: PasswordChangeConfirmationStatus = PasswordChangeConfirmationStatus.SUCCESSFUL):
"""Init Frame."""
super().__init__(Command.GW_PASSWORD_CHANGE_CFM)
self.status = status
def get_payload(self) -> bytes:
"""Return Payload."""
return bytes([self.status.value])
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.status = PasswordChangeConfirmationStatus(payload[0])
def __str__(self) -> str:
"""Return human readable string."""
return '<{} status="{}"/>'.format(type(self).__name__, self.status)
class FramePasswordChangeNotification(FrameBase):
"""Frame for sending password changed notification request."""
MAX_SIZE = 32
PAYLOAD_LEN = 32
def __init__(self, newpassword: Optional[str] = None):
"""Init Frame."""
super().__init__(Command.GW_PASSWORD_CHANGE_NTF)
self.newpassword = newpassword
def get_payload(self) -> bytes:
"""Return Payload."""
if self.newpassword is None:
raise PyVLXException("newpassword is none")
if len(self.newpassword) > self.MAX_SIZE:
raise PyVLXException("newpassword is too long")
return string_to_bytes(self.newpassword, self.MAX_SIZE)
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.newpassword = bytes_to_string(payload)
def __str__(self) -> str:
"""Return human readable string."""
newpassword_esc = (
None if self.newpassword is None else "{}****".format(self.newpassword[:2])
)
return '<{} newpassword="{}"/>'.format(type(self).__name__, newpassword_esc)
pyvlx-0.2.26/pyvlx/api/frames/frame_password_enter.py 0000664 0000000 0000000 00000004263 14734113327 0022770 0 ustar 00root root 0000000 0000000 """Module for password enter frame classes."""
from enum import Enum
from typing import Optional
from pyvlx.const import Command
from pyvlx.exception import PyVLXException
from pyvlx.string_helper import bytes_to_string, string_to_bytes
from .frame import FrameBase
class FramePasswordEnterRequest(FrameBase):
"""Frame for sending password enter request."""
MAX_SIZE = 32
PAYLOAD_LEN = 32
def __init__(self, password: Optional[str] = None):
"""Init Frame."""
super().__init__(Command.GW_PASSWORD_ENTER_REQ)
self.password = password
def get_payload(self) -> bytes:
"""Return Payload."""
if self.password is None:
raise PyVLXException("password is none")
if len(self.password) > self.MAX_SIZE:
raise PyVLXException("password is too long")
return string_to_bytes(self.password, self.MAX_SIZE)
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.password = bytes_to_string(payload)
def __str__(self) -> str:
"""Return human readable string."""
password_esc = (
None if self.password is None else "{}****".format(self.password[:2])
)
return '<{} password="{}"/>'.format(type(self).__name__, password_esc)
class PasswordEnterConfirmationStatus(Enum):
"""Enum class for status of password enter confirmation."""
SUCCESSFUL = 0
FAILED = 1
class FramePasswordEnterConfirmation(FrameBase):
"""Frame for confirmation for sent password."""
PAYLOAD_LEN = 1
def __init__(self, status: PasswordEnterConfirmationStatus = PasswordEnterConfirmationStatus.SUCCESSFUL):
"""Init Frame."""
super().__init__(Command.GW_PASSWORD_ENTER_CFM)
self.status = status
def get_payload(self) -> bytes:
"""Return Payload."""
return bytes([self.status.value])
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.status = PasswordEnterConfirmationStatus(payload[0])
def __str__(self) -> str:
"""Return human readable string."""
return '<{} status="{}"/>'.format(type(self).__name__, self.status)
pyvlx-0.2.26/pyvlx/api/frames/frame_reboot.py 0000664 0000000 0000000 00000001433 14734113327 0021217 0 ustar 00root root 0000000 0000000 """Module for reboot frame classes."""
from pyvlx.const import Command
from .frame import FrameBase
class FrameGatewayRebootRequest(FrameBase):
"""Frame for requesting reboot."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_REBOOT_REQ)
def __str__(self) -> str:
"""Return human readable string."""
return '<{}/>'.format(type(self).__name__)
class FrameGatewayRebootConfirmation(FrameBase):
"""Frame for response for reboot requests."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_REBOOT_CFM)
def __str__(self) -> str:
"""Return human readable string."""
return '<{}/>'.format(type(self).__name__)
pyvlx-0.2.26/pyvlx/api/frames/frame_set_node_name.py 0000664 0000000 0000000 00000004256 14734113327 0022533 0 ustar 00root root 0000000 0000000 """Module for requesting change of node name."""
from enum import Enum
from typing import Optional
from pyvlx.const import Command
from pyvlx.string_helper import bytes_to_string, string_to_bytes
from .frame import FrameBase
class FrameSetNodeNameRequest(FrameBase):
"""Frame for requesting node name change."""
PAYLOAD_LEN = 65
def __init__(self, node_id: int = 0, name: Optional[str] = None):
"""Init Frame."""
super().__init__(Command.GW_SET_NODE_NAME_REQ)
self.node_id = node_id
self.name = name
def get_payload(self) -> bytes:
"""Return Payload."""
ret = bytes([self.node_id])
assert self.name is not None
ret += string_to_bytes(self.name, 64)
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.node_id = payload[0]
self.name = bytes_to_string(payload[1:65])
def __str__(self) -> str:
"""Return human readable string."""
return '<{} node_id="{}" name="{}"/>'.format(
type(self).__name__, self.node_id, self.name
)
class SetNodeNameConfirmationStatus(Enum):
"""Enum class for status of password enter confirmation."""
OK = 0
ERROR_REQUEST_REJECTED = 1
ERROR_INVALID_SYSTEM_TABLE_INDEX = 2
class FrameSetNodeNameConfirmation(FrameBase):
"""Frame for confirmation for set node name."""
PAYLOAD_LEN = 2
def __init__(self, status: SetNodeNameConfirmationStatus = SetNodeNameConfirmationStatus.OK, node_id: int = 0):
"""Init Frame."""
super().__init__(Command.GW_SET_NODE_NAME_CFM)
self.status = status
self.node_id = node_id
def get_payload(self) -> bytes:
"""Return Payload."""
return bytes([self.status.value, self.node_id])
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.status = SetNodeNameConfirmationStatus(payload[0])
self.node_id = payload[1]
def __str__(self) -> str:
"""Return human readable string."""
return '<{} node_id="{}" status="{}"/>'.format(
type(self).__name__, self.node_id, self.status
)
pyvlx-0.2.26/pyvlx/api/frames/frame_set_utc.py 0000664 0000000 0000000 00000002367 14734113327 0021402 0 ustar 00root root 0000000 0000000 """Module for sending command to gw."""
import struct
from datetime import datetime
from pyvlx.const import Command
from .frame import FrameBase
class FrameSetUTCConfirmation(FrameBase):
"""Frame for confirmation for setting UTC time."""
PAYLOAD_LEN = 0
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_SET_UTC_CFM)
class FrameSetUTCRequest(FrameBase):
"""Frame for command for setting UTC time."""
PAYLOAD_LEN = 4
def __init__(self, timestamp: float = 0):
"""Init Frame."""
super().__init__(Command.GW_SET_UTC_REQ)
self.timestamp = timestamp
@property
def timestamp_formatted(self) -> str:
"""Return time as human readable string."""
return datetime.fromtimestamp(self.timestamp).strftime("%Y-%m-%d %H:%M:%S")
def get_payload(self) -> bytes:
"""Return Payload."""
return struct.pack(">I", self.timestamp)
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.timestamp = struct.unpack(">I", payload[0:4])[0]
def __str__(self) -> str:
"""Return human readable string."""
return '<{} time="{}"/>'.format(type(self).__name__, self.timestamp_formatted)
pyvlx-0.2.26/pyvlx/api/frames/frame_status_request.py 0000664 0000000 0000000 00000017565 14734113327 0023035 0 ustar 00root root 0000000 0000000 """Module for get node information from gateway."""
from enum import Enum
from typing import Dict, List, Optional
from pyvlx.const import (
Command, NodeParameter, RunStatus, StatusReply, StatusType)
from pyvlx.exception import PyVLXException
from pyvlx.parameter import Parameter
from .frame import FrameBase
class FrameStatusRequestRequest(FrameBase):
"""Frame for status request request."""
PAYLOAD_LEN = 26
def __init__(self, session_id: Optional[int] = None, node_ids: Optional[List[int]] = None):
"""Init Frame."""
super().__init__(Command.GW_STATUS_REQUEST_REQ)
self.session_id = session_id
self.node_ids = node_ids if node_ids is not None else []
self.status_type = StatusType.REQUEST_CURRENT_POSITION
self.fpi1 = 254 # Request FP1 to FP7
self.fpi2 = 0
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.session_id is not None
ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
ret += bytes([len(self.node_ids)]) # index array count
ret += bytes(self.node_ids) + bytes(20 - len(self.node_ids))
ret += bytes([self.status_type.value])
ret += bytes([self.fpi1])
ret += bytes([self.fpi2])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.session_id = payload[0] * 256 + payload[1]
len_node_ids = payload[2]
if len_node_ids > 20:
raise PyVLXException("command_send_request_wrong_node_length")
self.node_ids = []
for i in range(len_node_ids):
self.node_ids.append(payload[3] + i)
self.status_type = StatusType(payload[23])
self.fpi1 = payload[24]
self.fpi2 = payload[25]
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} session_id="{}" node_ids="{}" '
'status_type="{}" fpi1="{}" fpi2="{}"/>'.format(
type(self).__name__, self.session_id,
self.node_ids,
self.status_type, self.fpi1, self.fpi2
)
)
class StatusRequestStatus(Enum):
"""Enum for status request status."""
REJECTED = 0
ACCEPTED = 1
class FrameStatusRequestConfirmation(FrameBase):
"""Frame for confirmation for status request request."""
PAYLOAD_LEN = 3
def __init__(self, session_id: Optional[int] = None, status: Optional[StatusRequestStatus] = None):
"""Init Frame."""
super().__init__(Command.GW_STATUS_REQUEST_CFM)
self.session_id = session_id
self.status = status
def get_payload(self) -> bytes:
"""Return Payload."""
assert self.session_id is not None
ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
assert self.status is not None
ret += bytes([self.status.value])
return ret
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.session_id = payload[0] * 256 + payload[1]
self.status = StatusRequestStatus(payload[2])
def __str__(self) -> str:
"""Return human readable string."""
return '<{} session_id="{}" status="{}"/>'.format(
type(self).__name__, self.session_id, self.status
)
class FrameStatusRequestNotification(FrameBase):
"""Frame for notification of status request request."""
# PAYLOAD_LEN = 59
# No PAYLOAD_LEN because it is variable depending on StatusType
def __init__(self) -> None:
"""Init Frame."""
super().__init__(Command.GW_STATUS_REQUEST_NTF)
self.session_id = 0
self.status_id = 0
self.node_id = 0
self.run_status = RunStatus.EXECUTION_COMPLETED
self.status_reply = StatusReply.UNKNOWN_STATUS_REPLY
self.status_type = StatusType.REQUEST_TARGET_POSITION
self.status_count = 0
self.parameter_data: Dict[NodeParameter, Parameter] = {}
self.target_position = Parameter()
self.current_position = Parameter()
self.remaining_time = 0
self.last_master_execution_address = b''
self.last_command_originator = 0
def get_payload(self) -> bytes:
"""Return Payload."""
payload = bytes()
payload += bytes([self.session_id >> 8 & 255, self.session_id & 255])
payload += bytes([self.status_id])
payload += bytes([self.node_id])
payload += bytes([self.run_status.value])
payload += bytes([self.status_reply.value])
payload += bytes([self.status_type.value])
if self.status_type == StatusType.REQUEST_MAIN_INFO:
payload += bytes(self.target_position.raw)
payload += bytes(self.current_position.raw)
payload += bytes([self.remaining_time >> 8 & 255, self.remaining_time & 255])
payload += self.last_master_execution_address
payload += bytes([self.last_command_originator])
else:
payload += bytes([self.status_count])
keys = self.parameter_data.keys()
for key in keys:
payload += bytes([key.value])
payload += bytes(self.parameter_data[key].raw)
payload += bytes(51 - len(self.parameter_data))
return payload
def from_payload(self, payload: bytes) -> None:
"""Init frame from binary data."""
self.session_id = payload[0] * 256 + payload[1]
self.status_id = payload[2]
self.node_id = payload[3]
self.run_status = RunStatus(payload[4])
self.status_reply = StatusReply(payload[5])
self.status_type = StatusType(payload[6])
if self.status_type == StatusType.REQUEST_MAIN_INFO:
self.target_position = Parameter(payload[7:8])
self.current_position = Parameter(payload[9:10])
self.remaining_time = payload[11] * 256 + payload[12]
self.last_master_execution_address = payload[13:16]
self.last_command_originator = payload[17]
else:
self.status_count = payload[7]
for i in range(8, 8 + self.status_count*3, 3):
self.parameter_data.update({NodeParameter(payload[i]): Parameter(payload[i+1:i+3])})
def __str__(self) -> str:
"""Return human readable string."""
if self.status_type == StatusType.REQUEST_MAIN_INFO:
return (
'<{} session_id="{}" status_id="{}" '
'node_id="{}" run_status="{}" status_reply="{}" status_type="{}" target_position="{}" '
'current_position="{}" remaining_time="{}" last_master_execution_address="{!r}" last_command_originator="{}"/>'.format(
type(self).__name__,
self.session_id,
self.status_id,
self.node_id,
self.run_status,
self.status_reply,
self.status_type,
self.target_position,
self.current_position,
self.remaining_time,
self.last_master_execution_address,
self.last_command_originator,
)
)
parameter_data_str = ""
for key, value in self.parameter_data.items():
parameter_data_str += "%s: %s, " % (
str(key),
str(value),
)
return (
'<{} session_id="{}" status_id="{}" '
'node_id="{}" run_status="{}" status_reply="{}" status_type="{}" status_count="{}" '
'parameter_data="{}"/>'.format(
type(self).__name__,
self.session_id,
self.status_id,
self.node_id,
self.run_status,
self.status_reply,
self.status_type,
self.status_count,
parameter_data_str
)
)
pyvlx-0.2.26/pyvlx/api/get_all_nodes_information.py 0000664 0000000 0000000 00000003421 14734113327 0022501 0 ustar 00root root 0000000 0000000 """Module for retrieving node information from API."""
from typing import TYPE_CHECKING, List
from pyvlx.log import PYVLXLOG
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameGetAllNodesInformationConfirmation,
FrameGetAllNodesInformationFinishedNotification,
FrameGetAllNodesInformationNotification,
FrameGetAllNodesInformationRequest)
if TYPE_CHECKING:
from pyvlx import PyVLX
class GetAllNodesInformation(ApiEvent):
"""Class for retrieving node information from API."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize SceneList class."""
super().__init__(pyvlx=pyvlx)
self.number_of_nodes = 0
self.success = False
self.notification_frames: List[FrameGetAllNodesInformationNotification] = []
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if isinstance(frame, FrameGetAllNodesInformationConfirmation):
self.number_of_nodes = frame.number_of_nodes
# We are still waiting for FrameGetAllNodesInformationNotification
return False
if isinstance(frame, FrameGetAllNodesInformationNotification):
self.notification_frames.append(frame)
if isinstance(frame, FrameGetAllNodesInformationFinishedNotification):
if self.number_of_nodes != len(self.notification_frames):
PYVLXLOG.warning(
"Number of received scenes does not match expected number"
)
self.success = True
return True
return False
def request_frame(self) -> FrameGetAllNodesInformationRequest:
"""Construct initiating frame."""
return FrameGetAllNodesInformationRequest()
pyvlx-0.2.26/pyvlx/api/get_limitation.py 0000664 0000000 0000000 00000005035 14734113327 0020310 0 ustar 00root root 0000000 0000000 """Module for retrieving limitation value from API."""
from typing import TYPE_CHECKING, Optional
from ..const import LimitationType, Originator
from ..parameter import Position
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameGetLimitationStatus, FrameGetLimitationStatusConfirmation,
FrameGetLimitationStatusNotification)
from .session_id import get_new_session_id
if TYPE_CHECKING:
from pyvlx import PyVLX
class GetLimitation(ApiEvent):
"""Class for retrieving gateway state from API."""
def __init__(self, pyvlx: "PyVLX", node_id: int, limitation_type: LimitationType = LimitationType.MIN_LIMITATION):
"""Initialize SceneList class."""
super().__init__(pyvlx=pyvlx)
self.node_id = node_id
self.limitation_type = limitation_type
self.success = False
self.notification_frame: Optional[FrameGetLimitationStatusNotification] = None
self.session_id: Optional[int] = None
self.min_value_raw: Optional[bytes] = None
self.max_value_raw: Optional[bytes] = None
self.originator: Optional[Originator] = None
self.limit_time: Optional[int] = None
@property
def max_value(self) -> int:
"""Return max value."""
assert self.max_value_raw is not None
return Position.to_percent(self.max_value_raw)
@property
def min_value(self) -> int:
"""Return min value."""
assert self.min_value_raw is not None
return Position.to_percent(self.min_value_raw)
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if isinstance(frame, FrameGetLimitationStatusConfirmation):
return False # Wait for Notification Frame
if isinstance(frame, FrameGetLimitationStatusNotification):
if frame.session_id == self.session_id:
self.success = True
self.min_value_raw = frame.min_value
self.max_value_raw = frame.max_value
self.originator = frame.limit_originator
self.limit_time = frame.limit_time
self.notification_frame = frame
return True
return False
def request_frame(self) -> FrameGetLimitationStatus:
"""Construct initiating frame."""
self.session_id = get_new_session_id()
return FrameGetLimitationStatus(node_ids=[self.node_id], session_id=self.session_id,
limitation_type=self.limitation_type)
pyvlx-0.2.26/pyvlx/api/get_local_time.py 0000664 0000000 0000000 00000002034 14734113327 0020243 0 ustar 00root root 0000000 0000000 """Module for local time firmware version from API."""
from typing import TYPE_CHECKING
from pyvlx.dataobjects import DtoLocalTime
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameGetLocalTimeConfirmation, FrameGetLocalTimeRequest)
if TYPE_CHECKING:
from pyvlx import PyVLX
class GetLocalTime(ApiEvent):
"""Class for retrieving firmware version from API."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize GetLocalTime class."""
super().__init__(pyvlx=pyvlx)
self.success = False
self.localtime = DtoLocalTime()
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if not isinstance(frame, FrameGetLocalTimeConfirmation):
return False
self.localtime = frame.time
self.success = True
return True
def request_frame(self) -> FrameGetLocalTimeRequest:
"""Construct initiating frame."""
return FrameGetLocalTimeRequest()
pyvlx-0.2.26/pyvlx/api/get_network_setup.py 0000664 0000000 0000000 00000002203 14734113327 0021042 0 ustar 00root root 0000000 0000000 """Module for retrieving gateway state from API."""
from typing import TYPE_CHECKING
from pyvlx.dataobjects import DtoNetworkSetup
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameGetNetworkSetupConfirmation, FrameGetNetworkSetupRequest)
if TYPE_CHECKING:
from pyvlx import PyVLX
class GetNetworkSetup(ApiEvent):
"""Class for retrieving gateway state from API."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize GetNetworkSetup class."""
super().__init__(pyvlx=pyvlx)
self.success = False
self.networksetup = DtoNetworkSetup()
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if not isinstance(frame, FrameGetNetworkSetupConfirmation):
return False
self.success = True
self.networksetup = DtoNetworkSetup(
frame.ipaddress, frame.gateway, frame.netmask, frame.dhcp)
return True
def request_frame(self) -> FrameGetNetworkSetupRequest:
"""Construct initiating frame."""
return FrameGetNetworkSetupRequest()
pyvlx-0.2.26/pyvlx/api/get_node_information.py 0000664 0000000 0000000 00000002767 14734113327 0021502 0 ustar 00root root 0000000 0000000 """Module for retrieving node information from API."""
from typing import TYPE_CHECKING, Optional
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameGetNodeInformationConfirmation,
FrameGetNodeInformationNotification, FrameGetNodeInformationRequest)
if TYPE_CHECKING:
from pyvlx import PyVLX
class GetNodeInformation(ApiEvent):
"""Class for retrieving node informationfrom API."""
def __init__(self, pyvlx: "PyVLX", node_id: int):
"""Initialize SceneList class."""
super().__init__(pyvlx=pyvlx)
self.node_id = node_id
self.success = False
self.notification_frame: Optional[FrameGetNodeInformationNotification] = None
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if (
isinstance(frame, FrameGetNodeInformationConfirmation)
and frame.node_id == self.node_id
):
# We are still waiting for GetNodeInformationNotification
return False
if (
isinstance(frame, FrameGetNodeInformationNotification)
and frame.node_id == self.node_id
):
self.notification_frame = frame
self.success = True
return True
return False
def request_frame(self) -> FrameGetNodeInformationRequest:
"""Construct initiating frame."""
return FrameGetNodeInformationRequest(node_id=self.node_id)
pyvlx-0.2.26/pyvlx/api/get_protocol_version.py 0000664 0000000 0000000 00000002545 14734113327 0021550 0 ustar 00root root 0000000 0000000 """Module for retrieving protocol version from API."""
from typing import TYPE_CHECKING
from pyvlx.dataobjects import DtoProtocolVersion
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameGetProtocolVersionConfirmation,
FrameGetProtocolVersionRequest)
if TYPE_CHECKING:
from pyvlx import PyVLX
class GetProtocolVersion(ApiEvent):
"""Class for retrieving protocol version from API."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize GetProtocolVersion class."""
super().__init__(pyvlx=pyvlx)
self.success = False
self.protocolversion = DtoProtocolVersion()
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if not isinstance(frame, FrameGetProtocolVersionConfirmation):
return False
self.protocolversion = DtoProtocolVersion(frame.major_version, frame.minor_version)
self.success = True
return True
def request_frame(self) -> FrameGetProtocolVersionRequest:
"""Construct initiating frame."""
return FrameGetProtocolVersionRequest()
@property
def version(self) -> str:
"""Return Protocol Version as human readable string."""
return "{}.{}".format(self.protocolversion.majorversion, self.protocolversion.minorversion)
pyvlx-0.2.26/pyvlx/api/get_scene_list.py 0000664 0000000 0000000 00000003412 14734113327 0020264 0 ustar 00root root 0000000 0000000 """Module for retrieving scene list from API."""
from typing import TYPE_CHECKING, List, Optional, Tuple
from pyvlx.log import PYVLXLOG
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameGetSceneListConfirmation, FrameGetSceneListNotification,
FrameGetSceneListRequest)
if TYPE_CHECKING:
from pyvlx import PyVLX
class GetSceneList(ApiEvent):
"""Class for retrieving scene list from API."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize SceneList class."""
super().__init__(pyvlx=pyvlx)
self.success = False
self.count_scenes: Optional[int] = None
self.scenes: List[Tuple[int, str]] = []
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if isinstance(frame, FrameGetSceneListConfirmation):
self.count_scenes = frame.count_scenes
if self.count_scenes == 0:
self.success = True
return True
# We are still waiting for FrameGetSceneListNotification(s)
return False
if isinstance(frame, FrameGetSceneListNotification):
self.scenes.extend(frame.scenes)
if frame.remaining_scenes != 0:
# We are still waiting for FrameGetSceneListConfirmation(s)
return False
if self.count_scenes != len(self.scenes):
PYVLXLOG.warning(
"Warning: number of received scenes does not match expected number"
)
self.success = True
return True
return False
def request_frame(self) -> FrameGetSceneListRequest:
"""Construct initiating frame."""
return FrameGetSceneListRequest()
pyvlx-0.2.26/pyvlx/api/get_state.py 0000664 0000000 0000000 00000002713 14734113327 0017257 0 ustar 00root root 0000000 0000000 """Module for retrieving gateway state from API."""
from typing import TYPE_CHECKING, Optional
from pyvlx.const import GatewayState, GatewaySubState
from pyvlx.dataobjects import DtoState
from .api_event import ApiEvent
from .frames import FrameBase, FrameGetStateConfirmation, FrameGetStateRequest
if TYPE_CHECKING:
from pyvlx import PyVLX
class GetState(ApiEvent):
"""Class for retrieving gateway state from API."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize GetState class."""
super().__init__(pyvlx=pyvlx)
self.success = False
self.state = DtoState()
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if not isinstance(frame, FrameGetStateConfirmation):
return False
self.success = True
self.state = DtoState(frame.gateway_state, frame.gateway_sub_state)
return True
def request_frame(self) -> FrameGetStateRequest:
"""Construct initiating frame."""
return FrameGetStateRequest()
@property
def gateway_state(self) -> Optional[GatewayState]:
"""Return Gateway State as human readable string. Deprecated."""
return self.state.gateway_state
@property
def gateway_sub_state(self) -> Optional[GatewaySubState]:
"""Return Gateway Sub State as human readable string. Deprecated."""
return self.state.gateway_sub_state
pyvlx-0.2.26/pyvlx/api/get_version.py 0000664 0000000 0000000 00000002200 14734113327 0017613 0 ustar 00root root 0000000 0000000 """Module for retrieving firmware version from API."""
from typing import TYPE_CHECKING
from pyvlx.dataobjects import DtoVersion
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameGetVersionConfirmation, FrameGetVersionRequest)
if TYPE_CHECKING:
from pyvlx import PyVLX
class GetVersion(ApiEvent):
"""Class for retrieving firmware version from API."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize GetVersion class."""
super().__init__(pyvlx=pyvlx)
self.success = False
self.version = DtoVersion()
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if not isinstance(frame, FrameGetVersionConfirmation):
return False
self.version = DtoVersion(frame.software_version, frame.hardware_version,
frame.product_group, frame.product_type)
self.success = True
return True
def request_frame(self) -> FrameGetVersionRequest:
"""Construct initiating frame."""
return FrameGetVersionRequest()
pyvlx-0.2.26/pyvlx/api/house_status_monitor.py 0000664 0000000 0000000 00000003426 14734113327 0021577 0 ustar 00root root 0000000 0000000 """Module for house status monitor."""
from typing import TYPE_CHECKING
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameHouseStatusMonitorDisableConfirmation,
FrameHouseStatusMonitorDisableRequest,
FrameHouseStatusMonitorEnableConfirmation,
FrameHouseStatusMonitorEnableRequest)
if TYPE_CHECKING:
from pyvlx import PyVLX
class HouseStatusMonitorEnable(ApiEvent):
"""Class for enabling house status monotor."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize HouseStatusMonitorEnable class."""
super().__init__(pyvlx=pyvlx)
self.success = False
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if not isinstance(frame, FrameHouseStatusMonitorEnableConfirmation):
return False
self.success = True
return True
def request_frame(self) -> FrameHouseStatusMonitorEnableRequest:
"""Construct initiating frame."""
return FrameHouseStatusMonitorEnableRequest()
class HouseStatusMonitorDisable(ApiEvent):
"""Class for disabling house status monotor."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize HouseStatusMonitorEnable class."""
super().__init__(pyvlx=pyvlx)
self.success = False
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if not isinstance(frame, FrameHouseStatusMonitorDisableConfirmation):
return False
self.success = True
return True
def request_frame(self) -> FrameHouseStatusMonitorDisableRequest:
"""Construct initiating frame."""
return FrameHouseStatusMonitorDisableRequest()
pyvlx-0.2.26/pyvlx/api/leave_learn_state.py 0000664 0000000 0000000 00000002063 14734113327 0020753 0 ustar 00root root 0000000 0000000 """Module for handling the login to API."""
from typing import TYPE_CHECKING
from pyvlx.dataobjects import DtoLeaveLearnState
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameLeaveLearnStateConfirmation, FrameLeaveLearnStateRequest)
if TYPE_CHECKING:
from pyvlx import PyVLX
class LeaveLearnState(ApiEvent):
"""Class for handling leave learn state to API."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize leave learn state class."""
super().__init__(pyvlx=pyvlx)
self.status = DtoLeaveLearnState()
self.success = False
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if not isinstance(frame, FrameLeaveLearnStateConfirmation):
return False
self.status.status = frame.status
self.success = True
return True
def request_frame(self) -> FrameLeaveLearnStateRequest:
"""Construct initiating frame."""
return FrameLeaveLearnStateRequest()
pyvlx-0.2.26/pyvlx/api/password_enter.py 0000664 0000000 0000000 00000002501 14734113327 0020332 0 ustar 00root root 0000000 0000000 """Module for handling the login to API."""
from typing import TYPE_CHECKING
from pyvlx.log import PYVLXLOG
from .api_event import ApiEvent
from .frames import (
FrameBase, FramePasswordEnterConfirmation, FramePasswordEnterRequest,
PasswordEnterConfirmationStatus)
if TYPE_CHECKING:
from pyvlx import PyVLX
class PasswordEnter(ApiEvent):
"""Class for handling login to API."""
def __init__(self, pyvlx: "PyVLX", password: str):
"""Initialize login class."""
super().__init__(pyvlx=pyvlx)
self.password = password
self.success = False
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if not isinstance(frame, FramePasswordEnterConfirmation):
return False
if frame.status == PasswordEnterConfirmationStatus.FAILED:
PYVLXLOG.warning(
'Failed to authenticate with password "%s****"', self.password[:2]
)
self.success = False
if frame.status == PasswordEnterConfirmationStatus.SUCCESSFUL:
self.success = True
return True
def request_frame(self) -> FramePasswordEnterRequest:
"""Construct initiating frame."""
return FramePasswordEnterRequest(password=self.password)
pyvlx-0.2.26/pyvlx/api/reboot.py 0000664 0000000 0000000 00000001763 14734113327 0016576 0 ustar 00root root 0000000 0000000 """Module for handling the Reboot to API."""
from typing import TYPE_CHECKING
from pyvlx.log import PYVLXLOG
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameGatewayRebootConfirmation, FrameGatewayRebootRequest)
if TYPE_CHECKING:
from pyvlx import PyVLX
class Reboot(ApiEvent):
"""Class for handling Reboot to API."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize Reboot class."""
super().__init__(pyvlx=pyvlx)
self.pyvlx = pyvlx
self.success = False
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if isinstance(frame, FrameGatewayRebootConfirmation):
PYVLXLOG.warning("KLF200 is rebooting")
self.success = True
return True
return False
def request_frame(self) -> FrameGatewayRebootRequest:
"""Construct initiating frame."""
return FrameGatewayRebootRequest()
pyvlx-0.2.26/pyvlx/api/session_id.py 0000664 0000000 0000000 00000001037 14734113327 0017435 0 ustar 00root root 0000000 0000000 """Module for generating a unique session_id."""
LAST_SESSION_ID = 0
MAX_SESSION_ID = 65535
def get_new_session_id() -> int:
"""Generate new session_id."""
global LAST_SESSION_ID # pylint: disable=global-statement
LAST_SESSION_ID = LAST_SESSION_ID + 1
if LAST_SESSION_ID > MAX_SESSION_ID:
LAST_SESSION_ID = 1
return LAST_SESSION_ID
def set_session_id(session_id: int) -> None:
"""Set session id to value."""
global LAST_SESSION_ID # pylint: disable=global-statement
LAST_SESSION_ID = session_id
pyvlx-0.2.26/pyvlx/api/set_node_name.py 0000664 0000000 0000000 00000002103 14734113327 0020071 0 ustar 00root root 0000000 0000000 """Module for changing a node name."""
from typing import TYPE_CHECKING
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameSetNodeNameConfirmation, FrameSetNodeNameRequest,
SetNodeNameConfirmationStatus)
if TYPE_CHECKING:
from pyvlx import PyVLX
class SetNodeName(ApiEvent):
"""Class for changing the name of a node via API."""
def __init__(self, pyvlx: "PyVLX", node_id: int, name: str):
"""Initialize class."""
super().__init__(pyvlx=pyvlx)
self.node_id = node_id
self.name = name
self.success = False
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if not isinstance(frame, FrameSetNodeNameConfirmation):
return False
self.success = frame.status == SetNodeNameConfirmationStatus.OK
return True
def request_frame(self) -> FrameSetNodeNameRequest:
"""Construct initiating frame."""
return FrameSetNodeNameRequest(node_id=self.node_id, name=self.name)
pyvlx-0.2.26/pyvlx/api/set_utc.py 0000664 0000000 0000000 00000001762 14734113327 0016751 0 ustar 00root root 0000000 0000000 """Module for setting UTC time within gateway."""
import time
from typing import TYPE_CHECKING
from .api_event import ApiEvent
from .frames import FrameBase, FrameSetUTCConfirmation, FrameSetUTCRequest
if TYPE_CHECKING:
from pyvlx import PyVLX
class SetUTC(ApiEvent):
"""Class for setting UTC time within gateway."""
def __init__(self, pyvlx: "PyVLX", timestamp: float = time.time()):
"""Initialize SetUTC class."""
super().__init__(pyvlx=pyvlx)
self.timestamp = timestamp
self.success = False
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if not isinstance(frame, FrameSetUTCConfirmation):
return False
self.success = True
return True
def request_frame(self) -> FrameSetUTCRequest:
"""Construct initiating frame."""
timestamp = int(self.timestamp)
return FrameSetUTCRequest(timestamp=timestamp)
pyvlx-0.2.26/pyvlx/api/status_request.py 0000664 0000000 0000000 00000003232 14734113327 0020370 0 ustar 00root root 0000000 0000000 """Module for retrieving node information from API."""
from typing import TYPE_CHECKING, Optional
from .api_event import ApiEvent
from .frames import (
FrameBase, FrameStatusRequestConfirmation, FrameStatusRequestNotification,
FrameStatusRequestRequest)
from .session_id import get_new_session_id
if TYPE_CHECKING:
from pyvlx import PyVLX
class StatusRequest(ApiEvent):
"""Class for retrieving node informationfrom API."""
def __init__(self, pyvlx: "PyVLX", node_id: int):
"""Initialize SceneList class."""
super().__init__(pyvlx=pyvlx)
self.node_id = node_id
self.success = False
self.notification_frame: Optional[FrameStatusRequestNotification] = None
self.session_id: Optional[int] = None
async def handle_frame(self, frame: FrameBase) -> bool:
"""Handle incoming API frame, return True if this was the expected frame."""
if (
isinstance(frame, FrameStatusRequestConfirmation)
and frame.session_id == self.session_id
):
# We are still waiting for StatusRequestNotification
return False
if (
isinstance(frame, FrameStatusRequestNotification)
and frame.session_id == self.session_id
):
self.notification_frame = frame
self.success = True
return True
return False
def request_frame(self) -> FrameStatusRequestRequest:
"""Construct initiating frame."""
self.session_id = get_new_session_id()
return FrameStatusRequestRequest(
session_id=self.session_id,
node_ids=[self.node_id]
)
pyvlx-0.2.26/pyvlx/config.py 0000664 0000000 0000000 00000003623 14734113327 0015775 0 ustar 00root root 0000000 0000000 """Module for configuration."""
from typing import TYPE_CHECKING, Any, Optional, cast
import yaml
from .exception import PyVLXException
from .log import PYVLXLOG
if TYPE_CHECKING:
from pyvlx import PyVLX
class Config:
"""Object for configuration."""
DEFAULT_PORT = 51200
def __init__(self,
pyvlx: "PyVLX",
path: Optional[str] = None,
host: Optional[str] = None,
password: Optional[str] = None,
port: Optional[int] = None):
"""Initialize Config class."""
self.pyvlx = pyvlx
self.host = host
self.password = password
self.port = port or self.DEFAULT_PORT
if path is not None:
self.read_config(path)
def read_config(self, path: str) -> None:
"""Read configuration file."""
PYVLXLOG.info("Reading config file: %s", path)
try:
with open(path, "r", encoding="utf-8") as filehandle:
doc = yaml.safe_load(filehandle)
self.test_configuration(doc, path)
self.host = cast(str, doc["config"]["host"])
self.password = cast(str, doc["config"]["password"])
if "port" in doc["config"]:
self.port = doc["config"]["port"]
except FileNotFoundError as not_found:
raise PyVLXException("file does not exist: {0}".format(not_found)) from not_found
@staticmethod
def test_configuration(doc: Any, path: str) -> None:
"""Test if configuration file is sane."""
if "config" not in doc:
raise PyVLXException("no element config found in: {0}".format(path))
if "host" not in doc["config"]:
raise PyVLXException("no element host found in: {0}".format(path))
if "password" not in doc["config"]:
raise PyVLXException("no element password found in: {0}".format(path))
pyvlx-0.2.26/pyvlx/connection.py 0000664 0000000 0000000 00000015150 14734113327 0016665 0 ustar 00root root 0000000 0000000 """Module for handling the TCP connection with Gateway."""
import asyncio
import ssl
import sys
from typing import Callable, Coroutine, List, Optional, Set
from .api.frame_creation import frame_from_raw
from .api.frames import FrameBase
from .config import Config
from .exception import PyVLXException
from .log import PYVLXLOG
from .slip import get_next_slip, is_slip, slip_pack
class SlipTokenizer:
"""Helper class for splitting up binary stream to slip packets."""
def __init__(self) -> None:
"""Init Tokenizer."""
self.data = bytes()
def feed(self, chunk: bytes) -> None:
"""Feed chunk to tokenizer."""
if not chunk:
return
self.data += chunk
def has_tokens(self) -> bool:
"""Return True if Tokenizer has tokens."""
return is_slip(self.data)
def get_next_token(self) -> Optional[bytes]:
"""Get next token from Tokenizer."""
slip, self.data = get_next_slip(self.data)
return slip
class TCPTransport(asyncio.Protocol):
"""Class for handling asyncio connection transport."""
def __init__(
self,
frame_received_cb: Callable[[FrameBase], None],
connection_lost_cb: Callable[[], None],
):
"""Init TCPTransport."""
self.frame_received_cb = frame_received_cb
self.connection_lost_cb = connection_lost_cb
self.tokenizer = SlipTokenizer()
def connection_made(self, transport: object) -> None:
"""Handle sucessful connection."""
PYVLXLOG.debug("Socket connection to KLF 200 opened")
def data_received(self, data: bytes) -> None:
"""Handle data received."""
self.tokenizer.feed(data)
while self.tokenizer.has_tokens():
raw = self.tokenizer.get_next_token()
assert raw is not None
try:
frame = frame_from_raw(raw)
if frame is not None:
self.frame_received_cb(frame)
except PyVLXException:
PYVLXLOG.error("Error in data_received", exc_info=sys.exc_info())
def connection_lost(self, exc: object) -> None:
"""Handle lost connection."""
PYVLXLOG.debug("Socket connection to KLF 200 has been lost")
self.connection_lost_cb()
CallbackType = Callable[[FrameBase], Coroutine]
class Connection:
"""Class for handling TCP connection."""
def __init__(self, loop: asyncio.AbstractEventLoop, config: Config):
"""Init TCP connection."""
self.loop = loop
self.config = config
self.transport: Optional[asyncio.Transport] = None
self.frame_received_cbs: List[CallbackType] = []
self.connection_closed_cbs: List[Callable[[], Coroutine]] = []
self.connection_opened_cbs: List[Callable[[], Coroutine]] = []
self.connected = False
self.connection_counter = 0
self.tasks: Set[asyncio.Task] = set()
def __del__(self) -> None:
"""Destruct connection."""
self.disconnect()
def disconnect(self) -> None:
"""Disconnect connection."""
if self.transport is not None:
self.transport.close()
self.transport = None
self.connected = False
PYVLXLOG.debug("TCP transport closed.")
for connection_closed_cb in self.connection_closed_cbs:
if asyncio.iscoroutine(connection_closed_cb()):
task = self.loop.create_task(connection_closed_cb())
self.tasks.add(task)
task.add_done_callback(self.tasks.remove)
async def connect(self) -> None:
"""Connect to gateway via SSL."""
tcp_client = TCPTransport(self.frame_received_cb, connection_lost_cb=self.on_connection_lost)
assert self.config.host is not None
self.transport, _ = await self.loop.create_connection(
lambda: tcp_client,
host=self.config.host,
port=self.config.port,
ssl=self.create_ssl_context(),
)
self.connected = True
self.connection_counter += 1
PYVLXLOG.debug(
"Amount of connections since last HA start: %s", self.connection_counter
)
for connection_opened_cb in self.connection_opened_cbs:
if asyncio.iscoroutine(connection_opened_cb()):
task = self.loop.create_task(connection_opened_cb())
self.tasks.add(task)
task.add_done_callback(self.tasks.remove)
def register_frame_received_cb(self, callback: CallbackType) -> None:
"""Register frame received callback."""
self.frame_received_cbs.append(callback)
def unregister_frame_received_cb(self, callback: CallbackType) -> None:
"""Unregister frame received callback."""
self.frame_received_cbs.remove(callback)
def register_connection_closed_cb(self, callback: Callable[[], Coroutine]) -> None:
"""Register connection closed callback."""
self.connection_closed_cbs.append(callback)
def unregister_connection_closed_cb(self, callback: Callable[[], Coroutine]) -> None:
"""Unregister connection closed callback."""
self.connection_closed_cbs.remove(callback)
def register_connection_opened_cb(self, callback: Callable[[], Coroutine]) -> None:
"""Register connection opened callback."""
self.connection_opened_cbs.append(callback)
def unregister_connection_opened_cb(self, callback: Callable[[], Coroutine]) -> None:
"""Unregister connection opened callback."""
self.connection_opened_cbs.remove(callback)
def write(self, frame: FrameBase) -> None:
"""Write frame to Bus."""
if not isinstance(frame, FrameBase):
raise PyVLXException("Frame not of type FrameBase", *type(frame))
PYVLXLOG.debug("SEND: %s", frame)
assert self.transport is not None
self.transport.write(slip_pack(bytes(frame)))
@staticmethod
def create_ssl_context() -> ssl.SSLContext:
"""Create and return SSL Context."""
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
return ssl_context
def frame_received_cb(self, frame: FrameBase) -> None:
"""Received message."""
PYVLXLOG.debug("REC: %s", frame)
for frame_received_cb in self.frame_received_cbs:
# pylint: disable=not-callable
task = self.loop.create_task(frame_received_cb(frame))
self.tasks.add(task)
task.add_done_callback(self.tasks.remove)
def on_connection_lost(self) -> None:
"""Server closed connection."""
self.disconnect()
pyvlx-0.2.26/pyvlx/const.py 0000664 0000000 0000000 00000060466 14734113327 0015666 0 ustar 00root root 0000000 0000000 """Module for enum and consts."""
from enum import Enum
from typing_extensions import Any
class Command(Enum):
"""Enum class for GW Command bytes."""
# pylint: disable=invalid-name
GW_ERROR_NTF = 0x0000
GW_REBOOT_REQ = 0x0001
GW_REBOOT_CFM = 0x0002
GW_SET_FACTORY_DEFAULT_REQ = 0x0003
GW_SET_FACTORY_DEFAULT_CFM = 0x0004
GW_GET_VERSION_REQ = 0x0008
GW_GET_VERSION_CFM = 0x0009
GW_GET_PROTOCOL_VERSION_REQ = 0x000A
GW_GET_PROTOCOL_VERSION_CFM = 0x000B
GW_GET_STATE_REQ = 0x000C
GW_GET_STATE_CFM = 0x000D
GW_LEAVE_LEARN_STATE_REQ = 0x000E
GW_LEAVE_LEARN_STATE_CFM = 0x000F
GW_GET_NETWORK_SETUP_REQ = 0x00E0
GW_GET_NETWORK_SETUP_CFM = 0x00E1
GW_SET_NETWORK_SETUP_REQ = 0x00E2
GW_SET_NETWORK_SETUP_CFM = 0x00E3
GW_CS_GET_SYSTEMTABLE_DATQ_REQ = 0x0100
GW_CS_GET_SYSTEMTABLE_DATA_CFM = 0x0101
GW_CS_GET_SYSTEMTABLE_DATA_NTF = 0x0102
GW_CS_DISCOVER_NODES_REQ = 0x0103
GW_CS_DISCOVER_NODES_CFM = 0x0104
GW_CS_DISCOVER_NODES_NTF = 0x0105
GW_CS_REMOVE_NODES_REQ = 0x0106
GW_CS_REMOVE_NODES_CFM = 0x0107
GW_CS_VIRGIN_STATE_REQ = 0x0108
GW_CS_VIRGIN_STATE_CFM = 0x0109
GW_CS_CONTROLLER_COPY_REQ = 0x010A
GW_CS_CONTROLLER_COPY_CFM = 0x010B
GW_CS_CONTROLLER_COPY_NTF = 0x010C
GW_CS_CONTROLLER_COPY_CANCEL_NTF = 0x010D
GW_CS_RECEIVE_KEY_REQ = 0x010E
GW_CS_RECEIVE_KEY_CFM = 0x010F
GW_CS_RECEIVE_KEY_NTF = 0x0110
GW_CS_PGC_JOB_NTF = 0x0111
GW_CS_SYSTEM_TABLE_UPDATE_NTF = 0x0112
GW_CS_GENERATE_NEW_KEY_REQ = 0x0113
GW_CS_GENERATE_NEW_KEY_CFM = 0x0114
GW_CS_GENERATE_NEW_KEY_NTF = 0x0115
GW_CS_REPAIR_KEY_REQ = 0x0116
GW_CS_REPAIR_KEY_CFM = 0x0117
GW_CS_REPAID_KEY_NTF = 0x0118
GW_CS_ACTIVATE_CONFIGURATION_MODE_REQ = 0x0119
GW_CS_ACTIVATE_CONFIGURATION_MODE_CFM = 0x011A
GW_GET_NODE_INFORMATION_REQ = 0x0200
GW_GET_NODE_INFORMATION_CFM = 0x0201
GW_GET_NODE_INFORMATION_NTF = 0x0210
GW_GET_ALL_NODES_INFORMATION_REQ = 0x0202
GW_GET_ALL_NODES_INFORMATION_CFM = 0x0203
GW_GET_ALL_NODES_INFORMATION_NTF = 0x0204
GW_GET_ALL_NODES_INFORMATION_FINISHED_NTF = 0x0205
GW_SET_NODE_VARIATION_REQ = 0x0206
GW_SET_NODE_VARIATION_CFM = 0x0207
GW_SET_NODE_NAME_REQ = 0x0208
GW_SET_NODE_NAME_CFM = 0x0209
GW_SET_NODE_VELOCITY_REQ = 0x020A
GW_SET_NODE_VELOCITY_CFM = 0x020B
GW_NODE_INFORMATION_CHANGED_NTF = 0x020C
GW_NODE_STATE_POSITION_CHANGED_NTF = 0x0211
GW_SET_NODE_ORDER_AND_PLACEMENT_REQ = 0x020D
GW_SET_NODE_ORDER_AND_PLACEMENT_CFM = 0x020E
GW_GET_GROUP_INFORMATION_REQ = 0x0220
GW_GET_GROUP_INFORMATION_CFM = 0x0221
GW_GET_GROUP_INFORMATION_NTF = 0x0230
GW_SET_GROUP_INFORMATION_REQ = 0x0222
GW_SET_GROUP_INFORMATION_CFM = 0x0223
GW_GROUP_INFORMATION_CHANGED_NTF = 0x0224
GW_DELETE_GROUP_REQ = 0x0225
GW_DELETE_GROUP_CFM = 0x0226
GW_NEW_GROUP_REQ = 0x0227
GW_NEW_GROUP_CFM = 0x0228
GW_GET_ALL_GROUPS_INFORMATION_REQ = 0x0229
GW_GET_ALL_GROUPS_INFORMATION_CFM = 0x022A
GW_GET_ALL_GROUPS_INFORMATION_NTF = 0x022B
GW_GET_ALL_GROUPS_INFORMATION_FINISHED_NTF = 0x022C
GW_GROUP_DELETED_NTF = 0x022D
GW_HOUSE_STATUS_MONITOR_ENABLE_REQ = 0x0240
GW_HOUSE_STATUS_MONITOR_ENABLE_CFM = 0x0241
GW_HOUSE_STATUS_MONITOR_DISABLE_REQ = 0x0242
GW_HOUSE_STATUS_MONITOR_DISABLE_CFM = 0x0243
GW_COMMAND_SEND_REQ = 0x0300
GW_COMMAND_SEND_CFM = 0x0301
GW_COMMAND_RUN_STATUS_NTF = 0x0302
GW_COMMAND_REMAINING_TIME_NTF = 0x0303
GW_SESSION_FINISHED_NTF = 0x0304
GW_STATUS_REQUEST_REQ = 0x0305
GW_STATUS_REQUEST_CFM = 0x0306
GW_STATUS_REQUEST_NTF = 0x0307
GW_WINK_SEND_REQ = 0x0308
GW_WINK_SEND_CFM = 0x0309
GW_WINK_SEND_NTF = 0x030A
GW_SET_LIMITATION_REQ = 0x0310
GW_SET_LIMITATION_CFM = 0x0311
GW_GET_LIMITATION_STATUS_REQ = 0x0312
GW_GET_LIMITATION_STATUS_CFM = 0x0313
GW_LIMITATION_STATUS_NTF = 0x0314
GW_MODE_SEND_REQ = 0x0320
GW_MODE_SEND_CFM = 0x0321
GW_MODE_SEND_NTF = 0x0322
GW_INITIALIZE_SCENE_REQ = 0x0400
GW_INITIALIZE_SCENE_CFM = 0x0401
GW_INITIALIZE_SCENE_NTF = 0x0402
GW_INITIALIZE_SCENE_CANCEL_REQ = 0x0403
GW_INITIALIZE_SCENE_CANCEL_CFM = 0x0404
GW_RECORD_SCENE_REQ = 0x0405
GW_RECORD_SCENE_CFM = 0x0406
GW_RECORD_SCENE_NTF = 0x0407
GW_DELETE_SCENE_REQ = 0x0408
GW_DELETE_SCENE_CFM = 0x0409
GW_RENAME_SCENE_REQ = 0x040A
GW_RENAME_SCENE_CFM = 0x040B
GW_GET_SCENE_LIST_REQ = 0x040C
GW_GET_SCENE_LIST_CFM = 0x040D
GW_GET_SCENE_LIST_NTF = 0x040E
GW_GET_SCENE_INFORMATION_REQ = 0x040F
GW_GET_SCENE_INFORMATION_CFM = 0x0410
GW_GET_SCENE_INFORMATION_NTF = 0x0411
GW_ACTIVATE_SCENE_REQ = 0x0412
GW_ACTIVATE_SCENE_CFM = 0x0413
GW_STOP_SCENE_REQ = 0x0415
GW_STOP_SCENE_CFM = 0x0416
GW_SCENE_INFORMATION_CHANGED_NTF = 0x0419
GW_ACTIVATE_PRODUCTGROUP_REQ = 0x0447
GW_ACTIVATE_PRODUCTGROUP_CFM = 0x0448
GW_ACTIVATE_PRODUCTGROUP_NTF = 0x0449
GW_GET_CONTACT_INPUT_LINK_LIST_REQ = 0x0460
GW_GET_CONTACT_INPUT_LINK_LIST_CFM = 0x0461
GW_SET_CONTACT_INPUT_LINK_REQ = 0x0462
GW_SET_CONTACT_INPUT_LINK_CFM = 0x0463
GW_REMOVE_CONTACT_INPUT_LINK_REQ = 0x0464
GW_REMOVE_CONTACT_INPUT_LINK_CFM = 0x0465
GW_GET_ACTIVATION_LOG_HEADER_REQ = 0x0500
GW_GET_ACTIVATION_LOG_HEADER_CFM = 0x0501
GW_CLEAR_ACTIVATION_LOG_REQ = 0x0502
GW_CLEAR_ACTIVATION_LOG_CFM = 0x0503
GW_GET_ACTIVATION_LOG_LINE_REQ = 0x0504
GW_GET_ACTIVATION_LOG_LINE_CFM = 0x0505
GW_ACTIVATION_LOG_UPDATED_NTF = 0x0506
GW_GET_MULTIPLE_ACTIVATION_LOG_LINES_REQ = 0x0507
GW_GET_MULTIPLE_ACTIVATION_LOG_LINES_NTF = 0x0508
GW_GET_MULTIPLE_ACTIVATION_LOG_LINES_CFN = 0x0509
GW_SET_UTC_REQ = 0x2000
GW_SET_UTC_CFM = 0x2001
GW_RTC_SET_TIME_ZONE_REQ = 0x2002
GW_RTC_SET_TIME_ZONE_CFM = 0x2003
GW_GET_LOCAL_TIME_REQ = 0x2004
GW_GET_LOCAL_TIME_CFM = 0x2005
GW_PASSWORD_ENTER_REQ = 0x3000
GW_PASSWORD_ENTER_CFM = 0x3001
GW_PASSWORD_CHANGE_REQ = 0x3002
GW_PASSWORD_CHANGE_CFM = 0x3003
GW_PASSWORD_CHANGE_NTF = 0x3004
class Originator(Enum):
"""Enum class for originator."""
# pylint: disable=line-too-long
USER = 1 # User Remote control causing action on actuator
RAIN = 2 # Rain sensor
TIMER = 3 # Timer controlled
UPS = 5 # UPC unit
SAAC = 8 # Stand Alone Automatic Controls
WIND = 9 # Wind sensor
LOAD_SHEDDING = 11 # Managers for requiring a particular electric load shed
LOCAL_LIGHT = 12 # Local light sensor
UNSPECIFIC_ENVIRONMENT_SENSOR = 13 # Used in context with commands transmitted on basis of an unknown sensor for protection of an end-product or house
EMERGENCY = 255 # Used in context with emergency or security commands
class Priority(Enum):
"""Enum class for priority."""
PROTECTION_HUMAN = 0
PROTECTION_ENVIRONMENT = 1
USER_LEVEL_1 = 2
USER_LEVEL_2 = 3
COMFORT_LEVEL_1 = 4
COMFORT_LEVEL_2 = 5
COMFORT_LEVEL_3 = 6
COMFORT_LEVEL_4 = 7
class LockPriorityLevel(Enum):
"""Enum Class for Lock Priority Level."""
NO = 0 # Do not lock any priority level.
MIN30 = 1 # Lock one or more priority level in 30 minutes.
FOREVER = 2 # Lock one or more priority level forever
class Velocity(Enum):
"""Enum class for velocity."""
DEFAULT = 0
SILENT = 1
FAST = 2
NOT_AVAILABLE = 255
class NodeTypeWithSubtype(Enum):
"""Enum class for node type plus sub type combined values."""
# pylint: disable=invalid-name
NO_TYPE = 0
INTERIOR_VENETIAN_BLIND = 0x0040
ROLLER_SHUTTER = 0x0080
ADJUSTABLE_SLUTS_ROLLING_SHUTTER = 0x0081
ADJUSTABLE_SLUTS_ROLLING_SHUTTER_WITH_PROJECTION = 0x0082
VERTICAL_EXTERIOR_AWNING = 0x00C0
WINDOW_OPENER = 0x0100
WINDOW_OPENER_WITH_RAIN_SENSOR = 0x0101
GARAGE_DOOR_OPENER = 0x0140
LINAR_ANGULAR_POSITION_OF_GARAGE_DOOR = 0x017A
LIGHT = 0x0180
LIGHT_ON_OFF = 0x01BA
GATE_OPENER = 0x01C0
GATE_OPENER_ANGULAR_POSITION = 0x01FA
DOOR_LOCK = 0x0240
WINDOW_LOCK = 0x0241
VERTICAL_INTERIOR_BLINDS = 0x0280
DUAL_ROLLER_SHUTTER = 0x0340
ON_OFF_SWITCH = 0x03C0
HORIZONTAL_AWNING = 0x0400
# Not documented by Velux but reported by some devices
# Velux support refuses to provide additional information about this node sub type
HORIZONTAL_AWNING_ALT = 0x0401
EXTERIOR_VENETIAN_BLIND = 0x0440
LOUVER_BLIND = 0x0480
CURTAIN_TRACK = 0x04C0
VENTILATION_POINT = 0x0500
VENTILATION_POINT_AIR_INLET = 0x0502
VENTILATION_POINT_AIR_TRANSFER = 0x0503
VENTILATION_POINT_AIR_OUTLET = 0x0503
EXTERIOR_HEATING = 0x0540
SWINGING_SHUTTERS = 0x0600
SWINGING_SHUTTER_WITH_INDEPENDENT_LEAVES = 0x0601
BLADE_OPENER = 0x0740
class NodeType(Enum):
"""Enum class for node types."""
NO_TYPE = 0
VENETIAN_BLIND = 1
ROLLER_SHUTTER = 2
AWNING = 3
WINDOW_OPENER = 4
GARAGE_OPENER = 5
LIGHT = 6
GATE_OPENER = 7
ROLLING_DOOR_OPENER = 8
LOCK = 9
BLIND = 10
SECURE_CONFIGURATION_DEVICE = 11
BEACON = 12
DUAL_SHUTTER = 13
HEATING_TEMPERATURE_INTERFACE = 14
ON_OFF_SWITCH = 15
HORIZONTAL_AWNING = 16
EXTERNAL_VENETIAN_BLIND = 17
LOUVRE_BLINT = 18
CURTAIN_TRACK = 19
VENTILATION_POINT = 20
EXTERIOR_HEATING = 21
HEAT_PUMP = 22
INTRUSION_ALARM = 23
SWINGING_SHUTTER = 24
class NodeVariation(Enum):
"""Enum class for node variations."""
NOT_SET = 0
TOPHUNG = 1
KIP = 2
FLAT_ROOT = 3
SKY_LIGHT = 3
class DHCPParameter(Enum):
"""Enum class for dncp network setup of gateway."""
DISABLE = 0x00
ENABLE = 0x01
class GatewayState(Enum):
"""Enum class for state of gateway."""
TEST_MODE = 0
GATEWAY_MODE_NO_ACTUATOR = 1
GATEWAY_MODE_WITH_ACTUATORS = 2
BEACON_MODE_NOT_CONFIGURED = 3
BEACON_MODE_CONFIGURED = 4
class GatewaySubState(Enum):
"""Enum class for substate of gateway."""
IDLE = 0x00
PERFORMING_TASK_CONFIGURATION_SERVICE_HANDLER = 0x01
PERFORMING_TASK_SCENE_CONFIGURATION = 0x02
PERFORMING_TASK_INFORMATION_SERVICE_CONFIGURATION = 0x03
PERFORMING_TASK_CONTACT_INPUT_CONFIGURATION = 0x04
PERFORMING_TASK_COMMAND = 0x80
PERFORMING_TASK_ACTIVATE_GROUP = 0x81
PERFORMING_TASK_ACTIVATE_SCENE = 0x82
RESERVED_132 = 0x84 # <-- hey @VELUX: Can you tell us what this value means?
class LeaveLearnStateConfirmationStatus(Enum):
"""Enum class for status leaving Learn state."""
FAILED = 0
SUCCESSFUL = 1
class ErrorNumber(Enum):
"""Enum class for Errornumber in GW_ERROR_NTF."""
UNDEFINED = 0 # Not further defined error.
WRONG_COMMAND = 1 # Unknown Command or command is not accepted at this state.
FRAME_ERROR = 2 # ERROR on Frame Structure.
BUSY = 7 # Busy. Try again later.
BAD_SYSTABLE_INDEX = 8 # Bad system table index.
NO_AUTH = 12 # Not authenticated.
class ControllerCopyMode(Enum):
"""Enum class for Copy Controller Mode."""
# pylint: disable=line-too-long
TCM = 0 # Transmitting Configuration Mode (TCM): The gateway gets key and system table from another controller.
RCM = 1 # Receiving Configuration Mode (RCM): The gateway gives key and system table to another controller.
class ControllerCopyStatus(Enum):
"""Enum class for Copy Controller Mode."""
OK = 0 # OK. Data transfer to or from client controller.
FAILED_TRANSFER = 1 # Failed. Data transfer to or from client controller interrupted.
CANCELLED = 4 # Ok. Receiving configuration mode is cancelled in the client controller.
FAILED_TIMEOUT = 5 # Failed. Timeout.
FAILED_NOTREADY = 11 # Failed. Configuration service not ready.
class ChangeKeyStatus(Enum):
"""Enum class for Key Change Status."""
# pylint: disable=line-too-long
OK_CONTROLLER = 0 # Ok. Key Change in client controller.
OK_ALL = 2 # Ok. Key change in system table all nodes updated with current key.
OK_PARTIALLY = 3 # Ok. Key Change in System table. Not all nodes in system table was updated with current key. Check bit array.
OK_RECEIVED = 5 # Ok. Client controller received a key.
FAILED_NOTDISABLED = 7 # Failed. Local Stimuli not disabled in all Client System table nodes. See bit array.
FAILED_NOCONTROLLER = 9 # Failed. Not able to find a controller to get key from.
FAILED_DTSNOTREADY = 10 # Failed. DTS not ready.
FAILED_DTSERROR = 11 # Failed. DTS error. At DTS error no key change will take place.
FAILED_CSNOTREADY = 16 # Failed. CS not ready.
class PgcJobState(Enum):
"""Enum class for Product Generic Configuration Job State."""
STARTED = 0 # PGC job started
ENDED = 1 # PGC job ended. Either OK or with error.
CS_BUSY = 2 # CS busy with other services
class PgcJobStatus(Enum):
"""Enum class for Product Generic Configuration Job Status."""
OK = 0 # OK - PGC and CS job completed
OK_PARTIALLY = 1 # Partly success.
FAILED_PGCCS = 2 # Failed - Error in PGC/CS job.
FAILED = 3 # Failed - Too long key press or cancel of CS service.
class PgcJobType(Enum):
"""Enum class for Product Generic Configuration Job Type."""
# pylint: disable=line-too-long
RECEIVE_ONLY = 0 # Receive system copy or only get key. Short PGC button press.
RECEIVE_DISTRIBUTE = 1 # Receive key and distribute. Short PGC button press.
TRANSMIT = 2 # Transmit key (and system). Long PGC button press.
GENERATE = 3 # Generate new key and distribute or only generate new key. Very long PGC button press.
class DiscoverStatus(Enum):
"""Enum class for Discovery status."""
# pylint: disable=line-too-long
OK = 0 # OK. Discovered nodes. See bit array.
FAILED_CSNOTREADY = 5 # Failed. CS not ready.
OK_PARTIALLY = 6 # OK. Same as DISCOVER_NODES_PERFORMED but some nodes were not added to system table (e.g. System table has reached its limit).
FAILED_CSBUSY = 7 # CS busy with another task.
class PowerMode(Enum):
"""Enum class for Acutuator power Mode."""
ALWAYS_ALIVE = 0 # ALWAYS_ALIVE
LOW_POWER_MODE = 1 # LOW_POWER_MODE
class ChangeType(Enum):
"""Enum class Change Type in Group or Scene NTF."""
DELETED = 0 # Scene or Group deleted
MODIFIED = 1 # Information modified
class ContactInputAssignement(Enum):
"""Enum class for Contact Input."""
NOT_ASSINGED = 0 # Input not assigned.
SCENE = 1 # Scene
PRODUCT_GROUP = 2 # Product group
BY_MODE = 3 # One node controlled by mode
class OutputID(Enum):
"""Enum class for Error and Success Output ID."""
DONT_SEND = 0 # Don’t send any pulse.
PULSE_PORT_1 = 1 # Send pulse to output port number 1
PULSE_PORT_2 = 2 # Send pulse to output port number 2
PULSE_PORT_3 = 3 # Send pulse to output port number 3
PULSE_PORT_4 = 4 # Send pulse to output port number 4
PULSE_PORT_5 = 5 # Send pulse to output port number 5
class GroupType(Enum):
"""Enum class for Group Types."""
USER_GROUP = 0 # The group type is a user group.
ROOM = 1 # The group type is a Room.
HOUSE = 2 # The group type is a House.
ALL_GROUP = 3 # The group type is an All-group.
class LimitationTimer(Enum):
"""Enum class for Limitation Timer."""
BY_SECONDS = 1 # 1=30 seconds 2=60 seconds 252=7590 seconds
UNLIMITED = 253 # unlimited
CLEAR_MASTER = 254 # clear entry for the Master
CLEAR_ALL = 255 # clear all
class LimitationType(Enum):
"""Enum class for Limitation Types."""
MIN_LIMITATION = 0 # Resulting minimum limitation.
MAX_LIMITATION = 1 # Resulting maximum limitation.
class LockTime(Enum):
"""Enum class for Lock Time."""
BY_SECONDS = 1 # 1=30 seconds, 2=60 seconds .. 254=7650 seconds
UNLIMITED = 255 # Unlimited time
class WinkTime(Enum):
"""Enum class for Wink Time."""
STOP = 0 # Stop wink.
BY_SECONDS = 1 # 1=Wink in 1 sec., 2= Wink in 2 sec. 253=Wink in 253 sec.
BY_MANUFACTUERER = 254 # Manufacturer specific wink time.
FOREVER = 255 # Wink forever.
class NodeParameter(Enum):
"""Enum Class for Node Parameter."""
MP = 0x00 # Main Parameter.
FP1 = 0x01 # Functional Parameter number 1.
FP2 = 0x02 # Functional Parameter number 2.
FP3 = 0x03 # Functional Parameter number 3.
FP4 = 0x04 # Functional Parameter number 4.
FP5 = 0x05 # Functional Parameter number 5.
FP6 = 0x06 # Functional Parameter number 6.
FP7 = 0x07 # Functional Parameter number 7.
FP8 = 0x08 # Functional Parameter number 8.
FP9 = 0x09 # Functional Parameter number 9.
FP10 = 0x0A # Functional Parameter number 10.
FP11 = 0x0B # Functional Parameter number 11.
FP12 = 0x0C # Functional Parameter number 12.
FP13 = 0x0D # Functional Parameter number 13.
FP14 = 0x0E # Functional Parameter number 14.
FP15 = 0x0F # Functional Parameter number 15.
FP16 = 0x10 # Functional Parameter number 16.
NOT_USED = 0xFF # Value to indicate Functional Parameter not used.
class OperatingState(Enum):
"""Enum Class for operating state of the node."""
NON_EXECUTING = 0
ERROR_EXECUTING = 1
NOT_USED = 2
WAIT_FOR_POWER = 3
EXECUTING = 4
DONE = 5
UNKNOWN = 255
@classmethod
def _missing_(cls, value: object) -> Any:
return cls.UNKNOWN
class StatusReply(Enum):
"""Enum Class for Node Status Reply."""
# pylint: disable=line-too-long
UNKNOWN_STATUS_REPLY = 0x00 # Used to indicate unknown reply.
COMMAND_COMPLETED_OK = 0x01 # Indicates no errors detected.
NO_CONTACT = 0x02 # Indicates no communication to node.
MANUALLY_OPERATED = 0x03 # Indicates manually operated by a user.
BLOCKED = 0x04 # Indicates node has been blocked by an object.
WRONG_SYSTEMKEY = 0x05 # Indicates the node contains a wrong system key.
PRIORITY_LEVEL_LOCKED = 0x06 # Indicates the node is locked on this priority level.
REACHED_WRONG_POSITION = 0x07 # Indicates node has stopped in another position than expected.
ERROR_DURING_EXECUTION = 0x08 # Indicates an error has occurred during execution of command.
NO_EXECUTION = 0x09 # Indicates no movement of the node parameter.
CALIBRATING = 0x0A # Indicates the node is calibrating the parameters.
POWER_CONSUMPTION_TOO_HIGH = 0x0B # Indicates the node power consumption is too high.
POWER_CONSUMPTION_TOO_LOW = 0x0C # Indicates the node power consumption is too low.
LOCK_POSITION_OPEN = 0x0D # Indicates door lock errors. (Door open during lock command)
MOTION_TIME_TOO_LONG__COMMUNICATION_ENDED = 0x0E # Indicates the target was not reached in time.
THERMAL_PROTECTION = 0x0F # Indicates the node has gone into thermal protection mode.
PRODUCT_NOT_OPERATIONAL = 0x10 # Indicates the node is not currently operational.
FILTER_MAINTENANCE_NEEDED = 0x11 # Indicates the filter needs maintenance.
BATTERY_LEVEL = 0x12 # Indicates the battery level is low.
TARGET_MODIFIED = 0x13 # Indicates the node has modified the target value of the command.
MODE_NOT_IMPLEMENTED = 0x14 # Indicates this node does not support the mode received.
COMMAND_INCOMPATIBLE_TO_MOVEMENT = 0x15 # Indicates the node is unable to move in the right direction.
USER_ACTION = 0x16 # Indicates dead bolt is manually locked during unlock command.
DEAD_BOLT_ERROR = 0x17 # Indicates dead bolt error.
AUTOMATIC_CYCLE_ENGAGED = 0x18 # Indicates the node has gone into automatic cycle mode.
WRONG_LOAD_CONNECTED = 0x19 # Indicates wrong load on node.
COLOUR_NOT_REACHABLE = 0x1A # Indicates that node is unable to reach received colour code.
TARGET_NOT_REACHABLE = 0x1B # Indicates the node is unable to reach received target position.
BAD_INDEX_RECEIVED = 0x1C # Indicates io-protocol has received an invalid index.
COMMAND_OVERRULED = 0x1D # Indicates that the command was overruled by a new command.
NODE_WAITING_FOR_POWER = 0x1E # Indicates that the node reported waiting for power.
INFORMATION_CODE = 0xDF # Indicates an unknown error code received. (Hex code is shown on display)
PARAMETER_LIMITED = 0xE0 # Indicates the parameter was limited by an unknown device. (Same as LIMITATION_BY_UNKNOWN_DEVICE)
LIMITATION_BY_LOCAL_USER = 0xE1 # Indicates the parameter was limited by local button.
LIMITATION_BY_USER = 0xE2 # Indicates the parameter was limited by a remote control.
LIMITATION_BY_RAIN = 0xE3 # Indicates the parameter was limited by a rain sensor.
LIMITATION_BY_TIMER = 0xE4 # Indicates the parameter was limited by a timer.
LIMITATION_BY_UPS = 0xE6 # Indicates the parameter was limited by a power supply.
LIMITATION_BY_UNKNOWN_DEVICE = 0xE7 # Indicates the parameter was limited by an unknown device. (Same as PARAMETER_LIMITED)
LIMITATION_BY_SAAC = 0xEA # Indicates the parameter was limited by a standalone automatic controller.
LIMITATION_BY_WIND = 0xEB # Indicates the parameter was limited by a wind sensor.
LIMITATION_BY_MYSELF = 0xEC # Indicates the parameter was limited by the node itself.
LIMITATION_BY_AUTOMATIC_CYCLE = 0xED # Indicates the parameter was limited by an automatic cycle.
LIMITATION_BY_EMERGENCY = 0xEE # Indicates the parameter was limited by an emergency.
@classmethod
def _missing_(cls, value: object) -> Any:
return cls.UNKNOWN_STATUS_REPLY
class StatusId(Enum):
"""Enum Class for Status ID Reply."""
# pylint: disable=line-too-long
STATUS_USER = 0x01 # The status is from a user activation.
STATUS_RAIN = 0x02 # The status is from a rain sensor activation.
STATUS_TIMER = 0x03 # The status is from a timer generated action.
STATUS_UPS = 0x05 # The status is from a UPS generated action.
STATUS_PROGRAM = 0x08 # The status is from an automatic program generated action. (SAAC)
STATUS_WIND = 0x09 # The status is from a Wind sensor generated action.
STATUS_MYSELF = 0x0A # The status is from an actuator generated action.
STATUS_AUTOMATIC_CYCLE = 0x0B # The status is from a automatic cycle generated action.
STATUS_EMERGENCY = 0x0C # The status is from an emergency or a security generated action.
STATUS_UNKNOWN = 0xFF # The status is from from an unknown command originator action.
@classmethod
def _missing_(cls, value: object) -> Any:
return cls.STATUS_UNKNOWN
class RunStatus(Enum):
"""Enum Class for Node RunStatus."""
EXECUTION_COMPLETED = 0 # Execution is completed with no errors.
EXECUTION_FAILED = 1 # Execution has failed. (Get specifics in the following error code)
EXECUTION_ACTIVE = 2 # Execution is still active.
class StatusType(Enum):
"""Enum class for Status Type for GW_STATUS_REQUEST_NTF frame."""
REQUEST_TARGET_POSITION = 0 # Request Target Position
REQUEST_CURRENT_POSITION = 1 # Request Current Position
REQUEST_REMAINING_TIME = 2 # Request Remaining Time
REQUEST_MAIN_INFO = 3 # Request Main Info
REQUEST_UNKNOWN = 255 # Request Unknown
@classmethod
def _missing_(cls, value: object) -> Any:
return cls.REQUEST_UNKNOWN
pyvlx-0.2.26/pyvlx/dataobjects.py 0000664 0000000 0000000 00000013141 14734113327 0017007 0 ustar 00root root 0000000 0000000 """Module for Dataobjects."""
import time
from datetime import datetime
from typing import Optional
from .const import (
DHCPParameter, GatewayState, GatewaySubState,
LeaveLearnStateConfirmationStatus)
class DtoLocalTime:
"""Dataobject to hold KLF200 Time Data."""
def __init__(self, utctime: Optional[datetime] = None, localtime: Optional[datetime] = None):
"""Initialize DtoLocalTime class."""
if utctime is None:
utctime = datetime.fromtimestamp(0)
if localtime is None:
localtime = datetime.fromtimestamp(0)
self.utctime = utctime
self.localtime = localtime
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} utctime="{}" localtime="{}"/>'.format(
type(self).__name__, self.utctime, self.localtime)
)
def from_payload(self, payload: bytes) -> None:
"""Build the Dto From Data."""
self.utctime = datetime.fromtimestamp(int.from_bytes(payload[0:4], "big"))
weekday = payload[11] - 1
if weekday == -1:
weekday = 6
self.localtime = datetime.fromtimestamp(time.mktime(
(int.from_bytes(payload[9:11], byteorder='big') + 1900, # Year
payload[8], # month
payload[7], # day
payload[6], # hour
payload[5], # minute
payload[4], # second
weekday,
int.from_bytes(payload[12:14], byteorder='big'), # day of year
int.from_bytes(payload[14:15], byteorder='big', signed=True))))
def to_payload(self) -> bytes:
"""Build the Dto From Data."""
payload = b''
payload = int(self.utctime.timestamp()).to_bytes(4, byteorder='big')
payload += self.localtime.second.to_bytes(1, "big")
payload += self.localtime.minute.to_bytes(1, "big")
payload += self.localtime.hour.to_bytes(1, "big")
payload += self.localtime.day.to_bytes(1, "big")
payload += self.localtime.month.to_bytes(1, "big")
payload += (self.localtime.year - 1900).to_bytes(2, "big")
if self.localtime.weekday == 6:
payload += (0).to_bytes(1, "big")
else:
payload += (self.localtime.weekday() + 1).to_bytes(1, "big")
payload += self.localtime.timetuple().tm_yday.to_bytes(2, "big")
payload += (self.localtime.timetuple().tm_isdst).to_bytes(1, "big", signed=True)
return payload
class DtoNetworkSetup:
"""Dataobject to hold KLF200 Network Setup."""
def __init__(self,
ipaddress: Optional[str] = None,
gateway: Optional[str] = None,
netmask: Optional[str] = None,
dhcp: Optional[DHCPParameter] = None):
"""Initialize DtoNetworkSetup class."""
self.ipaddress = ipaddress
self.gateway = gateway
self.netmask = netmask
self.dhcp = dhcp
def __str__(self) -> str:
"""Return human readable string."""
return '<{} ipaddress="{}" gateway="{}" gateway="{}" dhcp="{}"/>'.format(
type(self).__name__, self.ipaddress, self.gateway,
self.gateway, self.dhcp
)
class DtoProtocolVersion:
"""KLF 200 Dataobject for Protocol version."""
def __init__(self, majorversion: Optional[int] = None, minorversion: Optional[int] = None):
"""Initialize DtoProtocolVersion class."""
self.majorversion = majorversion
self.minorversion = minorversion
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} majorversion="{}" minorversion="{}"/>'.format(
type(self).__name__, self.majorversion, self.minorversion
)
)
class DtoState:
"""Data Object for Gateway State."""
def __init__(self, gateway_state: Optional[GatewayState] = None, gateway_sub_state: Optional[GatewaySubState] = None):
"""Initialize DtoState class."""
self.gateway_state = gateway_state
self.gateway_sub_state = gateway_sub_state
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} gateway_state="{}" gateway_sub_state="{}"/>'.format(
type(self).__name__, self.gateway_state, self.gateway_sub_state
)
)
class DtoVersion:
"""Object for KLF200 Version Information."""
def __init__(self,
softwareversion: Optional[str] = None,
hardwareversion: Optional[int] = None,
productgroup: Optional[int] = None,
producttype: Optional[int] = None):
"""Initialize DtoVersion class."""
self.softwareversion = softwareversion
self.hardwareversion = hardwareversion
self.productgroup = productgroup
self.producttype = producttype
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} softwareversion="{}" hardwareversion="{}" '
'productgroup="{}" producttype="{}"/>'.format(
type(self).__name__,
self.softwareversion, self.hardwareversion, self.productgroup, self.producttype
)
)
class DtoLeaveLearnState:
"""Dataobject to hold KLF200 Data."""
def __init__(self, status: Optional[LeaveLearnStateConfirmationStatus] = None):
"""Initialize DtoLeaveLearnState class."""
self.status = status
def __str__(self) -> str:
"""Return human readable string."""
return (
'<{} status="{}"/>'.format(
type(self).__name__, self.status
)
)
pyvlx-0.2.26/pyvlx/discovery.py 0000664 0000000 0000000 00000006566 14734113327 0016550 0 ustar 00root root 0000000 0000000 """Module to discover Velux KLF200 devices on the network."""
import asyncio
from asyncio import Event, Future, Task
from dataclasses import dataclass
from typing import Any, Optional, Set
from zeroconf import IPVersion
from zeroconf.asyncio import (
AsyncServiceBrowser, AsyncServiceInfo, AsyncZeroconf)
SERVICE_STARTS_WITH: str = "VELUX_KLF_LAN"
SERVICE_TYPE: str = "_http._tcp.local."
@dataclass
class VeluxHost():
"""Class to store Velux KLF200 host information."""
hostname: str
ip_address: str
class VeluxDiscovery():
"""Class to discover Velux KLF200 devices on the network."""
hosts: list[VeluxHost] = []
infos: list[AsyncServiceInfo | None] = []
def __init__(self, zeroconf: AsyncZeroconf,) -> None:
"""Initialize VeluxDiscovery object."""
self.zc: AsyncZeroconf = zeroconf
async def _async_discover_hosts(self, min_wait_time: float, expected_hosts: int | None) -> None:
"""Listen for zeroconf ServiceInfo."""
self.hosts.clear()
service_names: list[str] = []
tasks: Set[Task] = set()
got_host: Event = Event()
def add_info_and_host(fut: Future) -> None:
info: AsyncServiceInfo = fut.result()
self.infos.append(info)
host = VeluxHost(
hostname=info.name.replace("._http._tcp.local.", ""),
ip_address=info.parsed_addresses(version=IPVersion.V4Only)[0],
)
self.hosts.append(host)
got_host.set()
def handler(name: str, **kwargs: Any) -> None: # pylint: disable=W0613:unused-argument
if name.startswith(SERVICE_STARTS_WITH):
if name not in service_names:
service_names.append(name)
task = asyncio.create_task(self.zc.async_get_service_info(type_=SERVICE_TYPE, name=name))
tasks.add(task)
task.add_done_callback(add_info_and_host)
task.add_done_callback(tasks.remove)
browser: AsyncServiceBrowser = AsyncServiceBrowser(self.zc.zeroconf, SERVICE_TYPE, handlers=[handler])
if expected_hosts:
while len(self.hosts) < expected_hosts:
await got_host.wait()
got_host.clear()
while not self.hosts:
await asyncio.sleep(min_wait_time)
await browser.async_cancel()
if tasks:
await asyncio.gather(*tasks)
async def async_discover_hosts(
self,
timeout: float = 10,
min_wait_time: float = 3,
expected_hosts: Optional[int] = None
) -> bool:
"""Return true if Velux KLF200 devices found on the network.
This function creates a zeroconf AsyncServiceBrowser and
waits min_wait_time (seconds) for ServiceInfos from hosts.
Some devices may take some time to respond (i.e. if they currently have a high CPU load).
If one or more Hosts are found, the function cancels the ServiceBrowser and returns true.
If expected_hosts is set, the function ignores min_wait_time and returns true once expected_hosts are found.
If timeout (seconds) is exceeded, the function returns false.
"""
try:
async with asyncio.timeout(timeout):
await self._async_discover_hosts(min_wait_time, expected_hosts)
except TimeoutError:
return False
return True
pyvlx-0.2.26/pyvlx/exception.py 0000664 0000000 0000000 00000001402 14734113327 0016517 0 ustar 00root root 0000000 0000000 """Module for PyVLX Exceptions."""
from typing import Any
class PyVLXException(Exception):
"""Default PyVLX Exception."""
def __init__(self, description: str, **kwargs: Any):
"""Initialize PyVLXException class."""
super().__init__(description)
self.description = description
self.parameter = kwargs
def _format_parameter(self) -> str:
return " ".join(
[
'%s="%s"' % (key, value)
for (key, value) in sorted(self.parameter.items())
]
)
def __str__(self) -> str:
"""Return object as readable string."""
return '<{} description="{}" {}/>'.format(
type(self).__name__, self.description, self._format_parameter()
)
pyvlx-0.2.26/pyvlx/heartbeat.py 0000664 0000000 0000000 00000005420 14734113327 0016464 0 ustar 00root root 0000000 0000000 """Module for sending get state requests to API in regular periods."""
import asyncio
from typing import TYPE_CHECKING, Any
from .api import GetState
from .api.status_request import StatusRequest
from .exception import PyVLXException
from .log import PYVLXLOG
from .opening_device import Blind, DualRollerShutter
if TYPE_CHECKING:
from pyvlx import PyVLX
class Heartbeat:
"""Class for sending heartbeats to API."""
def __init__(
self, pyvlx: "PyVLX", interval: int = 30, load_all_states: bool = True
):
"""Initialize Heartbeat object."""
PYVLXLOG.debug("Heartbeat __init__")
self.pyvlx = pyvlx
self.interval = interval
self.load_all_states = load_all_states
self.task: Any = None
async def _run(self) -> None:
PYVLXLOG.debug("Heartbeat: task started")
while True:
PYVLXLOG.debug("Heartbeat: sleeping")
await asyncio.sleep(self.interval)
PYVLXLOG.debug("Heartbeat: pulsing")
try:
await self.pulse()
except (OSError, PyVLXException) as e:
PYVLXLOG.debug("Heartbeat: pulsing failed: %s", e)
async def _start(self) -> None:
if self.task is not None:
await self.stop()
PYVLXLOG.debug("Heartbeat: creating task")
self.task = asyncio.create_task(self._run())
def start(self) -> None:
"""Start heartbeat."""
PYVLXLOG.debug("Heartbeat start")
asyncio.run_coroutine_threadsafe(self._start(), self.pyvlx.loop)
@property
def stopped(self) -> bool:
"""Return Heartbeat running state."""
return self.task is None
async def stop(self) -> None:
"""Stop heartbeat."""
if self.task is not None:
self.task.cancel()
self.task = None
PYVLXLOG.debug("Heartbeat stopped")
else:
PYVLXLOG.debug("Heartbeat was not running")
async def pulse(self) -> None:
"""Send get state request to API to keep the connection alive."""
PYVLXLOG.debug("Heartbeat pulse")
get_state = GetState(pyvlx=self.pyvlx)
await get_state.do_api_call()
if not get_state.success:
raise PyVLXException("Unable to send get state.")
# If nodes contain Blind or DualRollerShutter device, refresh orientation or upper/lower curtain positions because House Monitoring
# delivers wrong values for FP1, FP2 and FP3 parameter
for node in self.pyvlx.nodes:
if isinstance(node, (Blind, DualRollerShutter)) or self.load_all_states:
status_request = StatusRequest(self.pyvlx, node.node_id)
await status_request.do_api_call()
# give user requests a chance
await asyncio.sleep(0.5)
pyvlx-0.2.26/pyvlx/klf200gateway.py 0000664 0000000 0000000 00000015455 14734113327 0017116 0 ustar 00root root 0000000 0000000 """Module for basic klf200 gateway functions."""
from typing import TYPE_CHECKING, Awaitable, Callable, List, Optional
from .api import (
FactoryDefault, GetLocalTime, GetNetworkSetup, GetProtocolVersion,
GetState, GetVersion, HouseStatusMonitorDisable, HouseStatusMonitorEnable,
LeaveLearnState, PasswordEnter, Reboot, SetUTC)
from .dataobjects import (
DtoLocalTime, DtoNetworkSetup, DtoProtocolVersion, DtoState, DtoVersion)
from .exception import PyVLXException
if TYPE_CHECKING:
from pyvlx import PyVLX
CallbackType = Callable[["Klf200Gateway"], Awaitable[None]]
class Klf200Gateway:
"""Class for node abstraction."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize Node object."""
self.pyvlx = pyvlx
self.state: Optional[DtoState] = None
self.network_setup: Optional[DtoNetworkSetup] = None
self.password: Optional[str] = None
self.time: Optional[DtoLocalTime] = None
self.protocol_version: Optional[DtoProtocolVersion] = None
self.version: Optional[DtoVersion] = None
self.device_updated_cbs: List[CallbackType] = []
self.house_status_monitor_enabled = False
def register_device_updated_cb(self, device_updated_cb: CallbackType) -> None:
"""Register device updated callback."""
self.device_updated_cbs.append(device_updated_cb)
def unregister_device_updated_cb(self, device_updated_cb: CallbackType) -> None:
"""Unregister device updated callback."""
self.device_updated_cbs.remove(device_updated_cb)
async def after_update(self) -> None:
"""Execute callbacks after internal state has been changed."""
for device_updated_cb in self.device_updated_cbs:
# pylint: disable=not-callable
await device_updated_cb(self)
async def get_state(self) -> bool:
"""Retrieve state from API."""
get_state = GetState(pyvlx=self.pyvlx)
await get_state.do_api_call()
if not get_state.success:
raise PyVLXException("Unable to retrieve state")
self.state = get_state.state
return get_state.success
async def get_network_setup(self) -> bool:
"""Retrieve network setup from API."""
get_network_setup = GetNetworkSetup(pyvlx=self.pyvlx)
await get_network_setup.do_api_call()
if not get_network_setup.success:
raise PyVLXException("Unable to retrieve network setup")
self.network_setup = get_network_setup.networksetup
return get_network_setup.success
async def get_version(self) -> bool:
"""Retrieve version from API."""
get_version = GetVersion(pyvlx=self.pyvlx)
await get_version.do_api_call()
if not get_version.success:
raise PyVLXException("Unable to retrieve version")
self.version = get_version.version
return get_version.success
async def get_protocol_version(self) -> bool:
"""Retrieve protocol version from API."""
get_protocol_version = GetProtocolVersion(pyvlx=self.pyvlx)
await get_protocol_version.do_api_call()
if not get_protocol_version.success:
raise PyVLXException("Unable to retrieve protocol version")
self.protocol_version = get_protocol_version.protocolversion
return get_protocol_version.success
async def leave_learn_state(self) -> bool:
"""Leave Learn state from API."""
leave_learn_state = LeaveLearnState(pyvlx=self.pyvlx)
await leave_learn_state.do_api_call()
if not leave_learn_state.success:
raise PyVLXException("Unable to leave learn state")
return leave_learn_state.success
async def set_utc(self) -> bool:
"""Set UTC Clock."""
setutc = SetUTC(pyvlx=self.pyvlx)
await setutc.do_api_call()
if not setutc.success:
raise PyVLXException("Unable to set utc.")
return setutc.success
async def set_rtc_time_zone(self) -> None:
"""Set the RTC Time Zone."""
# idontwant = setrtctimezone(pyvlx=self.pyvlx)
raise PyVLXException("KLF 200 RTC Timezone Set not implemented")
# return setrtctimezone.success
async def reboot(self) -> bool:
"""Reboot gateway."""
reboot = Reboot(pyvlx=self.pyvlx)
await reboot.do_api_call()
if not reboot.success:
raise PyVLXException("Unable to reboot gateway.")
await self.pyvlx.disconnect()
return reboot.success
async def set_factory_default(self) -> bool:
"""Set Gateway to Factory Default."""
factorydefault = FactoryDefault(pyvlx=self.pyvlx)
await factorydefault.do_api_call()
if not factorydefault.success:
raise PyVLXException("Unable to factory Default Reset gateway.")
return factorydefault.success
async def get_local_time(self) -> bool:
"""Get local time from gateway."""
getlocaltime = GetLocalTime(pyvlx=self.pyvlx)
await getlocaltime.do_api_call()
if not getlocaltime.success:
raise PyVLXException("Unable to get local time.")
self.time = getlocaltime.localtime
return getlocaltime.success
async def password_enter(self, password: str) -> bool:
"""Get enter Password for gateway."""
self.password = password
passwordenter = PasswordEnter(pyvlx=self.pyvlx, password=self.password)
await passwordenter.do_api_call()
if not passwordenter.success:
raise PyVLXException("Login to KLF 200 failed, check credentials")
return passwordenter.success
async def house_status_monitor_enable(self, pyvlx: "PyVLX") -> None:
"""Enable house status monitor."""
status_monitor_enable = HouseStatusMonitorEnable(pyvlx=pyvlx)
await status_monitor_enable.do_api_call()
if not status_monitor_enable.success:
raise PyVLXException("Unable enable house status monitor.")
self.house_status_monitor_enabled = True
async def house_status_monitor_disable(self, pyvlx: "PyVLX", timeout: Optional[int] = None) -> None:
"""Disable house status monitor."""
status_monitor_disable = HouseStatusMonitorDisable(pyvlx=pyvlx)
if timeout is not None:
status_monitor_disable.timeout_in_seconds = timeout
await status_monitor_disable.do_api_call()
if not status_monitor_disable.success:
raise PyVLXException("Unable disable house status monitor.")
self.house_status_monitor_enabled = False
def __str__(self) -> str:
"""Return object as readable string."""
return '<{} state="{}" network_setup="{}" version="{}" protocol_version="{}"/>'.format(
type(self).__name__,
str(self.state),
str(self.network_setup),
str(self.version),
str(self.protocol_version),
)
pyvlx-0.2.26/pyvlx/lightening_device.py 0000664 0000000 0000000 00000006135 14734113327 0020200 0 ustar 00root root 0000000 0000000 """Module for lights."""
from typing import TYPE_CHECKING, Optional
from .api import CommandSend
from .node import Node
from .parameter import Intensity
if TYPE_CHECKING:
from pyvlx import PyVLX
class LighteningDevice(Node):
"""Meta class for turning on device with one main parameter for intensity."""
def __init__(self, pyvlx: "PyVLX", node_id: int, name: str, serial_number: Optional[str]):
"""Initialize turning on device.
Parameters:
* pyvlx: PyVLX object
* node_id: internal id for addressing nodes.
Provided by KLF 200 device
* name: node name
* serial_number: serial number of the node.
"""
super().__init__(
pyvlx=pyvlx, node_id=node_id, name=name, serial_number=serial_number
)
self.intensity = Intensity()
async def set_intensity(self, intensity: Intensity, wait_for_completion: bool = True) -> None:
"""Set light to desired intensity.
Parameters:
* intensity: Intensity object containing the target intensity.
* wait_for_completion: If set, function will return
after device has reached target intensity.
"""
command = CommandSend(
pyvlx=self.pyvlx,
wait_for_completion=wait_for_completion,
node_id=self.node_id,
parameter=intensity,
)
await command.send()
await self.after_update()
async def turn_on(self, wait_for_completion: bool = True) -> None:
"""Turn on light.
Parameters:
* wait_for_completion: If set, function will return
after device has reached target intensity.
"""
await self.set_intensity(
intensity=Intensity(intensity_percent=0),
wait_for_completion=wait_for_completion,
)
async def turn_off(self, wait_for_completion: bool = True) -> None:
"""Turn off light.
Parameters:
* wait_for_completion: If set, function will return
after device has reached target intensity.
"""
await self.set_intensity(
intensity=Intensity(intensity_percent=100),
wait_for_completion=wait_for_completion,
)
class Light(LighteningDevice):
"""Light object."""
def __init__(self, pyvlx: "PyVLX", node_id: int, name: str, serial_number: Optional[str]):
"""Initialize Light class.
Parameters:
* pyvlx: PyVLX object
* node_id: internal id for addressing nodes.
Provided by KLF 200 device
* name: node name
* serial_number: serial number of the node.
"""
super().__init__(
pyvlx=pyvlx, node_id=node_id, name=name, serial_number=serial_number
)
def __str__(self) -> str:
"""Return object as readable string."""
return (
'<{} name="{}" '
'node_id="{}" '
'serial_number="{}"/>'.format(
type(self).__name__, self.name, self.node_id, self.serial_number
)
)
pyvlx-0.2.26/pyvlx/log.py 0000664 0000000 0000000 00000000144 14734113327 0015304 0 ustar 00root root 0000000 0000000 """Module for global PyVLX logging object."""
import logging
PYVLXLOG = logging.getLogger("pyvlx")
pyvlx-0.2.26/pyvlx/node.py 0000664 0000000 0000000 00000005040 14734113327 0015450 0 ustar 00root root 0000000 0000000 """
Module for basic object for nodes.
Node object is an interface class and should
be derived by other objects like window openers
and roller shutters.
"""
from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, Optional
from .api import SetNodeName
from .exception import PyVLXException
if TYPE_CHECKING:
from pyvlx import PyVLX
CallbackType = Callable[["Node"], Awaitable[None]]
class Node:
"""Class for node abstraction."""
def __init__(self, pyvlx: "PyVLX", node_id: int, name: str, serial_number: Optional[str]):
"""Initialize Node object."""
self.pyvlx = pyvlx
self.node_id = node_id
self.name = name
self.serial_number = serial_number
self.device_updated_cbs: List[CallbackType] = []
self.pyvlx.connection.register_connection_opened_cb(self.after_update)
self.pyvlx.connection.register_connection_closed_cb(self.after_update)
def register_device_updated_cb(self, device_updated_cb: CallbackType) -> None:
"""Register device updated callback."""
self.device_updated_cbs.append(device_updated_cb)
def unregister_device_updated_cb(self, device_updated_cb: CallbackType) -> None:
"""Unregister device updated callback."""
self.device_updated_cbs.remove(device_updated_cb)
async def after_update(self) -> None:
"""Execute callbacks after internal state has been changed."""
for device_updated_cb in self.device_updated_cbs:
# pylint: disable=not-callable
await self.pyvlx.loop.create_task(device_updated_cb(self)) # type: ignore
async def rename(self, name: str) -> None:
"""Change name of node."""
set_node_name = SetNodeName(pyvlx=self.pyvlx, node_id=self.node_id, name=name)
await set_node_name.do_api_call()
if not set_node_name.success:
raise PyVLXException("Unable to rename node")
self.name = name
@property
def is_available(self) -> bool:
"""Return True if node is available."""
return self.pyvlx.get_connected()
def __str__(self) -> str:
"""Return object as readable string."""
return (
'<{} name="{}" '
'node_id="{}" '
'serial_number="{}"/>'.format(
type(self).__name__, self.name, self.node_id, self.serial_number
)
)
def __eq__(self, other: Any) -> bool:
"""Equal operator."""
return (
type(self).__name__ == type(other).__name__
and self.__dict__ == other.__dict__
)
pyvlx-0.2.26/pyvlx/node_helper.py 0000664 0000000 0000000 00000012211 14734113327 0017005 0 ustar 00root root 0000000 0000000 """Helper module for Node objects."""
from typing import TYPE_CHECKING, Optional, Union
from .api.frames import (
FrameGetAllNodesInformationNotification,
FrameGetNodeInformationNotification)
from .const import NodeTypeWithSubtype
from .lightening_device import Light
from .log import PYVLXLOG
from .node import Node
from .on_off_switch import OnOffSwitch
from .opening_device import (
Awning, Blade, Blind, DualRollerShutter, GarageDoor, Gate, RollerShutter,
Window)
if TYPE_CHECKING:
from pyvlx import PyVLX
def convert_frame_to_node(
pyvlx: "PyVLX",
frame: Union[
FrameGetNodeInformationNotification, FrameGetAllNodesInformationNotification
],
) -> Optional[Node]:
"""Convert FrameGet[All]Node[s]InformationNotification into Node object."""
# pylint: disable=too-many-return-statements
if frame.node_type == NodeTypeWithSubtype.WINDOW_OPENER:
return Window(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
position_parameter=frame.current_position,
rain_sensor=False,
)
if frame.node_type == NodeTypeWithSubtype.WINDOW_OPENER_WITH_RAIN_SENSOR:
return Window(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
position_parameter=frame.current_position,
rain_sensor=True,
)
if frame.node_type == NodeTypeWithSubtype.DUAL_ROLLER_SHUTTER:
return DualRollerShutter(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
position_parameter=frame.current_position,
)
if frame.node_type in [
NodeTypeWithSubtype.ROLLER_SHUTTER,
NodeTypeWithSubtype.SWINGING_SHUTTERS,
]:
return RollerShutter(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
position_parameter=frame.current_position,
)
if frame.node_type in [
NodeTypeWithSubtype.INTERIOR_VENETIAN_BLIND,
NodeTypeWithSubtype.VERTICAL_INTERIOR_BLINDS,
NodeTypeWithSubtype.INTERIOR_VENETIAN_BLIND,
]:
return RollerShutter(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
)
# Blinds have position and orientation (inherit frame.current_position_fp3) attribute
if frame.node_type in [
NodeTypeWithSubtype.EXTERIOR_VENETIAN_BLIND,
NodeTypeWithSubtype.ADJUSTABLE_SLUTS_ROLLING_SHUTTER,
NodeTypeWithSubtype.LOUVER_BLIND,
]:
return Blind(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
position_parameter=frame.current_position,
)
if frame.node_type in [
NodeTypeWithSubtype.VERTICAL_EXTERIOR_AWNING,
NodeTypeWithSubtype.HORIZONTAL_AWNING,
NodeTypeWithSubtype.HORIZONTAL_AWNING_ALT,
]:
return Awning(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
position_parameter=frame.current_position,
)
if frame.node_type == NodeTypeWithSubtype.ON_OFF_SWITCH:
return OnOffSwitch(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
)
if frame.node_type in [
NodeTypeWithSubtype.GARAGE_DOOR_OPENER,
NodeTypeWithSubtype.LINAR_ANGULAR_POSITION_OF_GARAGE_DOOR,
]:
return GarageDoor(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
position_parameter=frame.current_position,
)
if frame.node_type == NodeTypeWithSubtype.GATE_OPENER:
return Gate(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
position_parameter=frame.current_position,
)
if frame.node_type == NodeTypeWithSubtype.GATE_OPENER_ANGULAR_POSITION:
return Gate(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
position_parameter=frame.current_position,
)
if frame.node_type == NodeTypeWithSubtype.BLADE_OPENER:
return Blade(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
position_parameter=frame.current_position,
)
if frame.node_type in [NodeTypeWithSubtype.LIGHT, NodeTypeWithSubtype.LIGHT_ON_OFF]:
return Light(
pyvlx=pyvlx,
node_id=frame.node_id,
name=frame.name,
serial_number=frame.serial_number,
)
PYVLXLOG.warning("%s not implemented", frame.node_type)
return None
pyvlx-0.2.26/pyvlx/node_updater.py 0000664 0000000 0000000 00000017202 14734113327 0017177 0 ustar 00root root 0000000 0000000 """Module for updating nodes via frames."""
import datetime
from typing import TYPE_CHECKING, Any
from .api.frames import (
FrameBase, FrameGetAllNodesInformationNotification,
FrameNodeStatePositionChangedNotification, FrameStatusRequestNotification)
from .const import NodeParameter, OperatingState
from .lightening_device import LighteningDevice
from .log import PYVLXLOG
from .on_off_switch import OnOffSwitch
from .opening_device import Blind, DualRollerShutter, OpeningDevice
from .parameter import Intensity, Parameter, Position, SwitchParameter
if TYPE_CHECKING:
from pyvlx import PyVLX
class NodeUpdater:
"""Class for updating nodes via incoming frames, usually received by house monitor."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize NodeUpdater object."""
self.pyvlx = pyvlx
async def process_frame_status_request_notification(
self, frame: FrameStatusRequestNotification
) -> None:
"""Process FrameStatusRequestNotification."""
PYVLXLOG.debug("NodeUpdater process frame: %s", frame)
if frame.node_id not in self.pyvlx.nodes:
return
node = self.pyvlx.nodes[frame.node_id]
if isinstance(node, Blind):
if NodeParameter(0) not in frame.parameter_data: # MP missing in frame
return
if NodeParameter(3) not in frame.parameter_data: # FP3 missing in frame
return
position = Position(frame.parameter_data[NodeParameter(0)])
orientation = Position(frame.parameter_data[NodeParameter(3)])
if position.position <= Parameter.MAX:
node.position = position
PYVLXLOG.debug("%s position changed to: %s", node.name, position)
if orientation.position <= Parameter.MAX:
node.orientation = orientation
PYVLXLOG.debug("%s orientation changed to: %s", node.name, orientation)
await node.after_update()
if isinstance(node, DualRollerShutter):
if NodeParameter(0) not in frame.parameter_data: # MP missing in frame
return
if NodeParameter(1) not in frame.parameter_data: # FP1 missing in frame
return
if NodeParameter(2) not in frame.parameter_data: # FP2 missing in frame
return
position = Position(frame.parameter_data[NodeParameter(0)])
position_upper_curtain = Position(frame.parameter_data[NodeParameter(1)])
position_lower_curtain = Position(frame.parameter_data[NodeParameter(2)])
if position.position <= Parameter.MAX:
node.position = position
PYVLXLOG.debug("%s position changed to: %s", node.name, position)
if position_upper_curtain.position <= Parameter.MAX:
node.position_upper_curtain = position_upper_curtain
PYVLXLOG.debug(
"%s position upper curtain changed to: %s",
node.name,
position_upper_curtain,
)
if position_lower_curtain.position <= Parameter.MAX:
node.position_lower_curtain = position_lower_curtain
PYVLXLOG.debug(
"%s position lower curtain changed to: %s",
node.name,
position_lower_curtain,
)
await node.after_update()
async def process_frame(self, frame: FrameBase) -> None:
"""Update nodes via frame, usually received by house monitor."""
if isinstance(
frame,
(
FrameGetAllNodesInformationNotification,
FrameNodeStatePositionChangedNotification,
),
):
PYVLXLOG.debug("NodeUpdater process frame: %s", frame)
if frame.node_id not in self.pyvlx.nodes:
return
node = self.pyvlx.nodes[frame.node_id]
position = Position(frame.current_position)
target: Any = Position(frame.target)
# KLF transmits for functional parameters basically always 'No feed-back value known’ (0xF7FF).
# In home assistant this cause unreasonable values like -23%. Therefore a check is implemented
# whether the frame parameter is inside the maximum range.
# Set opening device status
if isinstance(node, OpeningDevice):
if (position.position > target.position <= Parameter.MAX) and (
(frame.state == OperatingState.EXECUTING)
or frame.remaining_time > 0
):
node.is_opening = True
PYVLXLOG.debug("%s is opening", node.name)
node.state_received_at = datetime.datetime.now()
node.estimated_completion = (
node.state_received_at
+ datetime.timedelta(0, frame.remaining_time)
)
PYVLXLOG.debug(
"%s will be opening until", node.estimated_completion
)
elif (position.position < target.position <= Parameter.MAX) and (
(frame.state == OperatingState.EXECUTING)
or frame.remaining_time > 0
):
node.is_closing = True
PYVLXLOG.debug("%s is closing", node.name)
node.state_received_at = datetime.datetime.now()
node.estimated_completion = (
node.state_received_at
+ datetime.timedelta(0, frame.remaining_time)
)
PYVLXLOG.debug(
"%s will be closing until", node.estimated_completion
)
else:
if node.is_opening:
node.is_opening = False
node.state_received_at = None
node.estimated_completion = None
PYVLXLOG.debug("%s stops opening", node.name)
if node.is_closing:
node.is_closing = False
PYVLXLOG.debug("%s stops closing", node.name)
# Set main parameter
if isinstance(node, OpeningDevice):
if position.position <= Parameter.MAX:
node.position = position
node.target = target
PYVLXLOG.debug("%s position changed to: %s", node.name, position)
await node.after_update()
elif isinstance(node, LighteningDevice):
intensity = Intensity(frame.current_position)
if intensity.intensity <= Parameter.MAX:
node.intensity = intensity
PYVLXLOG.debug("%s intensity changed to: %s", node.name, intensity)
await node.after_update()
elif isinstance(node, OnOffSwitch):
state = SwitchParameter(frame.current_position)
target = SwitchParameter(frame.target)
if state.state == target.state:
if state.state == Parameter.ON:
node.parameter = state
PYVLXLOG.debug("%s state changed to: %s", node.name, state)
elif state.state == Parameter.OFF:
node.parameter = state
PYVLXLOG.debug("%s state changed to: %s", node.name, state)
await node.after_update()
elif isinstance(frame, FrameStatusRequestNotification):
await self.process_frame_status_request_notification(frame)
pyvlx-0.2.26/pyvlx/nodes.py 0000664 0000000 0000000 00000006675 14734113327 0015652 0 ustar 00root root 0000000 0000000 """Module for storing nodes."""
from typing import TYPE_CHECKING, Iterator, List, Optional, Union
from .api import GetAllNodesInformation, GetNodeInformation
from .exception import PyVLXException
from .node import Node
from .node_helper import convert_frame_to_node
if TYPE_CHECKING:
from pyvlx import PyVLX
class Nodes:
"""Object for storing node objects."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize Nodes object."""
self.pyvlx = pyvlx
self.__nodes: List[Node] = []
def __iter__(self) -> Iterator[Node]:
"""Iterate."""
yield from self.__nodes
def __getitem__(self, key: Union[str, int]) -> Node:
"""Return node by name or by index."""
if isinstance(key, int):
for node in self.__nodes:
if node.node_id == key:
return node
for node in self.__nodes:
if node.name == key:
return node
raise KeyError
def __contains__(self, key: Union[str, int, Node]) -> bool:
"""Check if key is in index."""
if isinstance(key, int):
for node in self.__nodes:
if node.node_id == key:
return True
if isinstance(key, Node):
for node in self.__nodes:
if node == key:
return True
for node in self.__nodes:
if node.name == key:
return True
return False
def __len__(self) -> int:
"""Return number of nodes."""
return len(self.__nodes)
def add(self, node: Node) -> None:
"""Add Node, replace existing node if node with node_id is present."""
if not isinstance(node, Node):
raise TypeError()
for i, j in enumerate(self.__nodes):
if j.node_id == node.node_id:
self.__nodes[i] = node
return
self.__nodes.append(node)
def clear(self) -> None:
"""Clear internal node array."""
self.__nodes = []
async def load(self, node_id: Optional[int] = None) -> None:
"""Load nodes from KLF 200, if no node_id is specified all nodes are loaded."""
if node_id is not None:
await self._load_node(node_id=node_id)
else:
await self._load_all_nodes()
async def _load_node(self, node_id: int) -> None:
"""Load single node via API."""
get_node_information = GetNodeInformation(pyvlx=self.pyvlx, node_id=node_id)
await get_node_information.do_api_call()
if not get_node_information.success:
raise PyVLXException("Unable to retrieve node information")
notification_frame = get_node_information.notification_frame
if notification_frame is None:
return
node = convert_frame_to_node(self.pyvlx, notification_frame)
if node is not None:
self.add(node)
async def _load_all_nodes(self) -> None:
"""Load all nodes via API."""
get_all_nodes_information = GetAllNodesInformation(pyvlx=self.pyvlx)
await get_all_nodes_information.do_api_call()
if not get_all_nodes_information.success:
raise PyVLXException("Unable to retrieve node information")
self.clear()
for notification_frame in get_all_nodes_information.notification_frames:
node = convert_frame_to_node(self.pyvlx, notification_frame)
if node is not None:
self.add(node)
pyvlx-0.2.26/pyvlx/on_off_switch.py 0000664 0000000 0000000 00000002560 14734113327 0017356 0 ustar 00root root 0000000 0000000 """Module for on/off switches."""
from typing import TYPE_CHECKING, Optional
from .api.command_send import CommandSend
from .node import Node
from .parameter import SwitchParameter, SwitchParameterOff, SwitchParameterOn
if TYPE_CHECKING:
from pyvlx import PyVLX
class OnOffSwitch(Node):
"""Class for controlling on-off switches."""
def __init__(self, pyvlx: "PyVLX", node_id: int, name: str, serial_number: Optional[str]):
"""Initialize opening device."""
super().__init__(
pyvlx=pyvlx, node_id=node_id, name=name, serial_number=serial_number
)
self.parameter = SwitchParameter()
async def set_state(self, parameter: SwitchParameter) -> None:
"""Set switch to desired state."""
command = CommandSend(
pyvlx=self.pyvlx, node_id=self.node_id, parameter=parameter
)
await command.send()
await self.after_update()
async def set_on(self) -> None:
"""Set switch on."""
await self.set_state(SwitchParameterOn())
async def set_off(self) -> None:
"""Set switch off."""
await self.set_state(SwitchParameterOff())
def is_on(self) -> bool:
"""Return if switch is set to on."""
return self.parameter.is_on()
def is_off(self) -> bool:
"""Return if switch is set to off."""
return self.parameter.is_off()
pyvlx-0.2.26/pyvlx/opening_device.py 0000664 0000000 0000000 00000052714 14734113327 0017513 0 ustar 00root root 0000000 0000000 """Module for Opening devices."""
import asyncio
import datetime
from asyncio import Task
from typing import TYPE_CHECKING, Any, Optional
from .api.command_send import CommandSend
from .api.get_limitation import GetLimitation
from .const import Velocity
from .exception import PyVLXException
from .node import Node
from .parameter import (
CurrentPosition, DualRollerShutterPosition, IgnorePosition, Parameter,
Position, TargetPosition)
if TYPE_CHECKING:
from pyvlx import PyVLX
class OpeningDevice(Node):
"""Meta class for opening device with one main parameter for position."""
def __init__(
self,
pyvlx: "PyVLX",
node_id: int,
name: str,
serial_number: Optional[str] = None,
position_parameter: Parameter = Parameter(),
):
"""Initialize opening device.
Parameters:
* pyvlx: PyVLX object
* node_id: internal id for addressing nodes.
Provided by KLF 200 device
* name: node name
* serial_number: serial number of the node.
* position_parameter: initial position of the opening device.
"""
super().__init__(
pyvlx=pyvlx, node_id=node_id, name=name, serial_number=serial_number
)
self.position: Position = Position(parameter=position_parameter)
self.target: Position = Position(parameter=position_parameter)
self.is_opening: bool = False
self.is_closing: bool = False
self.state_received_at: Optional[datetime.datetime] = None
self.estimated_completion: Optional[datetime.datetime] = None
self.use_default_velocity: bool = False
self.default_velocity: Velocity = Velocity.DEFAULT
self.open_position_target: int = 0
self.close_position_target: int = 100
self._update_task: Task | None = None
async def _update_calls(self) -> None:
"""While cover are moving, perform periodically update calls."""
while self.is_moving():
await asyncio.sleep(1)
await self.after_update()
if self._update_task:
self._update_task.cancel()
self._update_task = None
async def set_position(
self,
position: Position,
velocity: Velocity | int | None = Velocity.DEFAULT,
wait_for_completion: bool = True,
) -> None:
"""Set opening device to desired position.
Parameters:
* position: Position object containing the target position.
* velocity: Velocity to be used during transition.
* wait_for_completion: If set, function will return
after device has reached target position.
"""
kwargs: Any = {}
if (
velocity is None or velocity is Velocity.DEFAULT
) and self.use_default_velocity:
velocity = self.default_velocity
if isinstance(velocity, Velocity):
if velocity is not Velocity.DEFAULT:
if velocity is Velocity.SILENT:
kwargs["fp1"] = Parameter(raw=b"\x00\x00")
else:
kwargs["fp1"] = Parameter(raw=b"\xC8\x00")
elif isinstance(velocity, int):
kwargs["fp1"] = Position.from_percent(velocity)
command = CommandSend(
pyvlx=self.pyvlx,
wait_for_completion=wait_for_completion,
node_id=self.node_id,
parameter=position,
functional_parameter=kwargs,
)
await command.send()
await self.after_update()
async def open(
self,
velocity: Velocity | int | None = Velocity.DEFAULT,
wait_for_completion: bool = True,
) -> None:
"""Open opening device.
Parameters:
* velocity: Velocity to be used during transition.
* wait_for_completion: If set, function will return
after device has reached target position.
"""
await self.set_position(
position=Position(position_percent=self.open_position_target),
velocity=velocity,
wait_for_completion=wait_for_completion,
)
async def close(
self,
velocity: Velocity | int | None = Velocity.DEFAULT,
wait_for_completion: bool = True,
) -> None:
"""Close opening device.
Parameters:
* velocity: Velocity to be used during transition.
* wait_for_completion: If set, function will return
after device has reached target position.
"""
await self.set_position(
position=Position(position_percent=self.close_position_target),
velocity=velocity,
wait_for_completion=wait_for_completion,
)
async def stop(self, wait_for_completion: bool = True) -> None:
"""Stop opening device.
Parameters:
* wait_for_completion: If set, function will return
after device has reached target position.
"""
await self.set_position(
position=CurrentPosition(), wait_for_completion=wait_for_completion
)
def is_moving(self) -> bool:
"""Return moving state of the cover."""
return self.is_opening or self.is_closing
def movement_percent(self) -> int:
"""Return movement percentage of the cover."""
if (
self.estimated_completion is None
or self.state_received_at is None
or self.estimated_completion < datetime.datetime.now()
):
return 100
movement_duration_s: float = (
self.estimated_completion - self.state_received_at
).total_seconds()
time_passed_s: float = (
datetime.datetime.now() - self.state_received_at
).total_seconds()
percent: int = int(time_passed_s / movement_duration_s * 100)
percent = max(percent, 0)
percent = min(percent, 100)
return percent
def get_position(self) -> Position:
"""Return position of the cover."""
if self.is_moving():
percent = self.movement_percent()
movement_origin = self.position.position_percent
movement_target = self.target.position_percent
current_position = (
movement_origin + (movement_target - movement_origin) / 100 * percent
)
if not self._update_task:
self._update_task = self.pyvlx.loop.create_task(self._update_calls())
return Position(position_percent=int(current_position))
return self.position
def __str__(self) -> str:
"""Return object as readable string."""
return '<{} name="{}" node_id="{}" serial_number="{}" position="{}"/>'.format(
type(self).__name__,
self.name,
self.node_id,
self.serial_number,
self.position,
)
class Window(OpeningDevice):
"""Window object."""
def __init__(
self,
pyvlx: "PyVLX",
node_id: int,
name: str,
serial_number: Optional[str],
position_parameter: Parameter = Parameter(),
rain_sensor: bool = False,
):
"""Initialize Window class.
Parameters:
* pyvlx: PyVLX object
* node_id: internal id for addressing nodes.
Provided by KLF 200 device
* name: node name
* serial_number: serial number of the node.
* position_parameter: initial position of the opening device.
* rain_sensor: set if device is equipped with a
rain sensor.
"""
super().__init__(
pyvlx=pyvlx,
node_id=node_id,
name=name,
serial_number=serial_number,
position_parameter=position_parameter,
)
self.rain_sensor = rain_sensor
def __str__(self) -> str:
"""Return object as readable string."""
return '<{} name="{}" node_id="{}" rain_sensor={} serial_number="{}" position="{}"/>'.format(
type(self).__name__,
self.name,
self.node_id,
self.rain_sensor,
self.serial_number,
self.position,
)
async def get_limitation(self) -> GetLimitation:
"""Return limitation."""
get_limitation = GetLimitation(pyvlx=self.pyvlx, node_id=self.node_id)
await get_limitation.do_api_call()
if not get_limitation.success:
raise PyVLXException("Unable to send command")
return get_limitation
class Blind(OpeningDevice):
"""Blind objects."""
def __init__(
self,
pyvlx: "PyVLX",
node_id: int,
name: str,
serial_number: Optional[str],
position_parameter: Parameter = Parameter(),
):
"""Initialize Blind class.
Parameters:
* pyvlx: PyVLX object
* node_id: internal id for addressing nodes.
Provided by KLF 200 device
* name: node name
"""
super().__init__(
pyvlx=pyvlx,
node_id=node_id,
name=name,
serial_number=serial_number,
position_parameter=position_parameter,
)
self.orientation: Position = Position(position_percent=0)
self.target_orientation: Position = TargetPosition()
self.target_position: Position = TargetPosition()
self.open_orientation_target: int = 50
self.close_orientation_target: int = 100
async def set_position_and_orientation(
self,
position: Position,
wait_for_completion: bool = True,
velocity: Velocity | int | None = None,
orientation: Optional[Position] = None,
) -> None:
"""Set window to desired position.
Parameters:
* position: Position object containing the current position.
* velocity: Velocity to be used during transition.
* target_position: Position object holding the target position
which allows to adjust the position while the blind is in movement
without stopping the blind (if orientation position has been changed.)
* wait_for_completion: If set, function will return
after device has reached target position.
* orientation: If set, the orientation of the device will be set in the same request.
Note, that, if the position is set to 0, the orientation will be set to 0 too.
"""
self.target_position = position
self.position = position
kwargs: Any = {}
if orientation is not None:
kwargs["fp3"] = orientation
elif self.target_position == Position(position_percent=0):
kwargs["fp3"] = Position(position_percent=0)
else:
kwargs["fp3"] = IgnorePosition()
if (
velocity is None or velocity is Velocity.DEFAULT
) and self.use_default_velocity:
velocity = self.default_velocity
if isinstance(velocity, Velocity):
if velocity is not Velocity.DEFAULT:
if velocity is Velocity.SILENT:
# The above code is declaring a variable called `kwargs`.
kwargs["fp1"] = Parameter(raw=b"\x00\x00")
else:
kwargs["fp1"] = Parameter(raw=b"\xC8\x00")
elif isinstance(velocity, int):
kwargs["fp1"] = Position.from_percent(velocity)
command = CommandSend(
pyvlx=self.pyvlx,
node_id=self.node_id,
parameter=position,
wait_for_completion=wait_for_completion,
**kwargs
)
await command.send()
await self.after_update()
async def set_position(
self,
position: Position,
velocity: Velocity | int | None = Velocity.DEFAULT,
wait_for_completion: bool = True,
) -> None:
"""Set window to desired position.
Parameters:
* position: Position object containing the current position.
* velocity: Velocity to be used during transition.
* target_position: Position object holding the target position
which allows to adjust the position while the blind is in movement
without stopping the blind (if orientation position has been changed.)
* wait_for_completion: If set, function will return
after device has reached target position.
"""
await self.set_position_and_orientation(
position=position,
wait_for_completion=wait_for_completion,
velocity=velocity,
)
async def open(
self,
velocity: Velocity | int | None = Velocity.DEFAULT,
wait_for_completion: bool = True,
) -> None:
"""Open window.
Parameters:
* velocity: Velocity to be used during transition.
* wait_for_completion: If set, function will return
after device has reached target position.
"""
await self.set_position(
position=Position(position_percent=self.open_position_target),
velocity=velocity,
wait_for_completion=wait_for_completion,
)
async def close(
self,
velocity: Velocity | int | None = Velocity.DEFAULT,
wait_for_completion: bool = True,
) -> None:
"""Close window.
Parameters:
* velocity: Velocity to be used during transition.
* wait_for_completion: If set, function will return
after device has reached target position.
"""
await self.set_position(
position=Position(position_percent=self.close_position_target),
velocity=velocity,
wait_for_completion=wait_for_completion,
)
async def stop(self, wait_for_completion: bool = True) -> None:
"""Stop Blind position."""
await self.set_position_and_orientation(
position=CurrentPosition(),
wait_for_completion=wait_for_completion,
orientation=self.target_orientation,
)
async def set_orientation(
self, orientation: Position, wait_for_completion: bool = True
) -> None:
"""Set Blind shades to desired orientation.
Parameters:
* orientation: Position object containing the target orientation.
+ target_orientation: Position object holding the target orientation
which allows to adjust the orientation while the blind is in movement
without stopping the blind (if the position has been changed.)
* wait_for_completion: If set, function will return
after device has reached target position.
"""
self.target_orientation = orientation
self.orientation = orientation
fp3 = (
Position(position_percent=0)
if self.target_position == Position(position_percent=0)
else self.target_orientation
)
print("Orientation in device: %s " % (orientation))
command = CommandSend(
pyvlx=self.pyvlx,
wait_for_completion=wait_for_completion,
node_id=self.node_id,
parameter=self.target_position,
fp3=fp3,
)
await command.send()
await self.after_update()
# KLF200 always send UNKNOWN position for functional parameter,
# so orientation is set directly and not via GW_NODE_STATE_POSITION_CHANGED_NTF
async def open_orientation(self, wait_for_completion: bool = True) -> None:
"""Open Blind slats orientation.
Blind slats with ±90° orientation are open at 50%
"""
await self.set_orientation(
orientation=Position(position_percent=self.open_orientation_target),
wait_for_completion=wait_for_completion,
)
async def close_orientation(self, wait_for_completion: bool = True) -> None:
"""Close Blind slats."""
await self.set_orientation(
orientation=Position(position_percent=self.close_orientation_target),
wait_for_completion=wait_for_completion,
)
async def stop_orientation(self, wait_for_completion: bool = True) -> None:
"""Stop Blind slats."""
await self.set_orientation(
orientation=CurrentPosition(), wait_for_completion=wait_for_completion
)
class Awning(OpeningDevice):
"""Awning objects."""
class DualRollerShutter(OpeningDevice):
"""DualRollerShutter object."""
def __init__(
self,
pyvlx: "PyVLX",
node_id: int,
name: str,
serial_number: Optional[str],
position_parameter: Parameter = Parameter(),
):
"""Initialize Blind class.
Parameters:
* pyvlx: PyVLX object
* node_id: internal id for addressing nodes.
Provided by KLF 200 device
* name: node name
"""
super().__init__(
pyvlx=pyvlx,
node_id=node_id,
name=name,
serial_number=serial_number,
position_parameter=position_parameter,
)
self.position_upper_curtain: Position = Position(position_percent=0)
self.position_lower_curtain: Position = Position(position_percent=0)
self.target_position: Any = Position()
self.active_parameter: int = 0
async def set_position(
self,
position: Position,
velocity: Velocity | int | None = Velocity.DEFAULT,
wait_for_completion: bool = True,
curtain: str = "dual",
) -> None:
"""Set DualRollerShutter to desired position.
Parameters:
* position: Position object containing the current position.
* target_position: Position object holding the target position
which allows to adjust the position while the blind is in movement
* wait_for_completion: If set, function will return
after device has reached target position.
"""
kwargs: Any = {}
if curtain == "upper":
self.target_position = DualRollerShutterPosition()
self.active_parameter = 1
kwargs["fp1"] = position
kwargs["fp2"] = TargetPosition()
elif curtain == "lower":
self.target_position = DualRollerShutterPosition()
self.active_parameter = 2
kwargs["fp1"] = TargetPosition()
kwargs["fp2"] = position
else:
self.target_position = position
self.active_parameter = 0
if (
velocity is None or velocity is Velocity.DEFAULT
) and self.use_default_velocity:
velocity = self.default_velocity
if isinstance(velocity, Velocity):
if velocity is not Velocity.DEFAULT:
if velocity is Velocity.SILENT:
kwargs["fp3"] = Parameter(raw=b"\x00\x00")
else:
kwargs["fp3"] = Parameter(raw=b"\xC8\x00")
elif isinstance(velocity, int):
kwargs["fp3"] = Position.from_percent(velocity)
command = CommandSend(
pyvlx=self.pyvlx,
wait_for_completion=wait_for_completion,
node_id=self.node_id,
parameter=self.target_position,
active_parameter=self.active_parameter,
**kwargs
)
await command.send()
if position.position <= Position.MAX:
if curtain == "upper":
self.position_upper_curtain = position
elif curtain == "lower":
self.position_lower_curtain = position
else:
self.position = position
await self.after_update()
async def open(
self,
velocity: Velocity | int | None = Velocity.DEFAULT,
wait_for_completion: bool = True,
curtain: str = "dual",
) -> None:
"""Open DualRollerShutter.
Parameters:
* wait_for_completion: If set, function will return
after device has reached target position.
"""
await self.set_position(
position=Position(position_percent=self.open_position_target),
velocity=velocity,
wait_for_completion=wait_for_completion,
curtain=curtain,
)
async def close(
self,
velocity: Velocity | int | None = Velocity.DEFAULT,
wait_for_completion: bool = True,
curtain: str = "dual",
) -> None:
"""Close DualRollerShutter.
Parameters:
* wait_for_completion: If set, function will return
after device has reached target position.
"""
await self.set_position(
position=Position(position_percent=self.close_position_target),
velocity=velocity,
wait_for_completion=wait_for_completion,
curtain=curtain,
)
async def stop(
self,
wait_for_completion: bool = True,
velocity: Velocity | int | None = Velocity.DEFAULT,
curtain: str = "dual",
) -> None:
"""Stop Blind position."""
await self.set_position(
position=CurrentPosition(),
velocity=velocity,
wait_for_completion=wait_for_completion,
curtain=curtain,
)
class RollerShutter(OpeningDevice):
"""RollerShutter object."""
class GarageDoor(OpeningDevice):
"""GarageDoor object."""
class Gate(OpeningDevice):
"""Gate object."""
class Blade(OpeningDevice):
"""Blade object."""
pyvlx-0.2.26/pyvlx/parameter.py 0000664 0000000 0000000 00000027064 14734113327 0016515 0 ustar 00root root 0000000 0000000 """Module for Position class."""
from typing import Optional
from .exception import PyVLXException
class Parameter:
"""General object for storing parameters."""
UNKNOWN_VALUE = 0xF7FF # F7 FF
CURRENT = 0xD200 # D2 00
MAX = 0xC800 # C8 00
MIN = 0x0000 # 00 00
ON = 0x0000 # 00 00
OFF = 0xC800 # C8 00
TARGET = 0xD100 # D1 00
IGNORE = 0xD400 # D4 00
DUAL_SHUTTER_CURTAINS = 0xD808 # D8 08
def __init__(self, raw: Optional[bytes] = None):
"""Initialize Parameter class."""
self.raw = self.from_int(Position.UNKNOWN_VALUE)
if raw is not None:
self.raw = self.from_raw(raw)
def __bytes__(self) -> bytes:
"""Convert object in byte representation."""
return self.raw
def from_parameter(self, parameter: "Parameter") -> None:
"""Set internal raw state from parameter."""
if not isinstance(parameter, Parameter):
raise PyVLXException("parameter::from_parameter_wrong_object")
self.raw = parameter.raw
@staticmethod
def from_int(value: int) -> bytes:
"""Create raw out of position value."""
if not isinstance(value, int):
raise PyVLXException("value_has_to_be_int")
if not Parameter.is_valid_int(value):
raise PyVLXException("value_out_of_range")
return bytes([value >> 8 & 255, value & 255])
@staticmethod
def to_int(raw: bytes) -> int:
"""Create int position value out of raw."""
return raw[0] * 256 + raw[1]
@staticmethod
def is_valid_int(value: int) -> bool:
"""Test if value can be rendered out of int."""
if 0 <= value <= Parameter.MAX: # This includes ON and OFF
return True
valid_values = {
Parameter.UNKNOWN_VALUE,
Parameter.IGNORE,
Parameter.CURRENT,
Parameter.TARGET,
Parameter.DUAL_SHUTTER_CURTAINS,
}
if value in valid_values:
return True
return False
@staticmethod
def from_raw(raw: bytes) -> bytes:
"""Test if raw packets are valid for initialization of Position."""
if not isinstance(raw, bytes):
raise PyVLXException("Position::raw_must_be_bytes")
if len(raw) != 2:
raise PyVLXException("Position::raw_must_be_two_bytes")
if (
raw != Position.from_int(Position.CURRENT)
and raw != Position.from_int(Position.IGNORE)
and raw != Position.from_int(Position.TARGET)
and raw != Position.from_int(Position.UNKNOWN_VALUE)
and Position.to_int(raw) > Position.MAX
):
return Position.from_int(Position.UNKNOWN_VALUE)
return raw
@staticmethod
def from_percent(percent: int) -> bytes:
"""Create raw value out of percent position."""
if not isinstance(percent, int):
raise PyVLXException("Position::percent_has_to_be_int")
if percent < 0:
raise PyVLXException("Position::percent_has_to_be_positive")
if percent > 100:
raise PyVLXException("Position::percent_out_of_range")
return bytes([percent * 2, 0])
@staticmethod
def to_percent(raw: bytes) -> int:
"""Create percent position value out of raw."""
# The first byte has the vlue from 0 to 200. Ignoring the second one.
# Adding 0.5 allows a slight tolerance for devices (e.g. Velux SML) that
# do not return exactly 51200 as final position when closed.
return int(raw[0] / 2 + 0.5)
def __eq__(self, other: object) -> bool:
"""Equal operator."""
if not isinstance(other, Parameter):
return NotImplemented
return self.raw == other.raw
def __str__(self) -> str:
"""Return string representation of object."""
if self.raw == self.from_int(Position.UNKNOWN_VALUE):
return "UNKNOWN"
if self.raw == self.from_int(Position.CURRENT):
return "CURRENT"
if self.raw == self.from_int(Position.TARGET):
return "TARGET"
if self.raw == self.from_int(Position.IGNORE):
return "IGNORE"
if self.raw == self.from_int(Position.DUAL_SHUTTER_CURTAINS):
return "DUAL"
return "{} %".format(int(self.to_percent(self.raw)))
class SwitchParameter(Parameter):
"""Class for storing On or Off values."""
def __init__(
self, parameter: Optional[Parameter] = None, state: Optional[int] = None
):
"""Initialize Parameter class."""
super().__init__()
if parameter is not None:
self.from_parameter(parameter)
elif state is not None:
self.state = state
@property
def state(self) -> int:
"""Position property."""
return self.to_int(self.raw)
@state.setter
def state(self, state: int) -> None:
"""Setter of internal raw via state."""
self.raw = self.from_int(state)
def set_on(self) -> None:
"""Set parameter to 'on' state."""
self.raw = self.from_int(Parameter.ON)
def set_off(self) -> None:
"""Set parameter to 'off' state."""
self.raw = self.from_int(Parameter.OFF)
def is_on(self) -> bool:
"""Return True if parameter is in 'on' state."""
return self.raw == self.from_int(Parameter.ON)
def is_off(self) -> bool:
"""Return True if parameter is in 'off' state."""
return self.raw == self.from_int(Parameter.OFF)
def __str__(self) -> str:
"""Return string representation of object."""
if self.raw == self.from_int(Parameter.ON):
return "ON"
if self.raw == self.from_int(Parameter.OFF):
return "OFF"
return "UNKNOWN"
class SwitchParameterOn(SwitchParameter):
"""Switch Parameter in switched 'on' state."""
def __init__(self) -> None:
"""Initialize SwitchParameterOn class."""
super().__init__(state=Parameter.ON)
class SwitchParameterOff(SwitchParameter):
"""Switch Parameter in switched 'off' state."""
def __init__(self) -> None:
"""Initialize SwitchParameterOff class."""
super().__init__(state=Parameter.OFF)
class Position(Parameter):
"""Class for storing a position."""
def __init__(
self,
parameter: Optional[Parameter] = None,
position: Optional[int] = None,
position_percent: Optional[int] = None,
):
"""Initialize Position class."""
super().__init__()
if parameter is not None:
self.from_parameter(parameter)
elif position is not None:
self.position = position
elif position_percent is not None:
self.position_percent = position_percent
@property
def known(self) -> bool:
"""Known property, true if position is not in an unknown position."""
return self.raw != self.from_int(Position.UNKNOWN_VALUE)
@property
def open(self) -> bool:
"""Return true if position is set to fully open."""
return self.raw == self.from_int(Position.MIN)
@property
def closed(self) -> bool:
"""Return true if position is set to fully closed."""
# Consider closed even if raw is not exactly 51200 (tolerance for devices like Velux SML)
return self.raw == self.from_int(Position.MAX)
@property
def position(self) -> int:
"""Position property."""
return self.to_int(self.raw)
@position.setter
def position(self, position: int) -> None:
"""Setter of internal raw via position."""
self.raw = self.from_int(position)
@property
def position_percent(self) -> int:
"""Position percent property."""
# unclear why it returns a here
return int(self.to_percent(self.raw))
@position_percent.setter
def position_percent(self, position_percent: int) -> None:
"""Setter of internal raw via percent position."""
self.raw = self.from_percent(percent=position_percent)
class UnknownPosition(Position):
"""Unknown position."""
def __init__(self) -> None:
"""Initialize UnknownPosition class."""
super().__init__(position=Position.UNKNOWN_VALUE)
class CurrentPosition(Position):
"""Current position, used to stop devices."""
def __init__(self) -> None:
"""Initialize CurrentPosition class."""
super().__init__(position=Position.CURRENT)
class TargetPosition(Position):
"""Class for using a target position."""
def __init__(self) -> None:
"""Initialize TargetPosition class."""
super().__init__(position=Position.TARGET)
class IgnorePosition(Position):
"""The Ignore is used where a parameter in the frame is to be ignored."""
def __init__(self) -> None:
"""Initialize CurrentPosition class."""
super().__init__(position=Position.IGNORE)
class Intensity(Parameter):
"""Class for storing an intensity."""
def __init__(
self,
parameter: Optional[Parameter] = None,
intensity: Optional[int] = None,
intensity_percent: Optional[int] = None,
):
"""Initialize Intensity class."""
super().__init__()
if parameter is not None:
self.from_parameter(parameter)
elif intensity is not None:
self.intensity = intensity
elif intensity_percent is not None:
self.intensity_percent = intensity_percent
@property
def known(self) -> bool:
"""Known property, true if intensity is not in an unknown intensity."""
return self.raw != self.from_int(Intensity.UNKNOWN_VALUE)
@property
def on(self) -> bool: # pylint: disable=invalid-name
"""Return true if intensity is set to fully turn on."""
return self.raw == self.from_int(Intensity.MIN)
@property
def off(self) -> bool:
"""Return true if intensity is set to fully turn off."""
return self.raw == bytes([self.MAX >> 8 & 255, self.MAX & 255])
@property
def intensity(self) -> int:
"""Intensity property."""
return self.to_int(self.raw)
@intensity.setter
def intensity(self, intensity: int) -> None:
"""Setter of internal raw via intensity."""
self.raw = self.from_int(intensity)
@property
def intensity_percent(self) -> int:
"""Intensity percent property."""
# unclear why it returns a here
return int(self.to_percent(self.raw))
@intensity_percent.setter
def intensity_percent(self, intensity_percent: int) -> None:
"""Setter of internal raw via percent intensity."""
self.raw = self.from_percent(percent=intensity_percent)
def __str__(self) -> str:
"""Return string representation of object."""
if self.raw == self.from_int(Intensity.UNKNOWN_VALUE):
return "UNKNOWN"
return "{} %".format(self.intensity_percent)
class UnknownIntensity(Intensity):
"""Unknown intensity."""
def __init__(self) -> None:
"""Initialize UnknownIntensity class."""
super().__init__(intensity=Intensity.UNKNOWN_VALUE)
class CurrentIntensity(Intensity):
"""Current intensity, used to stop devices."""
def __init__(self) -> None:
"""Initialize CurrentIntensity class."""
super().__init__(intensity=Intensity.CURRENT)
class DualRollerShutterPosition(Position):
"""Position to be provided when addressing the upper or lower curtain of a dual roller shutter by using FP1 or FP2."""
def __init__(self) -> None:
"""Initialize CurrentPosition class."""
super().__init__(position=Position.DUAL_SHUTTER_CURTAINS)
pyvlx-0.2.26/pyvlx/py.typed 0000664 0000000 0000000 00000000000 14734113327 0015637 0 ustar 00root root 0000000 0000000 pyvlx-0.2.26/pyvlx/pyvlx.py 0000664 0000000 0000000 00000011052 14734113327 0015705 0 ustar 00root root 0000000 0000000 """
Module for PyVLX object.
PyVLX is an asynchronous library for connecting to
a VELUX KLF 200 device for controlling window openers
and roller shutters.
"""
import asyncio
from typing import Optional
from .api import get_limitation
from .api.frames import FrameBase
from .config import Config
from .connection import Connection
from .exception import PyVLXException
from .heartbeat import Heartbeat
from .klf200gateway import Klf200Gateway
from .log import PYVLXLOG
from .node_updater import NodeUpdater
from .nodes import Nodes
from .scenes import Scenes
class PyVLX:
"""Class for PyVLX."""
def __init__(
self,
path: Optional[str] = None,
host: Optional[str] = None,
password: Optional[str] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
heartbeat_interval: int = 30,
heartbeat_load_all_states: bool = True,
):
"""Initialize PyVLX class."""
self.loop = loop or asyncio.get_event_loop()
self.config = Config(self, path, host, password)
self.connection = Connection(loop=self.loop, config=self.config)
self.heartbeat = Heartbeat(
pyvlx=self,
interval=heartbeat_interval,
load_all_states=heartbeat_load_all_states,
)
self.node_updater = NodeUpdater(pyvlx=self)
self.nodes = Nodes(self)
self.connection.register_frame_received_cb(self.node_updater.process_frame)
self.scenes = Scenes(self)
self.version = None
self.protocol_version = None
self.klf200 = Klf200Gateway(pyvlx=self)
self.api_call_semaphore = asyncio.Semaphore(1) # Limit parallel commands
async def connect(self) -> None:
"""Connect to KLF 200."""
PYVLXLOG.debug("Connecting to KLF 200")
await self.connection.connect()
assert self.config.password is not None
await self.klf200.password_enter(password=self.config.password)
await self.klf200.get_version()
await self.klf200.get_protocol_version()
PYVLXLOG.debug(
"Connected to: %s, %s",
str(self.klf200.version),
str(self.klf200.protocol_version),
)
await self.klf200.house_status_monitor_disable(pyvlx=self)
await self.klf200.get_state()
await self.klf200.set_utc()
await self.klf200.get_network_setup()
await self.klf200.house_status_monitor_enable(pyvlx=self)
self.heartbeat.start()
async def reboot_gateway(self) -> None:
"""For Compatibility: Reboot the KLF 200."""
if not self.get_connected():
PYVLXLOG.warning("KLF 200 reboot initiated, but gateway is not connected")
else:
PYVLXLOG.warning("KLF 200 reboot initiated")
await self.klf200.reboot()
def get_connected(self) -> bool:
"""Return whether the gateway is currently connected."""
return self.connection.connected
async def check_connected(self) -> None:
"""Check we're connected, and if not, connect."""
if not self.connection.connected:
await self.connect()
async def send_frame(self, frame: FrameBase) -> None:
"""Send frame to API via connection."""
await self.check_connected()
self.connection.write(frame)
async def disconnect(self) -> None:
"""Disconnect from KLF 200."""
await self.heartbeat.stop()
if self.connection.connected:
try:
# If the connection will be closed while house status monitor is enabled, a reconnection will fail on SSL handshake.
if self.klf200.house_status_monitor_enabled:
await self.klf200.house_status_monitor_disable(pyvlx=self, timeout=1)
# Reboot KLF200 when disconnecting to avoid unresponsive KLF200.
await self.klf200.reboot()
except (OSError, PyVLXException):
pass
self.connection.disconnect()
if self.connection.tasks:
await asyncio.gather(*self.connection.tasks) # Wait for all tasks to finish
async def load_nodes(self, node_id: Optional[int] = None) -> None:
"""Load devices from KLF 200, if no node_id is specified all nodes are loaded."""
await self.nodes.load(node_id)
async def load_scenes(self) -> None:
"""Load scenes from KLF 200."""
await self.scenes.load()
async def get_limitation(self, node_id: int) -> None:
"""Return limitation."""
limit = get_limitation.GetLimitation(self, node_id)
await limit.do_api_call()
pyvlx-0.2.26/pyvlx/scene.py 0000664 0000000 0000000 00000002715 14734113327 0015626 0 ustar 00root root 0000000 0000000 """Module for scene."""
from typing import TYPE_CHECKING, Any
from .api import ActivateScene
from .exception import PyVLXException
if TYPE_CHECKING:
from pyvlx import PyVLX
class Scene:
"""Object for scene."""
def __init__(self, pyvlx: "PyVLX", scene_id: int, name: str):
"""Initialize Scene object.
Parameters:
* pyvlx: PyVLX object
* scene_id: internal id for addressing scenes.
Provided by KLF 200 device
* name: scene name
"""
self.pyvlx = pyvlx
self.scene_id = scene_id
self.name = name
async def run(self, wait_for_completion: bool = True) -> None:
"""Run scene.
Parameters:
* wait_for_completion: If set, function will return
after device has reached target position.
"""
activate_scene = ActivateScene(
pyvlx=self.pyvlx,
wait_for_completion=wait_for_completion,
scene_id=self.scene_id,
)
await activate_scene.do_api_call()
if not activate_scene.success:
raise PyVLXException("Unable to activate scene")
def __str__(self) -> str:
"""Return object as readable string."""
return '<{} name="{}" id="{}"/>'.format(
type(self).__name__, self.name, self.scene_id
)
def __eq__(self, other: Any) -> bool:
"""Equal operator."""
return self.__dict__ == other.__dict__
pyvlx-0.2.26/pyvlx/scenes.py 0000664 0000000 0000000 00000003615 14734113327 0016011 0 ustar 00root root 0000000 0000000 """Module for storing and accessing scene list."""
from typing import TYPE_CHECKING, Iterator, List, Union
from .api import GetSceneList
from .exception import PyVLXException
from .scene import Scene
if TYPE_CHECKING:
from pyvlx import PyVLX
class Scenes:
"""Class for storing and accessing ."""
def __init__(self, pyvlx: "PyVLX"):
"""Initialize Scenes class."""
self.pyvlx = pyvlx
self.__scenes: List[Scene] = []
def __iter__(self) -> Iterator[Scene]:
"""Iterate."""
yield from self.__scenes
def __getitem__(self, key: Union[str, int]) -> Scene:
"""Return scene by name or by index."""
if isinstance(key, int):
for scene in self.__scenes:
if scene.scene_id == key:
return scene
for scene in self.__scenes:
if scene.name == key:
return scene
raise KeyError
def __len__(self) -> int:
"""Return number of scenes."""
return len(self.__scenes)
def add(self, scene: Scene) -> None:
"""Add scene, replace existing scene if scene with scene_id is present."""
if not isinstance(scene, Scene):
raise TypeError()
for i, j in enumerate(self.__scenes):
if j.scene_id == scene.scene_id:
self.__scenes[i] = scene
return
self.__scenes.append(scene)
def clear(self) -> None:
"""Clear internal scenes array."""
self.__scenes = []
async def load(self) -> None:
"""Load scenes from KLF 200."""
get_scene_list = GetSceneList(pyvlx=self.pyvlx)
await get_scene_list.do_api_call()
if not get_scene_list.success:
raise PyVLXException("Unable to retrieve scene information")
for scene in get_scene_list.scenes:
self.add(Scene(pyvlx=self.pyvlx, scene_id=scene[0], name=scene[1]))
pyvlx-0.2.26/pyvlx/slip.py 0000664 0000000 0000000 00000002457 14734113327 0015503 0 ustar 00root root 0000000 0000000 """Module for Serial Line Internet Protocol (SLIP)."""
from typing import Optional, Tuple
SLIP_END = 0xC0
SLIP_ESC = 0xDB
SLIP_ESC_END = 0xDC
SLIP_ESC_ESC = 0xDD
def is_slip(raw: bytes) -> bool:
"""Check if raw is a SLIP packet."""
if len(raw) < 2:
return False
return raw[0] == SLIP_END and SLIP_END in raw[1:]
def decode(raw: bytes) -> bytes:
"""Decode SLIP message."""
return raw.replace(bytes([SLIP_ESC, SLIP_ESC_END]), bytes([SLIP_END])).replace(
bytes([SLIP_ESC, SLIP_ESC_ESC]), bytes([SLIP_ESC])
)
def encode(raw: bytes) -> bytes:
"""Encode SLIP message."""
return raw.replace(bytes([SLIP_ESC]), bytes([SLIP_ESC, SLIP_ESC_ESC])).replace(
bytes([SLIP_END]), bytes([SLIP_ESC, SLIP_ESC_END])
)
def get_next_slip(raw: bytes) -> Tuple[Optional[bytes], bytes]:
"""
Get the next slip packet from raw data.
Returns the extracted packet plus the raw data with the remaining data stream.
"""
if not is_slip(raw):
return None, raw
length = raw[1:].index(SLIP_END)
slip_packet = decode(raw[1 : length + 1])
new_raw = raw[length + 2 :]
return slip_packet, new_raw
def slip_pack(raw: bytes) -> bytes:
"""Pack raw message to complete slip message."""
return bytes([SLIP_END]) + encode(raw) + bytes([SLIP_END])
pyvlx-0.2.26/pyvlx/string_helper.py 0000664 0000000 0000000 00000001143 14734113327 0017370 0 ustar 00root root 0000000 0000000 """Module for string encoding, decoding."""
from .exception import PyVLXException
def string_to_bytes(string: str, size: int) -> bytes:
"""Convert string to bytes add padding."""
if len(string) > size:
raise PyVLXException("string_to_bytes::string_to_large")
encoded = bytes(string, encoding="utf-8")
return encoded + bytes(size - len(encoded))
def bytes_to_string(raw: bytes) -> str:
"""Convert bytes to string."""
ret = bytes()
for byte in raw:
if byte == 0x00:
return ret.decode("utf-8")
ret += bytes([byte])
return ret.decode("utf-8")
pyvlx-0.2.26/requirements/ 0000775 0000000 0000000 00000000000 14734113327 0015513 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/requirements/production.txt 0000664 0000000 0000000 00000000040 14734113327 0020434 0 ustar 00root root 0000000 0000000 pyyaml==6.0.2
zeroconf==0.136.2
pyvlx-0.2.26/requirements/testing.txt 0000664 0000000 0000000 00000000326 14734113327 0017732 0 ustar 00root root 0000000 0000000 -r production.txt
isort==5.13.2
coveralls==4.0.1
flake8==7.1.1
flake8-isort==6.1.1
pydocstyle==6.3.0
pylint==3.3.3
pytest==8.3.4
pytest-cov==6.0.0
pytest-timeout==2.3.1
setuptools==75.6.0
twine==6.0.1
mypy==1.14.0
pyvlx-0.2.26/setup.cfg 0000664 0000000 0000000 00000000204 14734113327 0014605 0 ustar 00root root 0000000 0000000 [metadata]
description-file = README.md
[flake8]
ignore = E203, W503, E226
exclude = .git,bin,lib,deps,build
max-line-length = 160
pyvlx-0.2.26/setup.py 0000664 0000000 0000000 00000003166 14734113327 0014510 0 ustar 00root root 0000000 0000000 """Module for setting up PyVLX pypi object."""
import os
from os import path
from setuptools import find_packages, setup
REQUIRES = ["PyYAML", "zeroconf"]
PKG_ROOT = os.path.dirname(__file__)
VERSION = "0.2.26"
def get_long_description() -> str:
"""Read long description from README.md."""
this_directory = path.abspath(path.dirname(__file__))
with open(path.join(this_directory, "README.md"), encoding="utf-8") as readme:
long_description = readme.read()
return long_description
setup(
name="pyvlx",
version=VERSION,
download_url="https://github.com/Julius2342/pyvlx/archive/" + VERSION + ".zip",
url="https://github.com/Julius2342/pyvlx",
description="PyVLX is a wrapper for the Velux KLF 200 API. PyVLX enables you to run scenes and or open and close velux windows.",
long_description=get_long_description(),
long_description_content_type="text/markdown",
author="Julius Mittenzwei",
author_email="julius@mittenzwei.com",
license="LGPL",
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: End Users/Desktop",
"Intended Audience :: Developers",
"Topic :: System :: Hardware :: Hardware Drivers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
packages=find_packages(exclude=['test*']),
package_data={
"pyvlx": ["py.typed"],
},
python_requires='>=3.11',
install_requires=REQUIRES,
keywords="velux KLF 200 home automation",
zip_safe=False,
)
pyvlx-0.2.26/spec/ 0000775 0000000 0000000 00000000000 14734113327 0013722 5 ustar 00root root 0000000 0000000 pyvlx-0.2.26/spec/technical specification for klf 200 api-ver3-18.pdf 0000664 0000000 0000000 00015307274 14734113327 0024616 0 ustar 00root root 0000000 0000000 %PDF-1.7
%
1009 0 obj
<>
endobj
1030 0 obj
<>/Filter/FlateDecode/ID[<6562824D8359C243AC1ABB0B35EA1397><6562824D8359C243AC1ABB0B35EA1397>]/Index[1009 48]/Info 1008 0 R/Length 106/Prev 3507802/Root 1010 0 R/Size 1057/Type/XRef/W[1 3 1]>>stream
hbbd```b` "\@$-ɾD2ǂH`60
LEJp0&X_"7ę I+*@O3+#svADg/ h
endstream
endobj
startxref
0
%%EOF
1056 0 obj
<>stream
hVOSg~ᴜA+).^FӀe`k,2A-t@[@EDA鼬QK&̉ƄD)A~l]ZzX6}$ R X;H B&$>N
~lZ|ܪ ]WwMK7ʎ %out]m"ci^NكJ*;l%ߧo.v5pIx]D8^7^thMKmDxWiSN]iHYH//d뷜͝fT2OVv,cܜKwT\lwOfS>1Xs1R՞::qQSHxDVVjnGjGN5&"m5Oo=nr?L%R9xsKByd]O1d2O)?(xulUY%R(bp#PO=BLFBls(I "%OJ|l1EE01$IIR
*CMQ&v
7S(H$=Ӥ
v+$Xs dd!}"*l#Y2":c>
26)$-I@sDgc Z*2ڟyhEĊSm)Gئ*+KwbeH&f @C\D
Ȭ8_B4W+r5
ʘ+Y q2֬dv0R:zN~g#^
xYKWKpTGkTs *y9p#ӎJC e
endstream
endobj
1010 0 obj
<>
endobj
1011 0 obj
<>
endobj
1012 0 obj
<>stream
hė]O7\&)BGih#Cr)
vl{y= pQq̼=~|ƻ7xF^00ȃ~:#cˌP%KŒGX.Fl#!$<QB+*UE*Q5Fsf^vC"HrsA\tQeTԋzˋA)(ɪ"_?1/O'Stoɴޮϟ<|0@?2qv@)JT+tbAB%NLרW&U )@)D)ʔB)F))(E{ՊK*U<KjE㩂55Ҁ2օ)VԨTaFhjxYR^j\S[+/T㛨T-((