pypck 0.8.10__py3-none-any.whl → 0.9.1__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/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)
@@ -1,145 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: pypck
3
- Version: 0.8.10
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
- ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/alengwenus/pypck?color=success)
26
- ![GitHub Workflow Status (dev branch)](https://github.com/alengwenus/pypck/actions/workflows/ci.yaml/badge.svg?branch=dev)
27
- ![Codecov branch](https://img.shields.io/codecov/c/github/alengwenus/pypck/dev)
28
- [![PyPI - Downloads](https://img.shields.io/pypi/dm/pypck)](https://pypi.org/project/pypck/)
29
- [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](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.
@@ -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=bRXIVQjaBr6Gfs_xYMMDeTZAle86M-nm6YfzyTjs7kA,38476
8
- pypck/pck_commands.py,sha256=bkb3q49s4PVY6UNR0B6S31oU7aSaEbpPl3rj0eGxTQU,50380
9
- pypck/request_handlers.py,sha256=Ft9teTysw-tc9gMdXgh1en-e3MxXmIsiqv5ZQDI_b9E,25234
10
- pypck/timeout_retry.py,sha256=FzeoyhmA4Tj13wR4kGXUwH8HyXnZ1RQOAYY9sSbz6rw,3859
11
- pypck-0.8.10.dist-info/licenses/LICENSE,sha256=iYB6zyMJvShfAzQE7nhYFgLzzZuBmhasLw5fYP9KRz4,1023
12
- pypck-0.8.10.dist-info/METADATA,sha256=1Vkcj-dIBNtp_BJiHDJ_WB6hMwkRQTrHzEHm8ecnWTI,5604
13
- pypck-0.8.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- pypck-0.8.10.dist-info/top_level.txt,sha256=59ried49iFueDa5mQ_5BGVZcESjjzi4MZZKLcganvQA,6
15
- pypck-0.8.10.dist-info/RECORD,,
File without changes