Skip to content

Commit f486f08

Browse files
committed
Merge branch 'master' into mypy
2 parents 9e07ce7 + cf9f33e commit f486f08

40 files changed

+290
-88
lines changed

.github/workflows/pythonpackage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
- name: Test with pytest
4545
run: pytest -v --cov=canopen --cov-report=xml --cov-branch
4646
- name: Upload coverage reports to Codecov
47-
uses: codecov/codecov-action@v4
47+
uses: codecov/codecov-action@v5
4848
with:
4949
token: ${{ secrets.CODECOV_TOKEN }}
5050

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ Install from PyPI_ using :program:`pip`::
4242

4343
Install from latest ``master`` on GitHub::
4444

45-
$ pip install https://github.com/christiansandberg/canopen/archive/master.zip
45+
$ pip install https://github.com/canopen-python/canopen/archive/master.zip
4646

4747
If you want to be able to change the code while using it, clone it then install
4848
it in `develop mode`_::
4949

50-
$ git clone https://github.com/christiansandberg/canopen.git
50+
$ git clone https://github.com/canopen-python/canopen.git
5151
$ cd canopen
5252
$ pip install -e .
5353

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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import struct
21
import logging
2+
import struct
33
import threading
44
import time
55
from typing import Callable, List, Optional
66

7+
import canopen.network
8+
9+
710
# Error code, error register, vendor specific data
811
EMCY_STRUCT = struct.Struct("<HB5s")
912

