11from __future__ import annotations
22
3- from collections .abc import MutableMapping
43import logging
54import threading
5+ from collections .abc import MutableMapping
6+ from typing import Callable , Dict , Final , Iterator , List , Optional , Union
67from typing import Callable , Dict , Iterator , List , Optional , Union , TYPE_CHECKING , TextIO
78
8- try :
9- import can
10- from can import Listener
11- from can import CanError
12- except ImportError :
13- # Type checkers don't like this conditional logic, so it is only run when
14- # not type checking
15- if not TYPE_CHECKING :
16- # Do not fail if python-can is not installed
17- can = None
18- CanError = Exception
19- class Listener :
20- """ Dummy listener """
21-
22- from canopen .node import RemoteNode , LocalNode
23- from canopen .sync import SyncProducer
24- from canopen .timestamp import TimeProducer
25- from canopen .nmt import NmtMaster
9+ import can
10+ from can import Listener
11+
2612from canopen .lss import LssMaster
27- from canopen .objectdictionary .eds import import_from_node
13+ from canopen .nmt import NmtMaster
14+ from canopen .node import LocalNode , RemoteNode
2815from canopen .objectdictionary import ObjectDictionary
16+ from canopen .objectdictionary .eds import import_from_node
17+ from canopen .sync import SyncProducer
18+ from canopen .timestamp import TimeProducer
2919
3020if TYPE_CHECKING :
3121 from can .typechecking import CanData
@@ -38,6 +28,9 @@ class Listener:
3828class Network (MutableMapping ):
3929 """Representation of one CAN bus containing one or more nodes."""
4030
31+ NOTIFIER_CYCLE : float = 1.0 #: Maximum waiting time for one notifier iteration.
32+ NOTIFIER_SHUTDOWN_TIMEOUT : float = 5.0 #: Maximum waiting time to stop notifiers.
33+
4134 def __init__ (self , bus : Optional [can .BusABC ] = None ):
4235 """
4336 :param can.BusABC bus:
@@ -118,7 +111,7 @@ def connect(self, *args, **kwargs) -> Network:
118111 if self .bus is None :
119112 self .bus = can .Bus (* args , ** kwargs )
120113 logger .info ("Connected to '%s'" , self .bus .channel_info )
121- self .notifier = can .Notifier (self .bus , self .listeners , 1 )
114+ self .notifier = can .Notifier (self .bus , self .listeners , self . NOTIFIER_CYCLE )
122115 return self
123116
124117 def disconnect (self ) -> None :
@@ -130,7 +123,7 @@ def disconnect(self) -> None:
130123 if hasattr (node , "pdo" ):
131124 node .pdo .stop ()
132125 if self .notifier is not None :
133- self .notifier .stop ()
126+ self .notifier .stop (self . NOTIFIER_SHUTDOWN_TIMEOUT )
134127 if self .bus is not None :
135128 self .bus .shutdown ()
136129 self .bus = None
@@ -296,6 +289,21 @@ def __len__(self) -> int:
296289 return len (self .nodes )
297290
298291
292+ class _UninitializedNetwork (Network ):
293+ """Empty network implementation as a placeholder before actual initialization."""
294+
295+ def __init__ (self , bus : Optional [can .BusABC ] = None ):
296+ """Do not initialize attributes, by skipping the parent constructor."""
297+
298+ def __getattribute__ (self , name ):
299+ raise RuntimeError ("No actual Network object was assigned, "
300+ "try associating to a real network first." )
301+
302+
303+ #: Singleton instance
304+ _UNINITIALIZED_NETWORK : Final [Network ] = _UninitializedNetwork ()
305+
306+
299307class PeriodicMessageTask :
300308 """
301309 Task object to transmit a message periodically using python-can's
@@ -325,7 +333,6 @@ def __init__(
325333 self .msg = can .Message (is_extended_id = can_id > 0x7FF ,
326334 arbitration_id = can_id ,
327335 data = data , is_remote_frame = remote )
328- self ._task = None
329336 self ._start ()
330337
331338 def _start (self ):
@@ -391,9 +398,6 @@ class NodeScanner:
391398 The network to use when doing active searching.
392399 """
393400
394- #: Activate or deactivate scanning
395- active = True
396-
397401 SERVICES = (0x700 , 0x580 , 0x180 , 0x280 , 0x380 , 0x480 , 0x80 )
398402
399403 def __init__ (self , network : Optional [Network ] = None ):
0 commit comments