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 CHANGED
@@ -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
  ]
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 AbstractConnection, GroupConnection, ModuleConnection
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 = 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():
@@ -243,7 +244,7 @@ class PchkConnectionManager:
243
244
  if isinstance(exc, (ConnectionRefusedError, OSError)):
244
245
  raise PchkConnectionRefusedError()
245
246
  else:
246
- raise awaitable.exception() # type: ignore
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 = 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
- ) -> 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, 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(
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 Awaitable
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: Awaitable[Any]) -> "asyncio.Task[None]":
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) # type: ignore
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)): # type: ignore
430
+ if (var_id < 0) or (var_id >= len(Var.variables())):
373
431
  raise ValueError("Bad var_id.")
374
- return Var.variables[var_id] # type: ignore
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)): # type: ignore
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] # type: ignore
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)) # type: ignore
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] # type: ignore
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)): # type: ignore
476
+ if (s0_id < 0) or (s0_id >= len(Var.s0s())):
419
477
  raise ValueError("Bad s0_id.")
420
- return Var.s0s[s0_id] # type: ignore
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
  }