Skip to content

Commit 7822ca4

Browse files
authored
Merge branch 'canopen-python:master' into sdo-block-upload-retransmit-fix
2 parents ff12cb4 + 09c1ddd commit 7822ca4

File tree

17 files changed

+208
-23
lines changed

17 files changed

+208
-23
lines changed

.github/workflows/pr-linters.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Run PR linters
2+
3+
on:
4+
pull_request:
5+
workflow_dispatch:
6+
7+
permissions:
8+
contents: read
9+
pull-requests: read
10+
11+
jobs:
12+
13+
mypy:
14+
name: Run mypy static type checker (optional)
15+
runs-on: ubuntu-latest
16+
continue-on-error: true
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: actions/setup-python@v5
20+
with:
21+
python-version: 3.12
22+
cache: pip
23+
cache-dependency-path: |
24+
'pyproject.toml'
25+
'requirements-dev.txt'
26+
- run: pip install -r requirements-dev.txt -e .
27+
- name: Run mypy and report
28+
run: mypy --config-file pyproject.toml .

.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

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/network.py

Lines changed: 6 additions & 6 deletions
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.
@@ -392,7 +392,9 @@ class NodeScanner:
392392
SERVICES = (0x700, 0x580, 0x180, 0x280, 0x380, 0x480, 0x80)
393393

394394
def __init__(self, network: Optional[Network] = None):
395-
self.network = network
395+
if network is None:
396+
network = _UNINITIALIZED_NETWORK
397+
self.network: Network = network
396398
#: A :class:`list` of nodes discovered
397399
self.nodes: List[int] = []
398400

@@ -408,8 +410,6 @@ def reset(self):
408410

409411
def search(self, limit: int = 127) -> None:
410412
"""Search for nodes by sending SDO requests to all node IDs."""
411-
if self.network is None:
412-
raise RuntimeError("A Network is required to do active scanning")
413413
sdo_req = b"\x40\x00\x10\x00\x00\x00\x00\x00"
414414
for node_id in range(1, limit + 1):
415415
self.network.send_message(0x600 + node_id, sdo_req)

canopen/nmt.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import struct
33
import threading
44
import time
5-
from typing import Callable, Optional, TYPE_CHECKING
5+
from typing import Callable, Dict, Final, List, Optional, TYPE_CHECKING
66

77
import canopen.network
88

@@ -12,7 +12,7 @@
1212

1313
logger = logging.getLogger(__name__)
1414

15-
NMT_STATES = {
15+
NMT_STATES: Final[Dict[int, str]] = {
1616
0: 'INITIALISING',
1717
4: 'STOPPED',
1818
5: 'OPERATIONAL',
@@ -21,7 +21,7 @@
2121
127: 'PRE-OPERATIONAL'
2222
}
2323

24-
NMT_COMMANDS = {
24+
NMT_COMMANDS: Final[Dict[str, int]] = {
2525
'OPERATIONAL': 1,
2626
'STOPPED': 2,
2727
'SLEEP': 80,
@@ -32,7 +32,7 @@
3232
'RESET COMMUNICATION': 130
3333
}
3434

35-
COMMAND_TO_STATE = {
35+
COMMAND_TO_STATE: Final[Dict[int, int]] = {
3636
1: 5,
3737
2: 4,
3838
80: 80,
@@ -117,7 +117,7 @@ def __init__(self, node_id: int):
117117
#: Timestamp of last heartbeat message
118118
self.timestamp: Optional[float] = None
119119
self.state_update = threading.Condition()
120-
self._callbacks = []
120+
self._callbacks: List[Callable[[int], None]] = []
121121

122122
def on_heartbeat(self, can_id, data, timestamp):
123123
with self.state_update:
@@ -186,7 +186,8 @@ def start_node_guarding(self, period: float):
186186
:param period:
187187
Period (in seconds) at which the node guarding should be advertised to the slave node.
188188
"""
189-
if self._node_guarding_producer : self.stop_node_guarding()
189+
if self._node_guarding_producer:
190+
self.stop_node_guarding()
190191
self._node_guarding_producer = self.network.send_periodic(0x700 + self.id, None, period, True)
191192

192193
def stop_node_guarding(self):

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
@@ -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)

canopen/objectdictionary/eds.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1+
from __future__ import annotations
2+
13
import copy
24
import logging
35
import re
46
from configparser import NoOptionError, NoSectionError, RawConfigParser
7+
from typing import TYPE_CHECKING
58

69
from canopen import objectdictionary
710
from canopen.objectdictionary import ObjectDictionary, datatypes
811
from canopen.sdo import SdoClient
912

13+
if TYPE_CHECKING:
14+
import canopen.network
15+
1016

1117
logger = logging.getLogger(__name__)
1218

@@ -173,7 +179,7 @@ def import_eds(source, node_id):
173179
return od
174180

175181

176-
def import_from_node(node_id, network):
182+
def import_from_node(node_id: int, network: canopen.network.Network):
177183
""" Download the configuration from the remote node
178184
:param int node_id: Identifier of the node
179185
:param network: network object

canopen/sdo/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ def __init__(self, sdo_client, index, subindex=0, size=None, force_segment=False
353353
self._exp_header = None
354354
self._done = False
355355

356-
if size is None or size > 4 or force_segment:
356+
if size is None or size < 1 or size > 4 or force_segment:
357357
# Initiate segmented download
358358
request = bytearray(8)
359359
command = REQUEST_DOWNLOAD

canopen/sync.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
from typing import Optional
1+
from __future__ import annotations
2+
3+
from typing import Optional, TYPE_CHECKING
4+
5+
if TYPE_CHECKING:
6+
import canopen.network
27

38

49
class SyncProducer:
@@ -7,7 +12,7 @@ class SyncProducer:
712
#: COB-ID of the SYNC message
813
cob_id = 0x80
914

10-
def __init__(self, network):
15+
def __init__(self, network: canopen.network.Network):
1116
self.network = network
1217
self.period: Optional[float] = None
1318
self._task = None

0 commit comments

Comments
 (0)