Skip to content

Commit 9c365a7

Browse files
committed
Merge branch 'master' into sdo-upload-disregard-od
2 parents b431e91 + 1582ba5 commit 9c365a7

File tree

16 files changed

+188
-91
lines changed

16 files changed

+188
-91
lines changed

README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ The aim of the project is to support the most common parts of the CiA 301
66
standard in a simple Pythonic interface. It is mainly targeted for testing and
77
automation tasks rather than a standard compliant master implementation.
88

9-
The library supports Python 3.8 or newer.
9+
The library supports Python 3.9 or newer.
1010

1111

1212
Features
@@ -36,7 +36,7 @@ Incomplete support for creating slave nodes also exists.
3636
Installation
3737
------------
3838

39-
Install from PyPI_ using :program:`pip`::
39+
Install from PyPI_ using ``pip``::
4040

4141
$ pip install canopen
4242

@@ -56,7 +56,7 @@ Unit tests can be run using the pytest_ framework::
5656
$ pip install -r requirements-dev.txt
5757
$ pytest -v
5858

59-
You can also use :mod:`unittest` standard library module::
59+
You can also use ``unittest`` standard library module::
6060

6161
$ python3 -m unittest discover test -v
6262

canopen/emcy.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
from __future__ import annotations
2+
13
import logging
24
import struct
35
import threading
46
import time
5-
from typing import Callable, List, Optional
7+
from typing import Callable, Optional
68

79
import canopen.network
810

@@ -17,9 +19,9 @@ class EmcyConsumer:
1719

1820
def __init__(self):
1921
#: Log of all received EMCYs for this node
20-
self.log: List["EmcyError"] = []
22+
self.log: list[EmcyError] = []
2123
#: Only active EMCYs. Will be cleared on Error Reset
22-
self.active: List["EmcyError"] = []
24+
self.active: list[EmcyError] = []
2325
self.callbacks = []
2426
self.emcy_received = threading.Condition()
2527

@@ -39,7 +41,7 @@ def on_emcy(self, can_id, data, timestamp):
3941
for callback in self.callbacks:
4042
callback(entry)
4143

42-
def add_callback(self, callback: Callable[["EmcyError"], None]):
44+
def add_callback(self, callback: Callable[[EmcyError], None]):
4345
"""Get notified on EMCY messages from this node.
4446
4547
:param callback:
@@ -55,7 +57,7 @@ def reset(self):
5557

5658
def wait(
5759
self, emcy_code: Optional[int] = None, timeout: float = 10
58-
) -> "EmcyError":
60+
) -> EmcyError:
5961
"""Wait for a new EMCY to arrive.
6062
6163
:param emcy_code: EMCY code to wait for

canopen/network.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
import logging
44
import threading
5-
from collections.abc import MutableMapping
6-
from typing import Callable, Dict, Final, Iterator, List, Optional, Union
5+
from collections.abc import Iterator, MutableMapping
6+
from typing import Callable, Final, Optional, Union
77

88
import can
9-
from can import Listener
109

1110
from canopen.lss import LssMaster
1211
from canopen.nmt import NmtMaster
@@ -40,10 +39,10 @@ def __init__(self, bus: Optional[can.BusABC] = None):
4039
self.scanner = NodeScanner(self)
4140
#: List of :class:`can.Listener` objects.
4241
#: Includes at least MessageListener.
43-
self.listeners = [MessageListener(self)]
42+
self.listeners: list[can.Listener] = [MessageListener(self)]
4443
self.notifier: Optional[can.Notifier] = None
45-
self.nodes: Dict[int, Union[RemoteNode, LocalNode]] = {}
46-
self.subscribers: Dict[int, List[Callback]] = {}
44+
self.nodes: dict[int, Union[RemoteNode, LocalNode]] = {}
45+
self.subscribers: dict[int, list[Callback]] = {}
4746
self.send_lock = threading.Lock()
4847
self.sync = SyncProducer(self)
4948
self.time = TimeProducer(self)
@@ -352,7 +351,7 @@ def update(self, data: bytes) -> None:
352351
self._start()
353352

354353