@@ -82,7 +85,7 @@ def wait(
8285
class EmcyProducer:
8386

8487
def __init__(self, cob_id: int):
85-
self.network = None
88+
self.network: canopen.network.Network = canopen.network._UNINITIALIZED_NETWORK
8689
self.cob_id = cob_id
8790

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

canopen/lss.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import logging
2-
import time
3-
import struct
42
import queue
3+
import struct
4+
import time
5+
6+
import canopen.network
7+
58

69
logger = logging.getLogger(__name__)
710

@@ -78,8 +81,8 @@ class LssMaster:
7881
#: Max time in seconds to wait for response from server
7982
RESPONSE_TIMEOUT = 0.5
8083

81-
def __init__(self):
82-
self.network = None
84+
def __init__(self) -> None:
85+
self.network: canopen.network.Network = canopen.network._UNINITIALIZED_NETWORK
8386
self._node_id = 0
8487
self._data = None
8588
self.responses = queue.Queue()

canopen/network.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
from __future__ import annotations
22

3-
from collections.abc import MutableMapping
43
import logging
54
import threading
6-
from typing import Callable, Dict, Iterator, List, Optional, Union
5+
from collections.abc import MutableMapping
6+
from typing import Callable, Dict, Final, Iterator, List, Optional, Union
77

88
import can
99
from can import Listener
10-
from can import CanError
1110

12-
from canopen.node import RemoteNode, LocalNode
13-
from canopen.sync import SyncProducer
14-
from canopen.timestamp import TimeProducer
15-
from canopen.nmt import NmtMaster
1611
from canopen.lss import LssMaster
17-
from canopen.objectdictionary.eds import import_from_node
12+
from canopen.nmt import NmtMaster
13+
from canopen.node import LocalNode, RemoteNode
1814
from canopen.objectdictionary import ObjectDictionary
15+
from canopen.objectdictionary.eds import import_from_node
16+
from canopen.sync import SyncProducer
17+
from canopen.timestamp import TimeProducer
18+
1919

2020
logger = logging.getLogger(__name__)
2121

@@ -75,10 +75,10 @@ def unsubscribe(self, can_id, callback=None) -> None:
7575
If given, remove only this callback. Otherwise all callbacks for
7676
the CAN ID.
7777
"""
78-
if callback is None:
79-
del self.subscribers[can_id]
80-
else:
78+
if callback is not None:
8179
self.subscribers[can_id].remove(callback)
80+
if not self.subscribers[can_id] or callback is None:
81+
del self.subscribers[can_id]
8282

8383
def connect(self, *args, **kwargs) -> Network:
8484
"""Connect to CAN bus using python-can.
@@ -282,6 +282,21 @@ def __len__(self) -> int:
282282
return len(self.nodes)
283283

284284

285+
class _UninitializedNetwork(Network):
286+
"""Empty network implementation as a placeholder before actual initialization."""
287+
288+
def __init__(self, bus: Optional[can.BusABC] = None):
289+
"""Do not initialize attributes, by skipping the parent constructor."""
290+
291+
def __getattribute__(self, name):
292+
raise RuntimeError("No actual Network object was assigned, "
293+
"try associating to a real network first.")
294+
295+
296+
#: Singleton instance
297+
_UNINITIALIZED_NETWORK: Final[Network] = _UninitializedNetwork()
298+
299+
285300
class PeriodicMessageTask:
286301
"""
287302
Task object to transmit a message periodically using python-can's

canopen/nmt.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import threading
21
import logging
32
import struct
3+
import threading
44
import time
55
from typing import Callable, Optional, TYPE_CHECKING
66

7+
import canopen.network
8+
79
if TYPE_CHECKING:
810
from canopen.network import PeriodicMessageTask
911

@@ -49,7 +51,7 @@ class NmtBase:
4951

5052
def __init__(self, node_id: int):
5153
self.id = node_id
52-
self.network = None
54+
self.network: canopen.network.Network = canopen.network._UNINITIALIZED_NETWORK
5355
self._state = 0
5456

5557
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from typing import TextIO, Union
2+
3+
import canopen.network
24
from canopen.objectdictionary import ObjectDictionary, import_od
35

46

@@ -17,10 +19,14 @@ def __init__(
1719
node_id: int,
1820
object_dictionary: Union[ObjectDictionary, str, TextIO],
1921
):
20-
self.network = None
22+
self.network: canopen.network.Network = canopen.network._UNINITIALIZED_NETWORK
2123

2224
if not isinstance(object_dictionary, ObjectDictionary):
2325
object_dictionary = import_od(object_dictionary, node_id)
2426
self.object_dictionary = object_dictionary
2527

2628
self.id = node_id or self.object_dictionary.node_id
29+
30+
def has_network(self) -> bool:
31+
"""Check whether the node has been associated to a network."""
32+
return not isinstance(self.network, canopen.network._UninitializedNetwork)

canopen/node/local.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1+
from __future__ import annotations
2+
13
import logging
24
from typing import Dict, Union
35

4-
from canopen.node.base import BaseNode
5-
from canopen.sdo import SdoServer, SdoAbortedError
6-
from canopen.pdo import PDO, TPDO, RPDO
7-
from canopen.nmt import NmtSlave
6+
import canopen.network
7+
from canopen import objectdictionary
88
from canopen.emcy import EmcyProducer
9+
from canopen.nmt import NmtSlave
10+
from canopen.node.base import BaseNode
911
from canopen.objectdictionary import ObjectDictionary
10-
from canopen import objectdictionary
12+
from canopen.pdo import PDO, RPDO, TPDO
13+
from canopen.sdo import SdoAbortedError, SdoServer
14+
1115

1216
logger = logging.getLogger(__name__)
1317

@@ -34,7 +38,9 @@ def __init__(
3438
self.add_write_callback(self.nmt.on_write)
3539
self.emcy = EmcyProducer(0x80 + self.id)
3640

37-
def associate_network(self, network):
41+
def associate_network(self, network: canopen.network.Network):
42+
if self.has_network():
43+
raise RuntimeError("Node is already associated with a network")
3844
self.network = network
3945
self.sdo.network = network
4046
self.tpdo.network = network
@@ -44,15 +50,17 @@ def associate_network(self, network):
4450
network.subscribe(self.sdo.rx_cobid, self.sdo.on_request)
4551
network.subscribe(0, self.nmt.on_command)
4652

47-
def remove_network(self):
53+
def remove_network(self) -> None:
54+
if not self.has_network():
55+
return
4856
self.network.unsubscribe(self.sdo.rx_cobid, self.sdo.on_request)
4957
self.network.unsubscribe(0, self.nmt.on_command)
50-
self.network = None
51-
self.sdo.network = None
52-
self.tpdo.network = None
53-
self.rpdo.network = None
54-
self.nmt.network = None
55-
self.emcy.network = None
58+
self.network = canopen.network._UNINITIALIZED_NETWORK
59+
self.sdo.network = canopen.network._UNINITIALIZED_NETWORK
60+
self.tpdo.network = canopen.network._UNINITIALIZED_NETWORK
61+
self.rpdo.network = canopen.network._UNINITIALIZED_NETWORK
62+
self.nmt.network = canopen.network._UNINITIALIZED_NETWORK
63+
self.emcy.network = canopen.network._UNINITIALIZED_NETWORK
5664

5765
def add_read_callback(self, callback):
5866
self._read_callbacks.append(callback)

0 commit comments

Comments
 (0)