pypck 0.8.12__tar.gz → 0.9.2__tar.gz
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-0.9.2/PKG-INFO +65 -0
- pypck-0.9.2/README.md +43 -0
- pypck-0.9.2/VERSION +1 -0
- {pypck-0.8.12 → pypck-0.9.2}/pypck/__init__.py +0 -2
- {pypck-0.8.12 → pypck-0.9.2}/pypck/connection.py +12 -31
- {pypck-0.8.12 → pypck-0.9.2}/pypck/lcn_defs.py +22 -0
- {pypck-0.8.12 → pypck-0.9.2}/pypck/module.py +416 -230
- pypck-0.9.2/pypck.egg-info/PKG-INFO +65 -0
- {pypck-0.8.12 → pypck-0.9.2}/pypck.egg-info/SOURCES.txt +2 -2
- {pypck-0.8.12 → pypck-0.9.2}/tests/test_connection.py +14 -0
- pypck-0.9.2/tests/test_module.py +288 -0
- pypck-0.9.2/tests/test_status_requester.py +102 -0
- pypck-0.8.12/PKG-INFO +0 -145
- pypck-0.8.12/README.md +0 -123
- pypck-0.8.12/VERSION +0 -1
- pypck-0.8.12/pypck/request_handlers.py +0 -694
- pypck-0.8.12/pypck/timeout_retry.py +0 -110
- pypck-0.8.12/pypck.egg-info/PKG-INFO +0 -145
- {pypck-0.8.12 → pypck-0.9.2}/LICENSE +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/pypck/helpers.py +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/pypck/inputs.py +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/pypck/lcn_addr.py +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/pypck/pck_commands.py +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/pypck.egg-info/dependency_links.txt +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/pypck.egg-info/not-zip-safe +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/pypck.egg-info/top_level.txt +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/pyproject.toml +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/setup.cfg +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/tests/test_commands.py +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/tests/test_dyn_text.py +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/tests/test_input.py +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/tests/test_messages.py +0 -0
- {pypck-0.8.12 → pypck-0.9.2}/tests/test_vars.py +0 -0
pypck-0.9.2/PKG-INFO
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pypck
|
|
3
|
+
Version: 0.9.2
|
|
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
|
+
```
|
pypck-0.9.2/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# pypck - Asynchronous LCN-PCK library written in Python
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
[](https://pypi.org/project/pypck/)
|
|
7
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
8
|
+
|
|
9
|
+
<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>
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
**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.
|
|
14
|
+
To get started an unused license of the coupling software LCN-PCHK and a hardware coupler is necessary.
|
|
15
|
+
|
|
16
|
+
**pypck** is used by the LCN integration of the [Home Assistant](https://home-assistant.io/) project.
|
|
17
|
+
|
|
18
|
+
## Example
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
"""Example for switching an output port of module 10 on and off."""
|
|
22
|
+
import asyncio
|
|
23
|
+
|
|
24
|
+
from pypck.connection import PchkConnectionManager
|
|
25
|
+
from pypck.lcn_addr import LcnAddr
|
|
26
|
+
|
|
27
|
+
async def main():
|
|
28
|
+
"""Connect to PCK host, get module object and switch output port on and off."""
|
|
29
|
+
async with PchkConnectionManager(
|
|
30
|
+
"192.168.2.41",
|
|
31
|
+
4114,
|
|
32
|
+
username="lcn",
|
|
33
|
+
password="lcn",
|
|
34
|
+
settings={"SK_NUM_TRIES": 0},
|
|
35
|
+
) as pck_client:
|
|
36
|
+
module = pck_client.get_address_conn(LcnAddr(0, 10, False))
|
|
37
|
+
|
|
38
|
+
await module.dim_output(0, 100, 0)
|
|
39
|
+
await asyncio.sleep(1)
|
|
40
|
+
await module.dim_output(0, 0, 0)
|
|
41
|
+
|
|
42
|
+
asyncio.run(main())
|
|
43
|
+
```
|
pypck-0.9.2/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.9.2
|
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import logging
|
|
7
|
-
import time
|
|
8
7
|
from collections.abc import Callable, Iterable
|
|
9
8
|
from types import TracebackType
|
|
10
9
|
from typing import Any
|
|
@@ -98,7 +97,7 @@ class PchkConnectionManager:
|
|
|
98
97
|
self.reader: asyncio.StreamReader | None = None
|
|
99
98
|
self.writer: asyncio.StreamWriter | None = None
|
|
100
99
|
self.buffer: asyncio.Queue[bytes] = asyncio.Queue()
|
|
101
|
-
self.last_bus_activity =
|
|
100
|
+
self.last_bus_activity = asyncio.get_running_loop().time()
|
|
102
101
|
|
|
103
102
|
self.username = username
|
|
104
103
|
self.password = password
|
|
@@ -145,6 +144,7 @@ class PchkConnectionManager:
|
|
|
145
144
|
"""Processes incoming data."""
|
|
146
145
|
assert self.reader is not None
|
|
147
146
|
assert self.writer is not None
|
|
147
|
+
loop = asyncio.get_running_loop()
|
|
148
148
|
_LOGGER.debug("Read data loop started")
|
|
149
149
|
try:
|
|
150
150
|
while not self.writer.is_closing():
|
|
@@ -152,7 +152,7 @@ class PchkConnectionManager:
|
|
|
152
152
|
data = await self.reader.readuntil(
|
|
153
153
|
PckGenerator.TERMINATION.encode()
|
|
154
154
|
)
|
|
155
|
-
self.last_bus_activity =
|
|
155
|
+
self.last_bus_activity = loop.time()
|
|
156
156
|
except (
|
|
157
157
|
asyncio.IncompleteReadError,
|
|
158
158
|
TimeoutError,
|
|
@@ -187,11 +187,12 @@ class PchkConnectionManager:
|
|
|
187
187
|
async def write_data_loop(self) -> None:
|
|
188
188
|
"""Processes queue and writes data."""
|
|
189
189
|
assert self.writer is not None
|
|
190
|
+
loop = asyncio.get_running_loop()
|
|
190
191
|
try:
|
|
191
192
|
_LOGGER.debug("Write data loop started")
|
|
192
193
|
while not self.writer.is_closing():
|
|
193
194
|
data = await self.buffer.get()
|
|
194
|
-
while (
|
|
195
|
+
while (loop.time() - self.last_bus_activity) < self.idle_time:
|
|
195
196
|
await asyncio.sleep(self.idle_time)
|
|
196
197
|
|
|
197
198
|
_LOGGER.debug(
|
|
@@ -201,7 +202,7 @@ class PchkConnectionManager:
|
|
|
201
202
|
)
|
|
202
203
|
self.writer.write(data)
|
|
203
204
|
await self.writer.drain()
|
|
204
|
-
self.last_bus_activity =
|
|
205
|
+
self.last_bus_activity = loop.time()
|
|
205
206
|
finally:
|
|
206
207
|
# empty the queue
|
|
207
208
|
while not self.buffer.empty():
|
|
@@ -274,7 +275,6 @@ class PchkConnectionManager:
|
|
|
274
275
|
|
|
275
276
|
async def async_close(self) -> None:
|
|
276
277
|
"""Close the active connection."""
|
|
277
|
-
await self.cancel_requests()
|
|
278
278
|
if self.ping_timeout_handle is not None:
|
|
279
279
|
self.ping_timeout_handle.cancel()
|
|
280
280
|
await self.task_registry.cancel_all_tasks()
|
|
@@ -347,7 +347,7 @@ class PchkConnectionManager:
|
|
|
347
347
|
"""Ping was received."""
|
|
348
348
|
if self.ping_timeout_handle is not None:
|
|
349
349
|
self.ping_timeout_handle.cancel()
|
|
350
|
-
self.last_ping =
|
|
350
|
+
self.last_ping = asyncio.get_running_loop().time()
|
|
351
351
|
|
|
352
352
|
def is_ready(self) -> bool:
|
|
353
353
|
"""Retrieve the overall connection state."""
|
|
@@ -378,9 +378,7 @@ class PchkConnectionManager:
|
|
|
378
378
|
addr.is_group,
|
|
379
379
|
)
|
|
380
380
|
|
|
381
|
-
def get_module_conn(
|
|
382
|
-
self, addr: LcnAddr, request_serials: bool = True
|
|
383
|
-
) -> ModuleConnection:
|
|
381
|
+
def get_module_conn(self, addr: LcnAddr) -> ModuleConnection:
|
|
384
382
|
"""Create and/or return the given LCN module."""
|
|
385
383
|
assert not addr.is_group
|
|
386
384
|
if addr.seg_id == 0 and self.local_seg_id != -1:
|
|
@@ -390,8 +388,6 @@ class PchkConnectionManager:
|
|
|
390
388
|
address_conn = ModuleConnection(
|
|
391
389
|
self, addr, wants_ack=self.settings["ACKNOWLEDGE"]
|
|
392
390
|
)
|
|
393
|
-
if request_serials:
|
|
394
|
-
self.task_registry.create_task(address_conn.request_serials())
|
|
395
391
|
self.address_conns[addr] = address_conn
|
|
396
392
|
|
|
397
393
|
return address_conn
|
|
@@ -403,17 +399,15 @@ class PchkConnectionManager:
|
|
|
403
399
|
addr = LcnAddr(self.local_seg_id, addr.addr_id, addr.is_group)
|
|
404
400
|
return GroupConnection(self, addr)
|
|
405
401
|
|
|
406
|
-
def get_address_conn(
|
|
407
|
-
self, addr: LcnAddr, request_serials: bool = True
|
|
408
|
-
) -> ModuleConnection | GroupConnection:
|
|
402
|
+
def get_address_conn(self, addr: LcnAddr) -> ModuleConnection | GroupConnection:
|
|
409
403
|
"""Create and/or return a connection to the given module or group."""
|
|
410
404
|
if addr.is_group:
|
|
411
405
|
return self.get_group_conn(addr)
|
|
412
|
-
return self.get_module_conn(addr
|
|
406
|
+
return self.get_module_conn(addr)
|
|
413
407
|
|
|
414
408
|
# Other
|
|
415
409
|
|
|
416
|
-
def dump_modules(self) -> dict[str, dict[str, dict[str, Any]]]:
|
|
410
|
+
async def dump_modules(self) -> dict[str, dict[str, dict[str, Any]]]:
|
|
417
411
|
"""Dump all modules and information about them in a JSON serializable dict."""
|
|
418
412
|
dump: dict[str, dict[str, dict[str, Any]]] = {}
|
|
419
413
|
for address_conn in self.address_conns.values():
|
|
@@ -421,7 +415,7 @@ class PchkConnectionManager:
|
|
|
421
415
|
addr = f"{address_conn.addr.addr_id}"
|
|
422
416
|
if seg not in dump:
|
|
423
417
|
dump[seg] = {}
|
|
424
|
-
dump[seg][addr] = address_conn.dump_details()
|
|
418
|
+
dump[seg][addr] = await address_conn.dump_details()
|
|
425
419
|
return dump
|
|
426
420
|
|
|
427
421
|
# Command sending / retrieval.
|
|
@@ -584,19 +578,6 @@ class PchkConnectionManager:
|
|
|
584
578
|
|
|
585
579
|
self.segment_scan_completed_event.set()
|
|
586
580
|
|
|
587
|
-
# Status requests, responses
|
|
588
|
-
|
|
589
|
-
async def cancel_requests(self) -> None:
|
|
590
|
-
"""Cancel all TimeoutRetryHandlers."""
|
|
591
|
-
cancel_tasks = [
|
|
592
|
-
asyncio.create_task(address_conn.cancel_requests())
|
|
593
|
-
for address_conn in self.address_conns.values()
|
|
594
|
-
if isinstance(address_conn, ModuleConnection)
|
|
595
|
-
]
|
|
596
|
-
|
|
597
|
-
if cancel_tasks:
|
|
598
|
-
await asyncio.wait(cancel_tasks)
|
|
599
|
-
|
|
600
581
|
# Callbacks for inputs and events
|
|
601
582
|
|
|
602
583
|
def register_for_inputs(
|
|
@@ -378,6 +378,16 @@ class Var(Enum):
|
|
|
378
378
|
cls.VAR12,
|
|
379
379
|
]
|
|
380
380
|
|
|
381
|
+
@classmethod
|
|
382
|
+
def variables_new(cls) -> list[Var]:
|
|
383
|
+
"""Return a list of all new variable types (firmware >=0x170206)."""
|
|
384
|
+
return cls.variables()
|
|
385
|
+
|
|
386
|
+
@classmethod
|
|
387
|
+
def variables_old(cls) -> list[Var]:
|
|
388
|
+
"""Return a list of all variable types (firmware <0x170206)."""
|
|
389
|
+
return cls.variables()[:3]
|
|
390
|
+
|
|
381
391
|
@classmethod
|
|
382
392
|
def set_points(cls) -> list[Var]:
|
|
383
393
|
"""Return a list of all set-point variable types."""
|
|
@@ -393,6 +403,16 @@ class Var(Enum):
|
|
|
393
403
|
[cls.THRS4_1, cls.THRS4_2, cls.THRS4_3, cls.THRS4_4],
|
|
394
404
|
]
|
|
395
405
|
|
|
406
|
+
@classmethod
|
|
407
|
+
def thresholds_new(cls) -> list[list[Var]]:
|
|
408
|
+
"""Return a list of all threshold variable types (firmware >=0x170206)."""
|
|
409
|
+
return [cls.thresholds()[0][:4], *cls.thresholds()[1:]]
|
|
410
|
+
|
|
411
|
+
@classmethod
|
|
412
|
+
def thresholds_old(cls) -> list[list[Var]]:
|
|
413
|
+
"""Return a list of all old threshold variable types (firmware <0x170206)."""
|
|
414
|
+
return [cls.thresholds()[0]]
|
|
415
|
+
|
|
396
416
|
@classmethod
|
|
397
417
|
def s0s(cls) -> list[Var]:
|
|
398
418
|
"""Return a list of all S0-input variable types."""
|
|
@@ -1460,7 +1480,9 @@ default_connection_settings: dict[str, Any] = {
|
|
|
1460
1480
|
# been send which
|
|
1461
1481
|
# potentially changed
|
|
1462
1482
|
# that status
|
|
1483
|
+
"MAX_RESPONSE_AGE": 60, # Age in seconds after which stored responses are purged
|
|
1463
1484
|
"BUS_IDLE_TIME": 0.05, # Time to wait for message traffic before sending
|
|
1464
1485
|
"PING_SEND_DELAY": 600, # The default timeout for pings sent to PCHK
|
|
1465
1486
|
"PING_RECV_TIMEOUT": 10, # The default timeout for pings expected from PCHK
|
|
1487
|
+
"PING_MODULE_TIMEOUT": 60, # The delay before sending a ping to a module
|
|
1466
1488
|
}
|