355-
class MessageListener(Listener):
354+
class MessageListener(can.Listener):
356355
"""Listens for messages on CAN bus and feeds them to a Network instance.
357356
358357
:param network:
@@ -396,7 +395,7 @@ def __init__(self, network: Optional[Network] = None):
396395
network = _UNINITIALIZED_NETWORK
397396
self.network: Network = network
398397
#: A :class:`list` of nodes discovered
399-
self.nodes: List[int] = []
398+
self.nodes: list[int] = []
400399

401400
def on_message_received(self, can_id: int):
402401
service = can_id & 0x780

canopen/nmt.py

Lines changed: 5 additions & 5 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, Dict, Final, List, Optional, TYPE_CHECKING
5+
from typing import Callable, Final, Optional, TYPE_CHECKING
66

77
import canopen.network
88

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

1313
logger = logging.getLogger(__name__)
1414

15-
NMT_STATES: Final[Dict[int, str]] = {
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: Final[Dict[str, int]] = {
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: Final[Dict[int, int]] = {
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: List[Callable[[int], None]] = []
120+
self._callbacks: list[Callable[[int], None]] = []
121121

122122
def on_heartbeat(self, can_id, data, timestamp):
123123
with self.state_update:

canopen/node/local.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import logging
4-
from typing import Dict, Union
4+
from typing import Union
55

66
import canopen.network
77
from canopen import objectdictionary
@@ -25,7 +25,7 @@ def __init__(
2525
):
2626
super(LocalNode, self).__init__(node_id, object_dictionary)
2727

28-
self.data_store: Dict[int, Dict[int, bytes]] = {}
28+
self.data_store: dict[int, dict[int, bytes]] = {}
2929
self._read_callbacks = []
3030
self._write_callbacks = []
3131

canopen/objectdictionary/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
import logging
88
import struct
9-
from collections.abc import Mapping, MutableMapping
10-
from typing import Dict, Iterator, List, Optional, TextIO, Union
9+
from collections.abc import Iterator, Mapping, MutableMapping
10+
from typing import Optional, TextIO, Union
1111

1212
from canopen.objectdictionary.datatypes import *
1313
from canopen.objectdictionary.datatypes import IntegerN, UnsignedN
@@ -367,9 +367,9 @@ def __init__(self, name: str, index: int, subindex: int = 0):
367367
#: Description of variable
368368
self.description: str = ""
369369
#: Dictionary of value descriptions
370-
self.value_descriptions: Dict[int, str] = {}
370+
self.value_descriptions: dict[int, str] = {}
371371
#: Dictionary of bitfield definitions
372-
self.bit_definitions: Dict[str, List[int]] = {}
372+
self.bit_definitions: dict[str, list[int]] = {}
373373
#: Storage location of index
374374
self.storage_location = None
375375
#: Can this variable be mapped to a PDO

canopen/objectdictionary/eds.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ def export_record(var, eds):
473473
for rate in od.device_information.allowed_baudrates.union(
474474
{10e3, 20e3, 50e3, 125e3, 250e3, 500e3, 800e3, 1000e3}):
475475
eds.set(
476-
"DeviceInfo", f"BaudRate_{rate//1000}",
476+
"DeviceInfo", f"BaudRate_{int(rate//1000)}",
477477
int(rate in od.device_information.allowed_baudrates))
478478

479479
if device_commisioning and (od.bitrate or od.node_id):

canopen/pdo/base.py

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import logging
55
import math
66
import threading
7-
from collections.abc import Mapping
8-
from typing import Callable, Dict, Iterator, List, Optional, TYPE_CHECKING, Union
7+
from collections.abc import Iterator, Mapping
8+
from typing import Callable, Optional, TYPE_CHECKING, Union
99

1010
import canopen.network
1111
from canopen import objectdictionary
@@ -150,7 +150,7 @@ def __init__(self, com_offset, map_offset, pdo_node: PdoBase, cob_base=None):
150150
:param pdo_node:
151151
:param cob_base:
152152
"""
153-
self.maps: Dict[int, PdoMap] = {}
153+
self.maps: dict[int, PdoMap] = {}
154154
for map_no in range(512):
155155
if com_offset + map_no in pdo_node.node.object_dictionary:
156156
new_map = PdoMap(
@@ -196,7 +196,7 @@ def __init__(self, pdo_node, com_record, map_array):
196196
#: Ignores SYNC objects up to this SYNC counter value (optional)
197197
self.sync_start_value: Optional[int] = None
198198
#: List of variables mapped to this PDO
199-
self.map: List[PdoVariable] = []
199+
self.map: list[PdoVariable] = []
200200
self.length: int = 0
201201
#: Current message data
202202
self.data = bytearray()
@@ -403,19 +403,27 @@ def save(self) -> None:
403403
logger.info("Skip saving %s: COB-ID was never set", self.com_record.od.name)
404404
return
405405
logger.info("Setting COB-ID 0x%X and temporarily disabling PDO", self.cob_id)
406-
self.com_record[1].raw = self.cob_id | PDO_NOT_VALID | (RTR_NOT_ALLOWED if not self.rtr_allowed else 0x0)
407-
if self.trans_type is not None:
408-
logger.info("Setting transmission type to %d", self.trans_type)
409-
self.com_record[2].raw = self.trans_type
410-
if self.inhibit_time is not None:
411-
logger.info("Setting inhibit time to %d us", (self.inhibit_time * 100))
412-
self.com_record[3].raw = self.inhibit_time
413-
if self.event_timer is not None:
414-
logger.info("Setting event timer to %d ms", self.event_timer)
415-
self.com_record[5].raw = self.event_timer
416-
if self.sync_start_value is not None:
417-
logger.info("Setting SYNC start value to %d", self.sync_start_value)
418-
self.com_record[6].raw = self.sync_start_value
406+
self.com_record[1].raw = (
407+
self.cob_id
408+
| PDO_NOT_VALID
409+
| (RTR_NOT_ALLOWED if not self.rtr_allowed else 0)
410+
)
411+
412+
def _set_com_record(
413+
subindex: int, value: Optional[int], log_fmt: str, log_factor: int = 1
414+
):
415+
if value is None:
416+
return
417+
if self.com_record[subindex].writable:
418+
logger.info(f"Setting {log_fmt}", value * log_factor)
419+
self.com_record[subindex].raw = value
420+
else:
421+
logger.info(f"Cannot set {log_fmt}, not writable", value * log_factor)
422+
423+
_set_com_record(2, self.trans_type, "transmission type to %d")
424+
_set_com_record(3, self.inhibit_time, "inhibit time to %d us", 100)
425+
_set_com_record(5, self.event_timer, "event timer to %d ms")
426+
_set_com_record(6, self.sync_start_value, "SYNC start value to %d")
419427

420428
try:
421429
self.map_array[0].raw = 0
@@ -425,20 +433,21 @@ def save(self) -> None:
425433
# mappings for an invalid object 0x0000:00 to overwrite any
426434
# excess entries with all-zeros.
427435
self._fill_map(self.map_array[0].raw)
428-
subindex = 1
429-
for var in self.map:
430-
logger.info("Writing %s (0x%04X:%02X, %d bits) to PDO map",
431-
var.name, var.index, var.subindex, var.length)
436+
for var, entry in zip(self.map, self.map_array.values()):
437+
if not entry.od.writable:
438+
continue
439+
logger.info(
440+
"Writing %s (0x%04X:%02X, %d bits) to PDO map",
441+
var.name,
442+
var.index,
443+
var.subindex,
444+
var.length,
445+
)
432446
if getattr(self.pdo_node.node, "curtis_hack", False):
433447
# Curtis HACK: mixed up field order
434-
self.map_array[subindex].raw = (var.index |
435-
var.subindex << 16 |
436-
var.length << 24)
448+
entry.raw = var.index | var.subindex << 16 | var.length << 24
437449
else:
438-
self.map_array[subindex].raw = (var.index << 16 |
439-
var.subindex << 8 |
440-
var.length)
441-
subindex += 1
450+
entry.raw = var.index << 16 | var.subindex << 8 | var.length
442451
try:
443452
self.map_array[0].raw = len(self.map)
444453
except SdoAbortedError as e:

canopen/profiles/p402.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# inspired by the NmtMaster code
22
import logging
33
import time
4-
from typing import Dict
54

65
from canopen.node import RemoteNode
76
from canopen.pdo import PdoMap
@@ -215,8 +214,8 @@ class BaseNode402(RemoteNode):
215214
def __init__(self, node_id, object_dictionary):
216215
super(BaseNode402, self).__init__(node_id, object_dictionary)
217216
self.tpdo_values = {} # { index: value from last received TPDO }
218-
self.tpdo_pointers: Dict[int, PdoMap] = {}
219-
self.rpdo_pointers: Dict[int, PdoMap] = {}
217+
self.tpdo_pointers: dict[int, PdoMap] = {}
218+
self.rpdo_pointers: dict[int, PdoMap] = {}
220219

221220
def setup_402_state_machine(self, read_pdos=True):
222221
"""Configure the state machine by searching for a TPDO that has the StatusWord mapped.

canopen/sdo/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from __future__ import annotations
22

33
import binascii
4-
from collections.abc import Mapping
5-
from typing import Iterator, Optional, Union
4+
from collections.abc import Iterator, Mapping
5+
from typing import Optional, Union
66

77
import canopen.network
88
from canopen import objectdictionary

0 commit comments

Comments
 (0)