Skip to content

Commit 120ae99

Browse files
committed
Merge branch 'master' into pdo-getitem-zero
2 parents 383a017 + be9c56d commit 120ae99

12 files changed

Lines changed: 80 additions & 55 deletions

File tree

README.rst

Lines changed: 1 addition & 1 deletion
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

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/pdo/base.py

Lines changed: 4 additions & 4 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
@@ -154,7 +154,7 @@ def __init__(self, com_offset, map_offset, pdo_node: PdoBase, cob_base=None):
154154
:param pdo_node:
155155
:param cob_base:
156156
"""
157-
self.maps: Dict[int, PdoMap] = {}
157+
self.maps: dict[int, PdoMap] = {}
158158
for map_no in range(512):
159159
if com_offset + map_no in pdo_node.node.object_dictionary:
160160
new_map = PdoMap(
@@ -200,7 +200,7 @@ def __init__(self, pdo_node, com_record, map_array):
200200
#: Ignores SYNC objects up to this SYNC counter value (optional)
201201
self.sync_start_value: Optional[int] = None
202202
#: List of variables mapped to this PDO
203-
self.map: List[PdoVariable] = []
203+
self.map: list[PdoVariable] = []
204204
self.length: int = 0
205205
#: Current message data
206206
self.data = bytearray()

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: 14 additions & 3 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
@@ -148,7 +148,18 @@ def __init__(self, sdo_node: SdoBase, od: objectdictionary.ODVariable):
148148
variable.Variable.__init__(self, od)
149149

150150
def get_data(self) -> bytes:
151-
return self.sdo_node.upload(self.od.index, self.od.subindex)
151+
data = self.sdo_node.upload(self.od.index, self.od.subindex)
152+
response_size = len(data)
153+
154+
# If size is available through variable in OD, then use the smaller of the two sizes.
155+
# Some devices send U32/I32 even if variable is smaller in OD
156+
if self.od.fixed_size:
157+
# Get the size in bytes for this variable
158+
var_size = len(self.od) // 8
159+
if response_size is None or var_size < response_size:
160+
# Truncate the data to specified size
161+
data = data[:var_size]
162+
return data
152163

153164
def set_data(self, data: bytes):
154165
force_segment = self.od.data_type == objectdictionary.DOMAIN

canopen/sdo/client.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ def abort(self, abort_code=ABORT_GENERAL_ERROR):
112112
def upload(self, index: int, subindex: int) -> bytes:
113113
"""May be called to make a read operation without an Object Dictionary.
114114
115+
No validation against the Object Dictionary is performed, even if an object description
116+
would be available. The length of the returned data depends only on the transferred
117+
amount, possibly truncated to the size indicated by the server.
118+
115119
:param index:
116120
Index of object to read.
117121
:param subindex:
@@ -128,17 +132,8 @@ def upload(self, index: int, subindex: int) -> bytes:
128132
response_size = fp.size
129133
data = fp.read()
130134

131-
# If size is available through variable in OD, then use the smaller of the two sizes.
132-
# Some devices send U32/I32 even if variable is smaller in OD
133-
var = self.od.get_variable(index, subindex)
134-
if var is not None:
135-
# Found a matching variable in OD
136-
if var.fixed_size:
137-
# Get the size in bytes for this variable
138-
var_size = len(var) // 8
139-
if response_size is None or var_size < response_size:
140-
# Truncate the data to specified size
141-
data = data[0:var_size]
135+
if response_size and response_size < len(data):
136+
data = data[:response_size]
142137
return data
143138

144139
def download(

0 commit comments

Comments
 (0)