Skip to content

Commit dba463a

Browse files
committed
Merge master into feature-asyncio
2 parents bd749cd + ae71853 commit dba463a

35 files changed

+187
-130
lines changed

canopen/__init__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
from canopen.network import Network, NodeScanner
2-
from canopen.node import RemoteNode, LocalNode
3-
from canopen.sdo import SdoCommunicationError, SdoAbortedError
4-
from canopen.objectdictionary import import_od, export_od, ObjectDictionary, ObjectDictionaryError
2+
from canopen.node import LocalNode, RemoteNode
3+
from canopen.objectdictionary import (
4+
ObjectDictionary,
5+
ObjectDictionaryError,
6+
export_od,
7+
import_od,
8+
)
59
from canopen.profiles.p402 import BaseNode402
10+
from canopen.sdo import SdoAbortedError, SdoCommunicationError
11+
612
try:
713
from canopen._version import version as __version__
814
except ImportError:

canopen/emcy.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import struct
1+
import asyncio
22
import logging
3+
import struct
34
import threading
4-
import asyncio
55
import time
6-
from typing import Callable, List, Optional, TYPE_CHECKING
6+
from typing import Callable, List, Optional
77

88
from canopen.async_guard import ensure_not_async
9+
import canopen.network
910

10-
if TYPE_CHECKING:
11-
from canopen.network import Network
1211

1312
# Error code, error register, vendor specific data
1413
EMCY_STRUCT = struct.Struct("<HB5s")
@@ -117,7 +116,7 @@ def wait(
117116
class EmcyProducer:
118117

119118
def __init__(self, cob_id: int):
120-
self.network: Optional[Network] = None
119+
self.network: canopen.network.Network = canopen.network._UNINITIALIZED_NETWORK
121120
self.cob_id = cob_id
122121

123122
def send(self, code: int, register: int = 0, data: bytes = b""):

canopen/lss.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
from typing import Optional, TYPE_CHECKING
1+
import asyncio
22
import logging
3-
import time
4-
import struct
53
import queue
6-
import asyncio
4+
import struct
5+
import time
6+
from typing import Optional, TYPE_CHECKING
77

88
from canopen.async_guard import ensure_not_async
9+
import canopen.network
910

10-
if TYPE_CHECKING:
11-
from canopen.network import Network
1211

1312
logger = logging.getLogger(__name__)
1413

@@ -85,8 +84,8 @@ class LssMaster:
8584
#: Max time in seconds to wait for response from server
8685
RESPONSE_TIMEOUT = 0.5
8786

88-
def __init__(self):
89-
self.network: Optional[Network] = None
87+
def __init__(self) -> None:
88+
self.network: canopen.network.Network = canopen.network._UNINITIALIZED_NETWORK
9089
self._node_id = 0
9190
self._data = None
9291
self.responses = queue.Queue()

canopen/network.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
from __future__ import annotations
2-
from collections.abc import MutableMapping
2+
3+
import asyncio
34
import logging
45
import threading
5-
from typing import Callable, Dict, Iterator, List, Optional, Union, TYPE_CHECKING
6-
import asyncio
7-
8-
if TYPE_CHECKING:
9-
from can import BusABC, Notifier
10-
from asyncio import AbstractEventLoop
6+
from collections.abc import MutableMapping
7+
from typing import Callable, Dict, Final, Iterator, List, Optional, Union
118

129
import can
1310
from can import Listener
14-
from can import CanError
1511

16-
from canopen.node import RemoteNode, LocalNode
17-
from canopen.sync import SyncProducer
18-
from canopen.timestamp import TimeProducer
19-
from canopen.nmt import NmtMaster
12+
from canopen.async_guard import set_async_sentinel
2013
from canopen.lss import LssMaster
21-
from canopen.objectdictionary.eds import import_from_node
14+
from canopen.nmt import NmtMaster
15+
from canopen.node import LocalNode, RemoteNode
2216
from canopen.objectdictionary import ObjectDictionary
23-
from canopen.async_guard import set_async_sentinel
17+
from canopen.objectdictionary.eds import import_from_node
18+
from canopen.sync import SyncProducer
19+
from canopen.timestamp import TimeProducer
20+
2421

2522
logger = logging.getLogger(__name__)
2623

@@ -34,7 +31,7 @@ class Network(MutableMapping):
3431
NOTIFIER_SHUTDOWN_TIMEOUT: float = 5.0 #: Maximum waiting time to stop notifiers.
3532

3633
def __init__(self, bus: Optional[can.BusABC] = None, notifier: Optional[can.Notifier] = None,
37-
loop: Optional[AbstractEventLoop] = None):
34+
loop: Optional[asyncio.AbstractEventLoop] = None):
3835
"""
3936
:param can.BusABC bus:
4037
A python-can bus instance to re-use.
@@ -314,6 +311,21 @@ def __len__(self) -> int:
314311
return len(self.nodes)
315312

316313

314+
class _UninitializedNetwork(Network):
315+
"""Empty network implementation as a placeholder before actual initialization."""
316+
317+
def __init__(self, bus: Optional[can.BusABC] = None):
318+
"""Do not initialize attributes, by skipping the parent constructor."""
319+
320+
def __getattribute__(self, name):
321+
raise RuntimeError("No actual Network object was assigned, "
322+
"try associating to a real network first.")
323+
324+
325+
#: Singleton instance
326+
_UNINITIALIZED_NETWORK: Final[Network] = _UninitializedNetwork()
327+
328+
317329
class PeriodicMessageTask:
318330
"""
319331
Task object to transmit a message periodically using python-can's

