Skip to content

Commit 2204ef3

Browse files
committed
Merge branch 'master' into feature-asyncio
2 parents edc0444 + e840449 commit 2204ef3

File tree

5 files changed

+116
-4
lines changed

5 files changed

+116
-4
lines changed

.github/workflows/pythonpackage.yml

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

canopen/network.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ def unsubscribe(self, can_id, callback=None) -> None:
8888
If given, remove only this callback. Otherwise all callbacks for
8989
the CAN ID.
9090
"""
91-
if callback is None:
92-
del self.subscribers[can_id]
93-
else:
91+
if callback is not None:
9492
self.subscribers[can_id].remove(callback)
93+
if not self.subscribers[can_id] or callback is None:
94+
del self.subscribers[can_id]
9595

9696
def connect(self, *args, **kwargs) -> Network:
9797
"""Connect to CAN bus using python-can.

canopen/node/local.py

Lines changed: 4 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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
@@ -68,6 +70,8 @@ def associate_network(self, network: canopen.network.Network):
6870
network.subscribe(0, self.nmt.on_command)
6971

7072
def remove_network(self) -> None:
73+
if not self.has_network():
74+
return
7175
for sdo in self.sdo_channels:
7276
self.network.unsubscribe(sdo.tx_cobid, sdo.on_response)
7377
if self.network.is_async():

test/test_node.py

Lines changed: 104 additions & 0 deletions
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)