Skip to content

Commit 9dbb9fd

Browse files
authored
Make sure any BaseNode always has a valid Node ID (#657)
The BaseNode.id attribute can be initialized either from the given node_id parameter, or from the Object Dictionary if zero was passed. The None value as mentioned in the docstring actually violates the type hint, so drop that suggestion. Make sure the inferred type of the self.id attribute does not allow None, but is always an integer. Furthermore, check the resulting Node ID against the allowed value range and raise a ValueError instead of constructing a node object with an invalid Node ID, which will cause problems down the road. This is technically an API change, but it just causes things to fail early on instead of raising exceptions later on. Copy the docstring for LocalNode as well, to document the expected node_id "invalid" value. And because there was none at all before, include a bit of functional description to set the expectations straight.
1 parent 35f4b7f commit 9dbb9fd

4 files changed

Lines changed: 39 additions & 3 deletions

File tree

canopen/node/base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class BaseNode:
88
"""A CANopen node.
99
1010
:param node_id:
11-
Node ID (set to None or 0 if specified by object dictionary)
11+
Node ID (set to 0 if specified by object dictionary)
1212
:param object_dictionary:
1313
Object dictionary as either a path to a file, an ``ObjectDictionary``
1414
or a file like object.
@@ -25,7 +25,9 @@ def __init__(
2525
object_dictionary = import_od(object_dictionary, node_id)
2626
self.object_dictionary = object_dictionary
2727

28-
self.id = node_id or self.object_dictionary.node_id
28+
self.id = node_id or object_dictionary.node_id or 0
29+
if not 1 <= self.id <= 127:
30+
raise ValueError(f"No valid Node ID provided, {self.id} not in range 1..127")
2931

3032
def has_network(self) -> bool:
3133
"""Check whether the node has been associated to a network."""

canopen/node/local.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@
1818

1919

2020
class LocalNode(BaseNode):
21+
"""Local CANopen node implementing essential communication services.
22+
23+
This does not provide a full-fledged communication logic stack, but needs
24+
additional application logic to wire up the various services, such as
25+
triggering PDO transmissions according to their communication parameters.
26+
27+
Notable exceptions are a local data store for SDO server access, and using
28+
the Heartbeat Producer Time parameter to control Heartbeat transmission.
29+
30+
:param node_id:
31+
Node ID (set to 0 if specified by object dictionary)
32+
:param object_dictionary:
33+
Object dictionary as either a path to a file, an ``ObjectDictionary``
34+
or a file like object.
35+
"""
2136

2237
def __init__(
2338
self,

canopen/node/remote.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class RemoteNode(BaseNode):
1919
"""A CANopen remote node.
2020
2121
:param node_id:
22-
Node ID (set to None or 0 if specified by object dictionary)
22+
Node ID (set to 0 if specified by object dictionary)
2323
:param object_dictionary:
2424
Object dictionary as either a path to a file, an ``ObjectDictionary``
2525
or a file like object.

test/test_node.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,25 @@ def count_subscribers(network: canopen.Network) -> int:
88
return sum(len(n) for n in network.subscribers.values())
99

1010

11+
class TestBaseNode(unittest.TestCase):
12+
13+
def test_valid_node_id(self):
14+
node = canopen.node.base.BaseNode(1, canopen.ObjectDictionary())
15+
self.assertEqual(node.id, 1)
16+
17+
def test_valid_node_id_from_od(self):
18+
od = canopen.ObjectDictionary()
19+
od.node_id = 2
20+
node = canopen.node.base.BaseNode(0, od)
21+
self.assertEqual(node.id, 2)
22+
23+
def test_invalid_node_id(self):
24+
with self.assertRaises(ValueError):
25+
_ = canopen.node.base.BaseNode(0, canopen.ObjectDictionary())
26+
with self.assertRaises(ValueError):
27+
_ = canopen.node.base.BaseNode(128, canopen.ObjectDictionary())
28+
29+
1130
class TestLocalNode(unittest.TestCase):
1231

1332
@classmethod

0 commit comments

Comments
 (0)