canopen/nmt.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import threading
1+
import asyncio
22
import logging
33
import struct
4+
import threading
45
import time
5-
import asyncio
66
from typing import Callable, Optional, TYPE_CHECKING
77

88
from canopen.async_guard import ensure_not_async
9+
import canopen.network
910

1011
if TYPE_CHECKING:
11-
from canopen.network import Network, PeriodicMessageTask
12+
from canopen.network import PeriodicMessageTask
1213

1314

1415
logger = logging.getLogger(__name__)
@@ -52,7 +53,7 @@ class NmtBase:
5253

5354
def __init__(self, node_id: int):
5455
self.id = node_id
55-
self.network: Optional[Network] = None
56+
self.network: canopen.network.Network = canopen.network._UNINITIALIZED_NETWORK
5657
self._state = 0
5758

5859
def on_command(self, can_id, data, timestamp):

canopen/node/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
from canopen.node.remote import RemoteNode
21
from canopen.node.local import LocalNode
2+
from canopen.node.remote import RemoteNode

canopen/node/base.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
from typing import TextIO, Union, Optional, TYPE_CHECKING
1+
from typing import TextIO, Union
22

3+
import canopen.network
34
from canopen.objectdictionary import ObjectDictionary, import_od
45

5-
if TYPE_CHECKING:
6-
from canopen.network import Network
7-
86

