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.
Files changed (33) hide show
  1. pypck-0.9.2/PKG-INFO +65 -0
  2. pypck-0.9.2/README.md +43 -0
  3. pypck-0.9.2/VERSION +1 -0
  4. {pypck-0.8.12 → pypck-0.9.2}/pypck/__init__.py +0 -2
  5. {pypck-0.8.12 → pypck-0.9.2}/pypck/connection.py +12 -31
  6. {pypck-0.8.12 → pypck-0.9.2}/pypck/lcn_defs.py +22 -0
  7. {pypck-0.8.12 → pypck-0.9.2}/pypck/module.py +416 -230
  8. pypck-0.9.2/pypck.egg-info/PKG-INFO +65 -0
  9. {pypck-0.8.12 → pypck-0.9.2}/pypck.egg-info/SOURCES.txt +2 -2
  10. {pypck-0.8.12 → pypck-0.9.2}/tests/test_connection.py +14 -0
  11. pypck-0.9.2/tests/test_module.py +288 -0
  12. pypck-0.9.2/tests/test_status_requester.py +102 -0
  13. pypck-0.8.12/PKG-INFO +0 -145
  14. pypck-0.8.12/README.md +0 -123
  15. pypck-0.8.12/VERSION +0 -1
  16. pypck-0.8.12/pypck/request_handlers.py +0 -694
  17. pypck-0.8.12/pypck/timeout_retry.py +0 -110
  18. pypck-0.8.12/pypck.egg-info/PKG-INFO +0 -145
  19. {pypck-0.8.12 → pypck-0.9.2}/LICENSE +0 -0
  20. {pypck-0.8.12 → pypck-0.9.2}/pypck/helpers.py +0 -0
  21. {pypck-0.8.12 → pypck-0.9.2}/pypck/inputs.py +0 -0
  22. {pypck-0.8.12 → pypck-0.9.2}/pypck/lcn_addr.py +0 -0
  23. {pypck-0.8.12 → pypck-0.9.2}/pypck/pck_commands.py +0 -0
  24. {pypck-0.8.12 → pypck-0.9.2}/pypck.egg-info/dependency_links.txt +0 -0
  25. {pypck-0.8.12 → pypck-0.9.2}/pypck.egg-info/not-zip-safe +0 -0
  26. {pypck-0.8.12 → pypck-0.9.2}/pypck.egg-info/top_level.txt +0 -0
  27. {pypck-0.8.12 → pypck-0.9.2}/pyproject.toml +0 -0
  28. {pypck-0.8.12 → pypck-0.9.2}/setup.cfg +0 -0
  29. {pypck-0.8.12 → pypck-0.9.2}/tests/test_commands.py +0 -0
  30. {pypck-0.8.12 → pypck-0.9.2}/tests/test_dyn_text.py +0 -0
  31. {pypck-0.8.12 → pypck-0.9.2}/tests/test_input.py +0 -0
  32. {pypck-0.8.12 → pypck-0.9.2}/tests/test_messages.py +0 -0
  33. {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
+ ![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
+ ```
pypck-0.9.2/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # pypck - Asynchronous LCN-PCK library written in Python
2
+
3
+ ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/alengwenus/pypck?color=success)
4
+ ![GitHub Workflow Status (dev branch)](https://github.com/alengwenus/pypck/actions/workflows/ci.yaml/badge.svg?branch=dev)
5
+ ![Codecov branch](https://img.shields.io/codecov/c/github/alengwenus/pypck/dev)
6
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/pypck)](https://pypi.org/project/pypck/)
7
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](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
@@ -8,7 +8,6 @@ from pypck import (
8
8
  lcn_defs,
9
9
  module,
10
10
  pck_commands,
11
- timeout_retry,
12
11
  )
13
12
 
14
13
  __all__ = [
@@ -19,5 +18,4 @@ __all__ = [
19
18
  "lcn_defs",
20
19
  "module",
21
20
  "pck_commands",
22
- "timeout_retry",
23
21
  ]
@@ -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 = time.time()
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 = time.time()
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 (time.time() - self.last_bus_activity) < self.idle_time:
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 = time.time()
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 = time.time()
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, request_serials)
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
  }