Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e840449

Browse files
sveinseacolomb
andauthored
Fix multiple associations and removals of networks (#573)
* Fix multiple assosciations and removals of networks * Simplify cleanup logic in Network.unsubscribe(). Co-authored-by: André Colomb <[email protected]>
1 parent f1a71da commit e840449

File tree

4 files changed

+115
-3
lines changed

4 files changed

+115
-3
lines changed

canopen/network.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -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.

canopen/node/local.py

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ def __init__(
3939
self.emcy = EmcyProducer(0x80 + self.id)
4040

4141
def associate_network(self, network: canopen.network.Network):
42+
if self.has_network():
43+
raise RuntimeError("Node is already associated with a network")
4244
self.network = network
4345
self.sdo.network = network
4446
self.tpdo.network = network
@@ -49,6 +51,8 @@ def associate_network(self, network: canopen.network.Network):
4951
network.subscribe(0, self.nmt.on_command)
5052

5153
def remove_network(self) -> None:
54+
if not self.has_network():
55+
return
5256
self.network.unsubscribe(self.sdo.rx_cobid, self.sdo.on_request)
5357
self.network.unsubscribe(0, self.nmt.on_command)
5458
self.network = canopen.network._UNINITIALIZED_NETWORK

canopen/node/remote.py

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ def __init__(
5151
self.load_configuration()
5252

5353
def associate_network(self, network: canopen.network.Network):
54+
if self.has_network():
55+
raise RuntimeError("Node is already associated with a network")
5456
self.network = network
5557
self.sdo.network = network
5658
self.pdo.network = network
@@ -64,6 +66,8 @@ def associate_network(self, network: canopen.network.Network):
6466
network.subscribe(0, self.nmt.on_command)
6567

6668
def remove_network(self) -> None:
69+
if not self.has_network():
70+
return
6771
for sdo in self.sdo_channels:
6872
self.network.unsubscribe(sdo.tx_cobid, sdo.on_response)
6973
self.network.unsubscribe(0x700 + self.id, self.nmt.on_heartbeat)

test/test_node.py

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import unittest
2+
3+
import canopen
4+
5+
6+
def count_subscribers(network: canopen.Network) -> int:
7+
"""Count the number of subscribers in the network."""
8+
return sum(len(n) for n in network.subscribers.values())
9+
10+
11+
class TestLocalNode(unittest.TestCase):
12+
13+
@classmethod
14+
def setUpClass(cls):
15+
cls.network = canopen.Network()
16+
cls.network.NOTIFIER_SHUTDOWN_TIMEOUT = 0.0
17+
cls.network.connect(interface="virtual")
18+
19+
cls.node = canopen.LocalNode(2, canopen.objectdictionary.ObjectDictionary())
20+
21+
@classmethod
22+
def tearDownClass(cls):
23+
cls.network.disconnect()
24+
25+
def test_associate_network(self):
26+
# Need to store the number of subscribers before associating because the
27+
# network implementation automatically adds subscribers to the list
28+
n_subscribers = count_subscribers(self.network)
29+
30+
# Associating the network with the local node
31+
self.node.associate_network(self.network)
32+
self.assertIs(self.node.network, self.network)
33+
self.assertIs(self.node.sdo.network, self.network)
34+
self.assertIs(self.node.tpdo.network, self.network)
35+
self.assertIs(self.node.rpdo.network, self.network)
36+
self.assertIs(self.node.nmt.network, self.network)
37+
self.assertIs(self.node.emcy.network, self.network)
38+
39+
# Test that its not possible to associate the network multiple times
40+
with self.assertRaises(RuntimeError) as cm:
41+
self.node.associate_network(self.network)
42+
self.assertIn("already associated with a network", str(cm.exception))
43+
44+
# Test removal of the network. The count of subscribers should
45+
# be the same as before the association
46+
self.node.remove_network()
47+
uninitalized = canopen.network._UNINITIALIZED_NETWORK
48+
self.assertIs(self.node.network, uninitalized)
49+
self.assertIs(self.node.sdo.network, uninitalized)
50+
self.assertIs(self.node.tpdo.network, uninitalized)
51+
self.assertIs(self.node.rpdo.network, uninitalized)
52+
self.assertIs(self.node.nmt.network, uninitalized)
53+
self.assertIs(self.node.emcy.network, uninitalized)
54+
self.assertEqual(count_subscribers(self.network), n_subscribers)
55+
56+
# Test that its possible to deassociate the network multiple times
57+
self.node.remove_network()
58+
59+
60+
class TestRemoteNode(unittest.TestCase):
61+
62+
@classmethod
63+
def setUpClass(cls):
64+
cls.network = canopen.Network()
65+
cls.network.NOTIFIER_SHUTDOWN_TIMEOUT = 0.0
66+
cls.network.connect(interface="virtual")
67+
68+
cls.node = canopen.RemoteNode(2, canopen.objectdictionary.ObjectDictionary())
69+
70+
@classmethod
71+
def tearDownClass(cls):
72+
cls.network.disconnect()
73+
74+
def test_associate_network(self):
75+
# Need to store the number of subscribers before associating because the
76+
# network implementation automatically adds subscribers to the list
77+
n_subscribers = count_subscribers(self.network)
78+
79+
# Associating the network with the local node
80+
self.node.associate_network(self.network)
81+
self.assertIs(self.node.network, self.network)
82+
self.assertIs(self.node.sdo.network, self.network)
83+
self.assertIs(self.node.tpdo.network, self.network)
84+
self.assertIs(self.node.rpdo.network, self.network)
85+
self.assertIs(self.node.nmt.network, self.network)
86+
87+
# Test that its not possible to associate the network multiple times
88+
with self.assertRaises(RuntimeError) as cm:
89+
self.node.associate_network(self.network)
90+
self.assertIn("already associated with a network", str(cm.exception))
91+
92+
# Test removal of the network. The count of subscribers should
93+
# be the same as before the association
94+
self.node.remove_network()
95+
uninitalized = canopen.network._UNINITIALIZED_NETWORK
96+
self.assertIs(self.node.network, uninitalized)
97+
self.assertIs(self.node.sdo.network, uninitalized)
98+
self.assertIs(self.node.tpdo.network, uninitalized)
99+
self.assertIs(self.node.rpdo.network, uninitalized)
100+
self.assertIs(self.node.nmt.network, uninitalized)
101+
self.assertEqual(count_subscribers(self.network), n_subscribers)
102+
103+
# Test that its possible to deassociate the network multiple times
104+
self.node.remove_network()

0 commit comments

Comments
 (0)