97
class BaseNode:
108
"""A CANopen node.
@@ -21,7 +19,7 @@ def __init__(
2119
node_id: int,
2220
object_dictionary: Union[ObjectDictionary, str, TextIO],
2321
):
24-
self.network: Optional[Network] = None
22+
self.network: canopen.network.Network = canopen.network._UNINITIALIZED_NETWORK
2523

2624
if not isinstance(object_dictionary, ObjectDictionary):
2725
object_dictionary = import_od(object_dictionary, node_id)
@@ -30,3 +28,7 @@ def __init__(
3028
self.id = node_id or self.object_dictionary.node_id
3129

3230
# FIXME: Should associate_network() and remove_network() be a part of the base API?
31+
32+
def has_network(self) -> bool:
33+
"""Check whether the node has been associated to a network."""
34+
return not isinstance(self.network, canopen.network._UninitializedNetwork)

canopen/node/local.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
from __future__ import annotations
2+
23
import logging
3-
from typing import Dict, Union, TYPE_CHECKING
4+
from typing import Dict, Union
45

5-
from canopen.node.base import BaseNode
6-
from canopen.sdo import SdoServer, SdoAbortedError
7-
from canopen.pdo import PDO, TPDO, RPDO
8-
from canopen.nmt import NmtSlave
6+
import canopen.network
7+
from canopen import objectdictionary
98
from canopen.emcy import EmcyProducer
9+
from canopen.nmt import NmtSlave
10+
from canopen.node.base import BaseNode
1011
from canopen.objectdictionary import ObjectDictionary
11-
from canopen import objectdictionary
12+
from canopen.pdo import PDO, RPDO, TPDO
13+
from canopen.sdo import SdoAbortedError, SdoServer
1214

13-
if TYPE_CHECKING:
14-
from canopen.network import Network
1515

1616
logger = logging.getLogger(__name__)
1717

@@ -38,7 +38,7 @@ def __init__(
3838
self.add_write_callback(self.nmt.on_write)
3939
self.emcy = EmcyProducer(0x80 + self.id)
4040

41-
def associate_network(self, network: Network):
41+
def associate_network(self, network: canopen.network.Network):
4242
self.network = network
4343
self.sdo.network = network
4444
self.tpdo.network = network
@@ -48,16 +48,15 @@ def associate_network(self, network: Network):
4848
network.subscribe(self.sdo.rx_cobid, self.sdo.on_request)
4949
network.subscribe(0, self.nmt.on_command)
5050

51-
def remove_network(self):
52-
network = self.network
53-
network.unsubscribe(self.sdo.rx_cobid, self.sdo.on_request)
54-
network.unsubscribe(0, self.nmt.on_command)
55-
self.network = None
56-
self.sdo.network = None
57-
self.tpdo.network = None
58-
self.rpdo.network = None
59-
self.nmt.network = None
60-
self.emcy.network = None
51+
def remove_network(self) -> None:
52+
self.network.unsubscribe(self.sdo.rx_cobid, self.sdo.on_request)
53+
self.network.unsubscribe(0, self.nmt.on_command)
54+
self.network = canopen.network._UNINITIALIZED_NETWORK
55+
self.sdo.network = canopen.network._UNINITIALIZED_NETWORK
56+
self.tpdo.network = canopen.network._UNINITIALIZED_NETWORK
57+
self.rpdo.network = canopen.network._UNINITIALIZED_NETWORK
58+
self.nmt.network = canopen.network._UNINITIALIZED_NETWORK
59+
self.emcy.network = canopen.network._UNINITIALIZED_NETWORK
6160

6261
def add_read_callback(self, callback):
6362
self._read_callbacks.append(callback)

canopen/node/remote.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
from __future__ import annotations
2+
23
import logging
3-
from typing import Union, TextIO, List, TYPE_CHECKING
4+
from typing import TextIO, Union, List
45

5-
from canopen.sdo import SdoClient, SdoCommunicationError, SdoAbortedError
6-
from canopen.nmt import NmtMaster
6+
import canopen.network
77
from canopen.emcy import EmcyConsumer
8-
from canopen.pdo import TPDO, RPDO, PDO
9-
from canopen.objectdictionary import ODRecord, ODArray, ODVariable, ObjectDictionary
8+
from canopen.nmt import NmtMaster
109
from canopen.node.base import BaseNode
10+
from canopen.objectdictionary import ODArray, ODRecord, ODVariable, ObjectDictionary
11+
from canopen.pdo import PDO, RPDO, TPDO
12+
from canopen.sdo import SdoAbortedError, SdoClient, SdoCommunicationError
1113

12-
if TYPE_CHECKING:
13-
from canopen.network import Network
1414

1515
logger = logging.getLogger(__name__)
1616

@@ -50,7 +50,7 @@ def __init__(
5050
if load_od:
5151
self.load_configuration()
5252

53-
def associate_network(self, network: Network):
53+
def associate_network(self, network: canopen.network.Network):
5454
self.network = network
5555
self.sdo.network = network
5656
self.pdo.network = network
@@ -67,23 +67,22 @@ def associate_network(self, network: Network):
6767
network.subscribe(0x80 + self.id, self.emcy.on_emcy)
6868
network.subscribe(0, self.nmt.on_command)
6969

70-
def remove_network(self):
71-
network = self.network
70+
def remove_network(self) -> None:
7271
for sdo in self.sdo_channels:
73-
network.unsubscribe(sdo.tx_cobid, sdo.on_response)
74-
if network.is_async():
75-
network.unsubscribe(0x700 + self.id, self.nmt.aon_heartbeat)
76-
network.unsubscribe(0x80 + self.id, self.emcy.aon_emcy)
72+
self.network.unsubscribe(sdo.tx_cobid, sdo.on_response)
73+
if self.network.is_async():
74+
self.network.unsubscribe(0x700 + self.id, self.nmt.aon_heartbeat)
75+
self.network.unsubscribe(0x80 + self.id, self.emcy.aon_emcy)
7776
else:
78-
network.unsubscribe(0x700 + self.id, self.nmt.on_heartbeat)
79-
network.unsubscribe(0x80 + self.id, self.emcy.on_emcy)
80-
network.unsubscribe(0, self.nmt.on_command)
81-
self.network = None
82-
self.sdo.network = None
83-
self.pdo.network = None
84-
self.tpdo.network = None
85-
self.rpdo.network = None
86-
self.nmt.network = None
77+
self.network.unsubscribe(0x700 + self.id, self.nmt.on_heartbeat)
78+
self.network.unsubscribe(0x80 + self.id, self.emcy.on_emcy)
79+
self.network.unsubscribe(0, self.nmt.on_command)
80+
self.network = canopen.network._UNINITIALIZED_NETWORK
81+
self.sdo.network = canopen.network._UNINITIALIZED_NETWORK
82+
self.pdo.network = canopen.network._UNINITIALIZED_NETWORK
83+
self.tpdo.network = canopen.network._UNINITIALIZED_NETWORK
84+
self.rpdo.network = canopen.network._UNINITIALIZED_NETWORK
85+
self.nmt.network = canopen.network._UNINITIALIZED_NETWORK
8786

8887
def add_sdo(self, rx_cobid, tx_cobid):
8988
"""Add an additional SDO channel.
@@ -100,7 +99,7 @@ def add_sdo(self, rx_cobid, tx_cobid):
10099
"""
101100
client = SdoClient(rx_cobid, tx_cobid, self.object_dictionary)
102101
self.sdo_channels.append(client)
103-
if self.network is not None:
102+
if self.has_network():
104103
self.network.subscribe(client.tx_cobid, client.on_response)
105104
return client
106105

canopen/objectdictionary/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
"""
22
Object Dictionary module
33
"""
4+
45
from __future__ import annotations
56

7+
import logging
68
import struct
9+
from collections.abc import Mapping, MutableMapping
710
from typing import Dict, Iterator, List, Optional, TextIO, Union
8-
from collections.abc import MutableMapping, Mapping
9-
import logging
1011

1112
from canopen.objectdictionary.datatypes import *
1213
from canopen.objectdictionary.datatypes import IntegerN, UnsignedN
1314
from canopen.utils import pretty_index
1415

16+
1517
logger = logging.getLogger(__name__)
1618

1719

0 commit comments

Comments
 (0)