pypck 0.8.9__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/__init__.py +0 -2
- pypck/connection.py +15 -34
- pypck/helpers.py +3 -3
- pypck/lcn_defs.py +68 -44
- pypck/module.py +416 -230
- pypck/py.typed +0 -0
- pypck-0.9.1.dist-info/METADATA +65 -0
- pypck-0.9.1.dist-info/RECORD +14 -0
- pypck/request_handlers.py +0 -693
- pypck/timeout_retry.py +0 -110
- pypck-0.8.9.dist-info/METADATA +0 -145
- pypck-0.8.9.dist-info/RECORD +0 -15
- {pypck-0.8.9.dist-info → pypck-0.9.1.dist-info}/WHEEL +0 -0
- {pypck-0.8.9.dist-info → pypck-0.9.1.dist-info}/licenses/LICENSE +0 -0
- {pypck-0.8.9.dist-info → pypck-0.9.1.dist-info}/top_level.txt +0 -0
pypck/__init__.py
CHANGED
pypck/connection.py
CHANGED
|
@@ -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
|
|
@@ -13,7 +12,7 @@ from pypck import inputs, lcn_defs
|
|
|
13
12
|
from pypck.helpers import TaskRegistry
|
|
14
13
|
from pypck.lcn_addr import LcnAddr
|
|
15
14
|
from pypck.lcn_defs import LcnEvent
|
|
16
|
-
from pypck.module import
|
|
15
|
+
from pypck.module import GroupConnection, ModuleConnection
|
|
17
16
|
from pypck.pck_commands import PckGenerator
|
|
18
17
|
|
|
19
18
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -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():
|
|
@@ -243,7 +244,7 @@ class PchkConnectionManager:
|
|
|
243
244
|
if isinstance(exc, (ConnectionRefusedError, OSError)):
|
|
244
245
|
raise PchkConnectionRefusedError()
|
|
245
246
|
else:
|
|
246
|
-
raise
|
|
247
|
+
raise exc
|
|
247
248
|
|
|
248
249
|
if pending:
|
|
249
250
|
for awaitable in pending:
|
|
@@ -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
|
-
|
|
408
|
-
) -> AbstractConnection:
|
|
409
|
-
"""Create and/or return an AbstractConnection to the given module or group."""
|
|
402
|
+
def get_address_conn(self, addr: LcnAddr) -> ModuleConnection | GroupConnection:
|
|
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(
|
pypck/helpers.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Helper functions for pypck."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
from collections.abc import
|
|
4
|
+
from collections.abc import Coroutine
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
|
|
@@ -30,9 +30,9 @@ class TaskRegistry:
|
|
|
30
30
|
if task in self.tasks:
|
|
31
31
|
self.tasks.remove(task)
|
|
32
32
|
|
|
33
|
-
def create_task(self, coro:
|
|
33
|
+
def create_task(self, coro: Coroutine[Any, Any, Any]) -> "asyncio.Task[None]":
|
|
34
34
|
"""Create a task and store a reference in the task registry."""
|
|
35
|
-
task = asyncio.create_task(coro)
|
|
35
|
+
task: asyncio.Task[Any] = asyncio.create_task(coro)
|
|
36
36
|
task.add_done_callback(self.remove_task)
|
|
37
37
|
self.tasks.append(task)
|
|
38
38
|
return task
|
pypck/lcn_defs.py
CHANGED
|
@@ -360,6 +360,64 @@ class Var(Enum):
|
|
|
360
360
|
S0INPUT3 = auto()
|
|
361
361
|
S0INPUT4 = auto() # LCN-BU4LJVarValue
|
|
362
362
|
|
|
363
|
+
@classmethod
|
|
364
|
+
def variables(cls) -> list[Var]:
|
|
365
|
+
"""Return a list of all variable types."""
|
|
366
|
+
return [
|
|
367
|
+
cls.VAR1ORTVAR,
|
|
368
|
+
cls.VAR2ORR1VAR,
|
|
369
|
+
cls.VAR3ORR2VAR,
|
|
370
|
+
cls.VAR4,
|
|
371
|
+
cls.VAR5,
|
|
372
|
+
cls.VAR6,
|
|
373
|
+
cls.VAR7,
|
|
374
|
+
cls.VAR8,
|
|
375
|
+
cls.VAR9,
|
|
376
|
+
cls.VAR10,
|
|
377
|
+
cls.VAR11,
|
|
378
|
+
cls.VAR12,
|
|
379
|
+
]
|
|
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
|
+
|
|
391
|
+
@classmethod
|
|
392
|
+
def set_points(cls) -> list[Var]:
|
|
393
|
+
"""Return a list of all set-point variable types."""
|
|
394
|
+
return [cls.R1VARSETPOINT, cls.R2VARSETPOINT]
|
|
395
|
+
|
|
396
|
+
@classmethod
|
|
397
|
+
def thresholds(cls) -> list[list[Var]]:
|
|
398
|
+
"""Return a list of all threshold variable types."""
|
|
399
|
+
return [
|
|
400
|
+
[cls.THRS1, cls.THRS2, cls.THRS3, cls.THRS4, cls.THRS5],
|
|
401
|
+
[cls.THRS2_1, cls.THRS2_2, cls.THRS2_3, cls.THRS2_4],
|
|
402
|
+
[cls.THRS3_1, cls.THRS3_2, cls.THRS3_3, cls.THRS3_4],
|
|
403
|
+
[cls.THRS4_1, cls.THRS4_2, cls.THRS4_3, cls.THRS4_4],
|
|
404
|
+
]
|
|
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
|
+
|
|
416
|
+
@classmethod
|
|
417
|
+
def s0s(cls) -> list[Var]:
|
|
418
|
+
"""Return a list of all S0-input variable types."""
|
|
419
|
+
return [cls.S0INPUT1, cls.S0INPUT2, cls.S0INPUT3, cls.S0INPUT4]
|
|
420
|
+
|
|
363
421
|
@staticmethod
|
|
364
422
|
def var_id_to_var(var_id: int) -> Var:
|
|
365
423
|
"""Translate a given id into a variable type.
|
|
@@ -369,9 +427,9 @@ class Var(Enum):
|
|
|
369
427
|
:returns: The translated variable enum.
|
|
370
428
|
:rtype: Var
|
|
371
429
|
"""
|
|
372
|
-
if (var_id < 0) or (var_id >= len(Var.variables)):
|
|
430
|
+
if (var_id < 0) or (var_id >= len(Var.variables())):
|
|
373
431
|
raise ValueError("Bad var_id.")
|
|
374
|
-
return Var.variables[var_id]
|
|
432
|
+
return Var.variables()[var_id]
|
|
375
433
|
|
|
376
434
|
@staticmethod
|
|
377
435
|
def set_point_id_to_var(set_point_id: int) -> Var:
|
|
@@ -382,9 +440,9 @@ class Var(Enum):
|
|
|
382
440
|
:return: The translated var
|
|
383
441
|
:rtype: Var
|
|
384
442
|
"""
|
|
385
|
-
if (set_point_id < 0) or (set_point_id >= len(Var.set_points)):
|
|
443
|
+
if (set_point_id < 0) or (set_point_id >= len(Var.set_points())):
|
|
386
444
|
raise ValueError("Bad set_point_id.")
|
|
387
|
-
return Var.set_points[set_point_id]
|
|
445
|
+
return Var.set_points()[set_point_id]
|
|
388
446
|
|
|
389
447
|
@staticmethod
|
|
390
448
|
def thrs_id_to_var(register_id: int, thrs_id: int) -> Var:
|
|
@@ -399,12 +457,12 @@ class Var(Enum):
|
|
|
399
457
|
"""
|
|
400
458
|
if (
|
|
401
459
|
(register_id < 0)
|
|
402
|
-
or (register_id >= len(Var.thresholds))
|
|
460
|
+
or (register_id >= len(Var.thresholds()))
|
|
403
461
|
or (thrs_id < 0)
|
|
404
462
|
or (thrs_id >= (5 if (register_id == 0) else 4))
|
|
405
463
|
):
|
|
406
464
|
raise ValueError("Bad register_id and/or thrs_id.")
|
|
407
|
-
return Var.thresholds[register_id][thrs_id]
|
|
465
|
+
return Var.thresholds()[register_id][thrs_id]
|
|
408
466
|
|
|
409
467
|
@staticmethod
|
|
410
468
|
def s0_id_to_var(s0_id: int) -> Var:
|
|
@@ -415,9 +473,9 @@ class Var(Enum):
|
|
|
415
473
|
:return: The translated var
|
|
416
474
|
:rtype: Var
|
|
417
475
|
"""
|
|
418
|
-
if (s0_id < 0) or (s0_id >= len(Var.s0s)):
|
|
476
|
+
if (s0_id < 0) or (s0_id >= len(Var.s0s())):
|
|
419
477
|
raise ValueError("Bad s0_id.")
|
|
420
|
-
return Var.s0s[s0_id]
|
|
478
|
+
return Var.s0s()[s0_id]
|
|
421
479
|
|
|
422
480
|
@staticmethod
|
|
423
481
|
def to_var_id(var: Var) -> int:
|
|
@@ -658,42 +716,6 @@ class Var(Enum):
|
|
|
658
716
|
return (not lock_state) and (software_serial < 0x170206)
|
|
659
717
|
|
|
660
718
|
|
|
661
|
-
# Helper list to get var by numeric id.
|
|
662
|
-
Var.variables = [ # type: ignore
|
|
663
|
-
Var.VAR1ORTVAR,
|
|
664
|
-
Var.VAR2ORR1VAR,
|
|
665
|
-
Var.VAR3ORR2VAR,
|
|
666
|
-
Var.VAR4,
|
|
667
|
-
Var.VAR5,
|
|
668
|
-
Var.VAR6,
|
|
669
|
-
Var.VAR7,
|
|
670
|
-
Var.VAR8,
|
|
671
|
-
Var.VAR9,
|
|
672
|
-
Var.VAR10,
|
|
673
|
-
Var.VAR11,
|
|
674
|
-
Var.VAR12,
|
|
675
|
-
]
|
|
676
|
-
|
|
677
|
-
# Helper list to get set-point var by numeric id.
|
|
678
|
-
Var.set_points = [Var.R1VARSETPOINT, Var.R2VARSETPOINT] # type: ignore
|
|
679
|
-
|
|
680
|
-
# Helper list to get threshold var by numeric id.
|
|
681
|
-
Var.thresholds = [ # type: ignore
|
|
682
|
-
[Var.THRS1, Var.THRS2, Var.THRS3, Var.THRS4, Var.THRS5],
|
|
683
|
-
[Var.THRS2_1, Var.THRS2_2, Var.THRS2_3, Var.THRS2_4],
|
|
684
|
-
[Var.THRS3_1, Var.THRS3_2, Var.THRS3_3, Var.THRS3_4],
|
|
685
|
-
[Var.THRS4_1, Var.THRS4_2, Var.THRS4_3, Var.THRS4_4],
|
|
686
|
-
]
|
|
687
|
-
|
|
688
|
-
# Helper list to get S0-input var by numeric id.
|
|
689
|
-
Var.s0s = [ # type: ignore
|
|
690
|
-
Var.S0INPUT1,
|
|
691
|
-
Var.S0INPUT2,
|
|
692
|
-
Var.S0INPUT3,
|
|
693
|
-
Var.S0INPUT4,
|
|
694
|
-
]
|
|
695
|
-
|
|
696
|
-
|
|
697
719
|
class VarUnit(Enum):
|
|
698
720
|
"""Measurement units used with LCN variables."""
|
|
699
721
|
|
|
@@ -1458,7 +1480,9 @@ default_connection_settings: dict[str, Any] = {
|
|
|
1458
1480
|
# been send which
|
|
1459
1481
|
# potentially changed
|
|
1460
1482
|
# that status
|
|
1483
|
+
"MAX_RESPONSE_AGE": 60, # Age in seconds after which stored responses are purged
|
|
1461
1484
|
"BUS_IDLE_TIME": 0.05, # Time to wait for message traffic before sending
|
|
1462
1485
|
"PING_SEND_DELAY": 600, # The default timeout for pings sent to PCHK
|
|
1463
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
|
|
1464
1488
|
}
|