Skip to content

Commit 2436923

Browse files
committed
Fix multiple assosciations and removals of networks
1 parent 48cca7c commit 2436923

File tree

3 files changed

+133
-0
lines changed

3 files changed

+133
-0
lines changed

canopen/node/local.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ def __init__(
3939
self.emcy = EmcyProducer(0x80 + self.id)
4040

4141
def associate_network(self, network: canopen.network.Network):
42+
if self.network is not canopen.network._UNINITIALIZED_NETWORK:
43+
# Unsubscribe from old network (to prevent double subscription)
44+
self.remove_network()
4245
self.network = network
4346
self.sdo.network = network
4447
self.tpdo.network = network
@@ -49,6 +52,9 @@ def associate_network(self, network: canopen.network.Network):
4952
network.subscribe(0, self.nmt.on_command)
5053

5154
def remove_network(self) -> None:
55+
# Make it safe to call this method multiple times
56+
if self.network is canopen.network._UNINITIALIZED_NETWORK:
57+
return
5258
self.network.unsubscribe(self.sdo.rx_cobid, self.sdo.on_request)
5359
self.network.unsubscribe(0, self.nmt.on_command)
5460
self.network = canopen.network._UNINITIALIZED_NETWORK

canopen/node/remote.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ def __init__(
5151
self.load_configuration()
5252

5353
def associate_network(self, network: canopen.network.Network):
54+
if self.network is not canopen.network._UNINITIALIZED_NETWORK:
55+
# Unsubscribe from old network (to prevent double subscriptions)
56+
self.remove_network()
5457
self.network = network
5558
self.sdo.network = network
5659
self.pdo.network = network
@@ -64,6 +67,9 @@ def associate_network(self, network: canopen.network.Network):
6467
network.subscribe(0, self.nmt.on_command)
6568

6669
def remove_network(self) -> None:
70+
# Make it safe to call this method multiple times
71+
if self.network is canopen.network._UNINITIALIZED_NETWORK:
72+
return
6773
for sdo in self.sdo_channels:
6874
self.network.unsubscribe(sdo.tx_cobid, sdo.on_response)
6975
self.network.unsubscribe(0x700 + self.id, self.nmt.on_heartbeat)

test/test_node.py

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

0 commit comments

Comments
 (0)