pypck 0.8.12__py3-none-any.whl → 0.9.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pypck/__init__.py +0 -2
- pypck/connection.py +12 -31
- pypck/lcn_defs.py +22 -0
- pypck/module.py +416 -230
- pypck-0.9.2.dist-info/METADATA +65 -0
- pypck-0.9.2.dist-info/RECORD +13 -0
- pypck/request_handlers.py +0 -694
- pypck/timeout_retry.py +0 -110
- pypck-0.8.12.dist-info/METADATA +0 -145
- pypck-0.8.12.dist-info/RECORD +0 -15
- {pypck-0.8.12.dist-info → pypck-0.9.2.dist-info}/WHEEL +0 -0
- {pypck-0.8.12.dist-info → pypck-0.9.2.dist-info}/licenses/LICENSE +0 -0
- {pypck-0.8.12.dist-info → pypck-0.9.2.dist-info}/top_level.txt +0 -0
pypck/timeout_retry.py
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
"""Base classes for handling reoccurent tasks."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
import logging
|
|
7
|
-
from collections.abc import Awaitable, Callable
|
|
8
|
-
from typing import Any
|
|
9
|
-
|
|
10
|
-
from pypck.helpers import TaskRegistry, cancel_task
|
|
11
|
-
|
|
12
|
-
_LOGGER = logging.getLogger(__name__)
|
|
13
|
-
|
|
14
|
-
# The default timeout to use for requests. Worst case: Requesting threshold
|
|
15
|
-
# 4-4 takes at least 1.8s
|
|
16
|
-
DEFAULT_TIMEOUT = 3.5
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class TimeoutRetryHandler:
|
|
20
|
-
"""Manage timeout and retry logic for an LCN request."""
|
|
21
|
-
|
|
22
|
-
def __init__(
|
|
23
|
-
self,
|
|
24
|
-
task_registry: TaskRegistry,
|
|
25
|
-
num_tries: int = 3,
|
|
26
|
-
timeout: float = DEFAULT_TIMEOUT,
|
|
27
|
-
):
|
|
28
|
-
"""Construct TimeoutRetryHandler."""
|
|
29
|
-
self.task_registry = task_registry
|
|
30
|
-
self.num_tries = num_tries
|
|
31
|
-
self.timeout = timeout
|
|
32
|
-
self._timeout_callback: (
|
|
33
|
-
Callable[..., None] | Callable[..., Awaitable[None]] | None
|
|
34
|
-
) = None
|
|
35
|
-
self._timeout_args: tuple[Any, ...] = ()
|
|
36
|
-
self._timeout_kwargs: dict[str, Any] = {}
|
|
37
|
-
self.timeout_loop_task: asyncio.Task[None] | None = None
|
|
38
|
-
|
|
39
|
-
def set_timeout(self, timeout: int) -> None:
|
|
40
|
-
"""Set the timeout in seconds."""
|
|
41
|
-
self.timeout = timeout
|
|
42
|
-
|
|
43
|
-
def set_timeout_callback(
|
|
44
|
-
self, timeout_callback: Any, *timeout_args: Any, **timeout_kwargs: Any
|
|
45
|
-
) -> None:
|
|
46
|
-
"""Timeout_callback function is called, if timeout expires.
|
|
47
|
-
|
|
48
|
-
Function has to take one argument:
|
|
49
|
-
Returns failed state (True if failed)
|
|
50
|
-
"""
|
|
51
|
-
self._timeout_callback = timeout_callback
|
|
52
|
-
self._timeout_args = timeout_args
|
|
53
|
-
self._timeout_kwargs = timeout_kwargs
|
|
54
|
-
|
|
55
|
-
def activate(self) -> None:
|
|
56
|
-
"""Schedule the next activation."""
|
|
57
|
-
self.task_registry.create_task(self.async_activate())
|
|
58
|
-
|
|
59
|
-
async def async_activate(self) -> None:
|
|
60
|
-
"""Clean start of next timeout_loop."""
|
|
61
|
-
if self.is_active():
|
|
62
|
-
return
|
|
63
|
-
self.timeout_loop_task = self.task_registry.create_task(self.timeout_loop())
|
|
64
|
-
|
|
65
|
-
async def done(self) -> None:
|
|
66
|
-
"""Signal the completion of the TimeoutRetryHandler."""
|
|
67
|
-
if self.timeout_loop_task is not None:
|
|
68
|
-
await self.timeout_loop_task
|
|
69
|
-
|
|
70
|
-
async def cancel(self) -> None:
|
|
71
|
-
"""Must be called when a response (requested or not) is received."""
|
|
72
|
-
if self.timeout_loop_task is not None:
|
|
73
|
-
await cancel_task(self.timeout_loop_task)
|
|
74
|
-
|
|
75
|
-
def is_active(self) -> bool:
|
|
76
|
-
"""Check whether the request logic is active."""
|
|
77
|
-
if self.timeout_loop_task is None:
|
|
78
|
-
return False
|
|
79
|
-
return not self.timeout_loop_task.done()
|
|
80
|
-
|
|
81
|
-
async def on_timeout(self, failed: bool = False) -> None:
|
|
82
|
-
"""Is called on timeout of TimeoutRetryHandler."""
|
|
83
|
-
if self._timeout_callback is not None:
|
|
84
|
-
if asyncio.iscoroutinefunction(self._timeout_callback):
|
|
85
|
-
# mypy fails to notice that `asyncio.iscoroutinefunction`
|
|
86
|
-
# separates await-callable from ordinary callables.
|
|
87
|
-
await self._timeout_callback(
|
|
88
|
-
failed, *self._timeout_args, **self._timeout_kwargs
|
|
89
|
-
)
|
|
90
|
-
else:
|
|
91
|
-
self._timeout_callback(
|
|
92
|
-
failed, *self._timeout_args, **self._timeout_kwargs
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
async def timeout_loop(self) -> None:
|
|
96
|
-
"""Timeout / retry loop."""
|
|
97
|
-
if self.timeout_loop_task is None:
|
|
98
|
-
return
|
|
99
|
-
tries_left = self.num_tries
|
|
100
|
-
while (tries_left > 0) or (tries_left == -1):
|
|
101
|
-
if not self.timeout_loop_task.done():
|
|
102
|
-
await self.on_timeout()
|
|
103
|
-
await asyncio.sleep(self.timeout)
|
|
104
|
-
if self.num_tries != -1:
|
|
105
|
-
tries_left -= 1
|
|
106
|
-
else:
|
|
107
|
-
break
|
|
108
|
-
|
|
109
|
-
if not self.timeout_loop_task.done():
|
|
110
|
-
await self.on_timeout(failed=True)
|
pypck-0.8.12.dist-info/METADATA
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: pypck
|
|
3
|
-
Version: 0.8.12
|
|
4
|
-
Summary: LCN-PCK library
|
|
5
|
-
Home-page: https://github.com/alengwenus/pypck
|
|
6
|
-
Author-email: Andre Lengwenus <alengwenus@gmail.com>
|
|
7
|
-
License: MIT
|
|
8
|
-
Project-URL: Source Code, https://github.com/alengwenus/pypck
|
|
9
|
-
Project-URL: Bug Reports, https://github.com/alengwenus/pypck/issues
|
|
10
|
-
Keywords: lcn,pck
|
|
11
|
-
Platform: any
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
16
|
-
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Topic :: Home Automation
|
|
18
|
-
Requires-Python: >=3.11
|
|
19
|
-
Description-Content-Type: text/markdown
|
|
20
|
-
License-File: LICENSE
|
|
21
|
-
Dynamic: license-file
|
|
22
|
-
|
|
23
|
-
# pypck - Asynchronous LCN-PCK library written in Python
|
|
24
|
-
|
|
25
|
-

|
|
26
|
-

|
|
27
|
-

|
|
28
|
-
[](https://pypi.org/project/pypck/)
|
|
29
|
-
[](https://github.com/pre-commit/pre-commit)
|
|
30
|
-
|
|
31
|
-
<a href="https://www.buymeacoffee.com/alengwenus" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/white_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>
|
|
32
|
-
|
|
33
|
-
## Overview
|
|
34
|
-
|
|
35
|
-
**pypck** is an open source library written in Python which allows the connection to the [LCN (local control network) system](https://www.lcn.eu). It uses the vendor protocol LCN-PCK.
|
|
36
|
-
To get started an unused license of the coupling software LCN-PCHK and a hardware coupler is necessary.
|
|
37
|
-
|
|
38
|
-
**pypck** is used by the LCN integration of the [Home Assistant](https://home-assistant.io/) project.
|
|
39
|
-
|
|
40
|
-
## Example
|
|
41
|
-
|
|
42
|
-
```python
|
|
43
|
-
"""Example for switching an output port of module 10 on and off."""
|
|
44
|
-
import asyncio
|
|
45
|
-
|
|
46
|
-
from pypck.connection import PchkConnectionManager
|
|
47
|
-
from pypck.lcn_addr import LcnAddr
|
|
48
|
-
|
|
49
|
-
async def main():
|
|
50
|
-
"""Connect to PCK host, get module object and switch output port on and off."""
|
|
51
|
-
async with PchkConnectionManager(
|
|
52
|
-
"192.168.2.41",
|
|
53
|
-
4114,
|
|
54
|
-
username="lcn",
|
|
55
|
-
password="lcn",
|
|
56
|
-
settings={"SK_NUM_TRIES": 0},
|
|
57
|
-
) as pck_client:
|
|
58
|
-
module = pck_client.get_address_conn(LcnAddr(0, 10, False))
|
|
59
|
-
|
|
60
|
-
await module.dim_output(0, 100, 0)
|
|
61
|
-
await asyncio.sleep(1)
|
|
62
|
-
await module.dim_output(0, 0, 0)
|
|
63
|
-
|
|
64
|
-
asyncio.run(main())
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## pypck REPL in ipython
|
|
68
|
-
|
|
69
|
-
**pypck** relies heavily on asyncio for talking to the LCN-PCHK software. This
|
|
70
|
-
makes it unusable with the standard python interactive interpreter.
|
|
71
|
-
Fortunately, ipython provides some support for asyncio in its interactive
|
|
72
|
-
interpreter, see
|
|
73
|
-
[ipython autoawait](https://ipython.readthedocs.io/en/stable/interactive/autoawait.html#).
|
|
74
|
-
|
|
75
|
-
### Requirements
|
|
76
|
-
|
|
77
|
-
- **ipython** at least version 7.0 (autoawait support)
|
|
78
|
-
- **pypck**
|
|
79
|
-
|
|
80
|
-
### Example session
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
Python 3.8.3 (default, Jun 9 2020, 17:39:39)
|
|
84
|
-
Type 'copyright', 'credits' or 'license' for more information
|
|
85
|
-
IPython 7.19.0 -- An enhanced Interactive Python. Type '?' for help.
|
|
86
|
-
|
|
87
|
-
In [1]: from pypck.connection import PchkConnectionManager
|
|
88
|
-
...: from pypck.lcn_addr import LcnAddr
|
|
89
|
-
...: import asyncio
|
|
90
|
-
|
|
91
|
-
In [2]: connection = PchkConnectionManager(host='localhost', port=4114, username='lcn', password='lcn')
|
|
92
|
-
|
|
93
|
-
In [3]: await connection.async_connect()
|
|
94
|
-
|
|
95
|
-
In [4]: module = connection.get_address_conn(LcnAddr(seg_id=0, addr_id=10, is_group=False), request_serials=False)
|
|
96
|
-
|
|
97
|
-
In [5]: await module.request_serials()
|
|
98
|
-
Out[5]:
|
|
99
|
-
{'hardware_serial': 127977263668,
|
|
100
|
-
'manu': 1,
|
|
101
|
-
'software_serial': 1771023,
|
|
102
|
-
'hardware_type': <HardwareType.UPU: 26>}
|
|
103
|
-
|
|
104
|
-
In [6]: await module.dim_output(0, 100, 0)
|
|
105
|
-
...: await asyncio.sleep(1)
|
|
106
|
-
...: await module.dim_output(0, 0, 0)
|
|
107
|
-
Out[6]: True
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Caveats
|
|
111
|
-
|
|
112
|
-
ipython starts and stops the asyncio event loop for each toplevel command
|
|
113
|
-
sequence. Also it only starts the loop if the toplevel commands includes async
|
|
114
|
-
code (like await or a call to an async function). This can lead to unexpected
|
|
115
|
-
behavior. For example, background tasks run only while ipython is executing
|
|
116
|
-
toplevel commands that started the event loop. Functions that use the event
|
|
117
|
-
loop only internally may fail, e.g. the following would fail:
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
In [4]: module = connection.get_address_conn(LcnAddr(seg_id=0, addr_id=10, is_group=False), request_serials=True)
|
|
121
|
-
---------------------------------------------------------------------------
|
|
122
|
-
RuntimeError Traceback (most recent call last)
|
|
123
|
-
<ipython-input-7-cd663974bde2> in <module>
|
|
124
|
-
----> 1 module = connection.get_address_conn(modaddr)
|
|
125
|
-
|
|
126
|
-
/pypck/connection.py in get_address_conn(self, addr, request_serials)
|
|
127
|
-
457 address_conn = ModuleConnection(self, addr)
|
|
128
|
-
458 if request_serials:
|
|
129
|
-
--> 459 self.request_serials_task = asyncio.create_task(
|
|
130
|
-
460 address_conn.request_serials()
|
|
131
|
-
461 )
|
|
132
|
-
|
|
133
|
-
/usr/local/lib/python3.8/asyncio/tasks.py in create_task(coro, name)
|
|
134
|
-
379 Return a Task object.
|
|
135
|
-
380 """
|
|
136
|
-
--> 381 loop = events.get_running_loop()
|
|
137
|
-
382 task = loop.create_task(coro)
|
|
138
|
-
383 _set_task_name(task, name)
|
|
139
|
-
|
|
140
|
-
RuntimeError: no running event loop
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
See
|
|
144
|
-
[ipython autoawait internals](https://ipython.readthedocs.io/en/stable/interactive/autoawait.html#internals)
|
|
145
|
-
for details.
|
pypck-0.8.12.dist-info/RECORD
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
pypck/__init__.py,sha256=vjI-MSv-ght8Y_tcKPXYMjL-FGlHX6Z2ofrUBrz8g4U,317
|
|
2
|
-
pypck/connection.py,sha256=M9AGdq46UjrOyz0V_tZX1IHqGg5Mb3SKs2tzy4wQQhg,24379
|
|
3
|
-
pypck/helpers.py,sha256=_5doqIsSRpqdQNPIUsjFh813xKGuMuEFY6sNGobJGIk,1280
|
|
4
|
-
pypck/inputs.py,sha256=cjhvYKPr5QHw7Rjfkkg8LPbP8ohq_-z_Tvk51Rkx0aY,45828
|
|
5
|
-
pypck/lcn_addr.py,sha256=N2Od8KuANOglqKjf596hJVH1SRcG7MhESKA5YYlDnbw,1946
|
|
6
|
-
pypck/lcn_defs.py,sha256=e2KqD6r-JUwp20BcTymbpjsX16zjOihGKDqreWDsrwo,41951
|
|
7
|
-
pypck/module.py,sha256=wu06MQqLJPGIimPjDd8uWEY9crj1d-2gl__OpVJsfA8,38526
|
|
8
|
-
pypck/pck_commands.py,sha256=eJxmh2e8EbKGpek97L2961Kr_nVfT8rKgJCN3YgjIQM,50458
|
|
9
|
-
pypck/request_handlers.py,sha256=Ft9teTysw-tc9gMdXgh1en-e3MxXmIsiqv5ZQDI_b9E,25234
|
|
10
|
-
pypck/timeout_retry.py,sha256=FzeoyhmA4Tj13wR4kGXUwH8HyXnZ1RQOAYY9sSbz6rw,3859
|
|
11
|
-
pypck-0.8.12.dist-info/licenses/LICENSE,sha256=iYB6zyMJvShfAzQE7nhYFgLzzZuBmhasLw5fYP9KRz4,1023
|
|
12
|
-
pypck-0.8.12.dist-info/METADATA,sha256=LmeQoq11Ron4bsfu8N4-KbDHrqUfPgEHZDwXnlMx8Zs,5604
|
|
13
|
-
pypck-0.8.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
-
pypck-0.8.12.dist-info/top_level.txt,sha256=59ried49iFueDa5mQ_5BGVZcESjjzi4MZZKLcganvQA,6
|
|
15
|
-
pypck-0.8.12.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|