Skip to content

Commit e9ef593

Browse files
committed
Merge 'master' into feature-asyncio
2 parents 751f854 + 7ddb19b commit e9ef593

File tree

12 files changed

+399
-26
lines changed

12 files changed

+399
-26
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 .

canopen/network.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,9 @@ class NodeScanner:
468468
SERVICES = (0x700, 0x580, 0x180, 0x280, 0x380, 0x480, 0x80)
469469

470470
def __init__(self, network: Optional[Network] = None):
471-
self.network = network
471+
if network is None:
472+
network = _UNINITIALIZED_NETWORK
473+
self.network: Network = network
472474
#: A :class:`list` of nodes discovered
473475
self.nodes: List[int] = []
474476

@@ -477,10 +479,6 @@ def on_message_received(self, can_id: int):
477479
service = can_id & 0x780
478480
node_id = can_id & 0x7F
479481
if node_id not in self.nodes and node_id != 0 and service in self.SERVICES:
480-
# NOTE: In the current CPython implementation append on lists are
481-
# atomic which makes this thread-safe. However, other py
482-
# interpreters might not. It should be considered if a better
483-
# mechanism is needed to protect against race.
484482
self.nodes.append(node_id)
485483

486484
def reset(self):
@@ -489,9 +487,6 @@ def reset(self):
489487

490488
def search(self, limit: int = 127) -> None:
491489
"""Search for nodes by sending SDO requests to all node IDs."""
492-
if self.network is None:
493-
raise RuntimeError("A Network is required to do active scanning")
494-
# SDO upload request, parameter 0x1000:0x00
495490
sdo_req = b"\x40\x00\x10\x00\x00\x00\x00\x00"
496491
for node_id in range(1, limit + 1):
497492
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
@@ -3,7 +3,7 @@
33
import struct
44
import threading
55
import time
6-
from typing import Callable, Optional, TYPE_CHECKING
6+
from typing import Callable, Dict, Final, List, Optional, TYPE_CHECKING
77

88
from canopen.async_guard import ensure_not_async
99
import canopen.network
@@ -14,7 +14,7 @@
1414

1515
logger = logging.getLogger(__name__)
1616

17-
NMT_STATES = {
17+
NMT_STATES: Final[Dict[int, str]] = {
1818
0: 'INITIALISING',
1919
4: 'STOPPED',
2020
5: 'OPERATIONAL',
@@ -23,7 +23,7 @@
2323
127: 'PRE-OPERATIONAL'
2424
}
2525

26-
NMT_COMMANDS = {
26+
NMT_COMMANDS: Final[Dict[str, int]] = {
2727
'OPERATIONAL': 1,
2828
'STOPPED': 2,
2929
'SLEEP': 80,
@@ -34,7 +34,7 @@
3434
'RESET COMMUNICATION': 130
3535
}
3636

37-
COMMAND_TO_STATE = {
37+
COMMAND_TO_STATE: Final[Dict[int, int]] = {
3838
1: 5,
3939
2: 4,
4040
80: 80,
@@ -121,7 +121,7 @@ def __init__(self, node_id: int):
121121
#: Timestamp of last heartbeat message
122122
self.timestamp: Optional[float] = None
123123
self.state_update = threading.Condition()
124-
self._callbacks = []
124+
self._callbacks: List[Callable[[int], None]] = []
125125

126126
# @callback # NOTE: called from another thread
127127
@ensure_not_async # NOTE: Safeguard for accidental async use
@@ -209,7 +209,8 @@ def start_node_guarding(self, period: float):
209209
:param period:
210210
Period (in seconds) at which the node guarding should be advertised to the slave node.
211211
"""
212-
if self._node_guarding_producer : self.stop_node_guarding()
212+
if self._node_guarding_producer:
213+
self.stop_node_guarding()
213214
self._node_guarding_producer = self.network.send_periodic(0x700 + self.id, None, period, True)
214215

215216
def stop_node_guarding(self):

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

@@ -174,7 +180,7 @@ def import_eds(source, node_id):
174180

175181

176182
# FIXME: Make async variant "aimport_from_node"
177-
def import_from_node(node_id, network):
183+
def import_from_node(node_id: int, network: canopen.network.Network):
178184
""" Download the configuration from the remote node
179185
:param int node_id: Identifier of the node
180186
:param network: network object

canopen/sdo/client.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -634,8 +634,7 @@ def _ack_block(self):
634634
request[1] = self._ackseq
635635
request[2] = self.blksize
636636
self.sdo_client.send_request(request)
637-
if self._ackseq == self.blksize:
638-
self._ackseq = 0
637+
self._ackseq = 0
639638

640639
def _end_upload(self):
641640
response = self.sdo_client.read_response()

canopen/sync.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from __future__ import annotations
2+
23
from typing import Optional, TYPE_CHECKING
34

45
if TYPE_CHECKING:
5-
from canopen.network import Network
6+
import canopen.network
67

78

89
class SyncProducer:
@@ -11,7 +12,7 @@ class SyncProducer:
1112
#: COB-ID of the SYNC message
1213
cob_id = 0x80
1314

14-
def __init__(self, network: Network):
15+
def __init__(self, network: canopen.network.Network):
1516
self.network = network
1617
self.period: Optional[float] = None
1718
self._task = None

canopen/timestamp.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from __future__ import annotations
2+
23
import struct
34
import time
45
from typing import Optional, TYPE_CHECKING
56

67
if TYPE_CHECKING:
7-
from canopen.network import Network
8+
import canopen.network
89

910

1011
# 1 Jan 1984
@@ -21,7 +22,7 @@ class TimeProducer:
2122
#: COB-ID of the SYNC message
2223
cob_id = 0x100
2324

24-
def __init__(self, network: Network):
25+
def __init__(self, network: canopen.network.Network):
2526
self.network = network
2627

2728
def transmit(self, timestamp: Optional[float] = None):
@@ -30,7 +31,7 @@ def transmit(self, timestamp: Optional[float] = None):
3031
:param float timestamp:
3132
Optional Unix timestamp to use, otherwise the current time is used.
3233
"""
33-
delta = timestamp or time.time() - OFFSET
34+
delta = (timestamp or time.time()) - OFFSET
3435
days, seconds = divmod(delta, ONE_DAY)
3536
data = TIME_OF_DAY_STRUCT.pack(int(seconds * 1000), int(days))
3637
self.network.send_message(self.cob_id, data)

pyproject.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,11 @@ testpaths = [
4848
filterwarnings = [
4949
"ignore::DeprecationWarning",
5050
]
51+
52+
[tool.mypy]
53+
python_version = "3.8"
54+
exclude = [
55+
"^examples*",
56+
"^test*",
57+
"^setup.py*",
58+
]

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
mypy~=1.10
12
pytest~=8.3
23
pytest-cov~=5.0

test/test_network.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ async def test_scanner_reset(self):
384384
self.assertListEqual(self.scanner.nodes, [])
385385

386386
async def test_scanner_search_no_network(self):
387-
with self.assertRaisesRegex(RuntimeError, "Network is required"):
387+
with self.assertRaisesRegex(RuntimeError, "No actual Network object was assigned"):
388388
self.scanner.search()
389389

390390
async def test_scanner_search(self):

0 commit comments

Comments
 (0)