Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9df972c

Browse files
sveinseacolomb
andauthored
Implement the remaining canopen datatypes (#440)
* Implement the remaining canopen datatypes * Add datatype defs for the canopen standard types * Move datatypes_24.py classes to datatypes.py * Replace Unsigned24 and Interger24 by generic UnsignedN and IntegerN respectively * Add EDS-file containing all datatypes * Added tests for encoding and decoding all datatypes * Added tests for SDO uploads of all datatypes * Annotate type hint for STRUCT_TYPES listing. * Disable failing tests waiting on a fix for #436. Co-authored-by: André Colomb <[email protected]>
1 parent 599f4ac commit 9df972c

File tree

6 files changed

+854
-43
lines changed

6 files changed

+854
-43
lines changed

canopen/objectdictionary/__init__.py

+19-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import logging
1010

1111
from canopen.objectdictionary.datatypes import *
12-
from canopen.objectdictionary.datatypes_24bit import Integer24, Unsigned24
1312
from canopen.utils import pretty_index
1413

1514
logger = logging.getLogger(__name__)
@@ -282,17 +281,25 @@ def add_member(self, variable: ODVariable) -> None:
282281
class ODVariable:
283282
"""Simple variable."""
284283

285-
STRUCT_TYPES = {
284+
STRUCT_TYPES: dict[int, struct.Struct] = {
285+
# Use struct module to pack/unpack data where possible and use the
286+
# custom IntegerN and UnsignedN classes for the special data types.
286287
BOOLEAN: struct.Struct("?"),
287288
INTEGER8: struct.Struct("b"),
288289
INTEGER16: struct.Struct("<h"),
289-
INTEGER24: Integer24(),
290+
INTEGER24: IntegerN(24),
290291
INTEGER32: struct.Struct("<l"),
292+
INTEGER40: IntegerN(40),
293+
INTEGER48: IntegerN(48),
294+
INTEGER56: IntegerN(56),
291295
INTEGER64: struct.Struct("<q"),
292296
UNSIGNED8: struct.Struct("B"),
293297
UNSIGNED16: struct.Struct("<H"),
294-
UNSIGNED24: Unsigned24(),
298+
UNSIGNED24: UnsignedN(24),
295299
UNSIGNED32: struct.Struct("<L"),
300+
UNSIGNED40: UnsignedN(40),
301+
UNSIGNED48: UnsignedN(48),
302+
UNSIGNED56: UnsignedN(56),
296303
UNSIGNED64: struct.Struct("<Q"),
297304
REAL32: struct.Struct("<f"),
298305
REAL64: struct.Struct("<d")
@@ -386,10 +393,13 @@ def add_bit_definition(self, name: str, bits: List[int]) -> None:
386393

387394
def decode_raw(self, data: bytes) -> Union[int, float, str, bytes, bytearray]:
388395
if self.data_type == VISIBLE_STRING:
389-
return data.rstrip(b"\x00").decode("ascii", errors="ignore")
396+
# Strip any trailing NUL characters from C-based systems
397+
return data.decode("ascii", errors="ignore").rstrip("\x00")
390398
elif self.data_type == UNICODE_STRING:
391-
# Is this correct?
392-
return data.rstrip(b"\x00").decode("utf_16_le", errors="ignore")
399+
# The CANopen standard does not specify the encoding. This
400+
# library assumes UTF-16, being the most common two-byte encoding format.
401+
# Strip any trailing NUL characters from C-based systems
402+
return data.decode("utf_16_le", errors="ignore").rstrip("\x00")
393403
elif self.data_type in self.STRUCT_TYPES:
394404
try:
395405
value, = self.STRUCT_TYPES[self.data_type].unpack(data)
@@ -407,8 +417,9 @@ def encode_raw(self, value: Union[int, float, str, bytes, bytearray]) -> bytes:
407417
elif self.data_type == VISIBLE_STRING:
408418
return value.encode("ascii")
409419
elif self.data_type == UNICODE_STRING:
410-
# Is this correct?
411420
return value.encode("utf_16_le")
421+
elif self.data_type in (DOMAIN, OCTET_STRING):
422+
return bytes(value)
412423
elif self.data_type in self.STRUCT_TYPES:
413424
if self.data_type in INTEGER_TYPES:
414425
value = int(value)

canopen/objectdictionary/datatypes.py

+103-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import struct
12

23
BOOLEAN = 0x1
34
INTEGER8 = 0x2
@@ -10,16 +11,116 @@
1011
VISIBLE_STRING = 0x9
1112
OCTET_STRING = 0xA
1213
UNICODE_STRING = 0xB
14+
TIME_OF_DAY = 0xC
15+
TIME_DIFFERENCE = 0xD
1316
DOMAIN = 0xF
1417
INTEGER24 = 0x10
1518
REAL64 = 0x11
19+
INTEGER40 = 0x12
20+
INTEGER48 = 0x13
21+
INTEGER56 = 0x14
1622
INTEGER64 = 0x15
1723
UNSIGNED24 = 0x16
24+
UNSIGNED40 = 0x18
25+
UNSIGNED48 = 0x19
26+
UNSIGNED56 = 0x1A
1827
UNSIGNED64 = 0x1B
28+
PDO_COMMUNICATION_PARAMETER = 0x20
29+
PDO_MAPPING = 0x21
30+
SDO_PARAMETER = 0x22
31+
IDENTITY = 0x23
1932

20-
SIGNED_TYPES = (INTEGER8, INTEGER16, INTEGER24, INTEGER32, INTEGER64)
21-
UNSIGNED_TYPES = (UNSIGNED8, UNSIGNED16, UNSIGNED24, UNSIGNED32, UNSIGNED64)
33+
SIGNED_TYPES = (
34+
INTEGER8,
35+
INTEGER16,
36+
INTEGER24,
37+
INTEGER32,
38+
INTEGER40,
39+
INTEGER48,
40+
INTEGER56,
41+
INTEGER64,
42+
)
43+
UNSIGNED_TYPES = (
44+
UNSIGNED8,
45+
UNSIGNED16,
46+
UNSIGNED24,
47+
UNSIGNED32,
48+
UNSIGNED40,
49+
UNSIGNED48,
50+
UNSIGNED56,
51+
UNSIGNED64,
52+
)
2253
INTEGER_TYPES = SIGNED_TYPES + UNSIGNED_TYPES
2354
FLOAT_TYPES = (REAL32, REAL64)
2455
NUMBER_TYPES = INTEGER_TYPES + FLOAT_TYPES
2556
DATA_TYPES = (VISIBLE_STRING, OCTET_STRING, UNICODE_STRING, DOMAIN)
57+
58+
59+
class UnsignedN(struct.Struct):
60+
"""Packing and unpacking unsigned integers of arbitrary width, like struct.Struct.
61+
62+
The width must be a multiple of 8 and must be between 8 and 64.
63+
"""
64+
65+
def __init__(self, width: int):
66+
self.width = width
67+
if width % 8 != 0:
68+
raise ValueError("Width must be a multiple of 8")
69+
if width <= 0 or width > 64:
70+
raise ValueError("Invalid width for UnsignedN")
71+
elif width <= 8:
72+
fmt = "B"
73+
elif width <= 16:
74+
fmt = "<H"
75+
elif width <= 32:
76+
fmt = "<L"
77+
else:
78+
fmt = "<Q"
79+
super().__init__(fmt)
80+
81+
def unpack(self, buffer):
82+
return super().unpack(buffer + b'\x00' * (super().size - self.size))
83+
84+
def pack(self, *v):
85+
return super().pack(*v)[:self.size]
86+
87+
@property
88+
def size(self) -> int:
89+
return self.width // 8
90+
91+
92+
class IntegerN(struct.Struct):
93+
"""Packing and unpacking integers of arbitrary width, like struct.Struct.
94+
95+
The width must be a multiple of 8 and must be between 8 and 64.
96+
"""
97+
98+
def __init__(self, width: int):
99+
self.width = width
100+
if width % 8 != 0:
101+
raise ValueError("Width must be a multiple of 8")
102+
if width <= 0 or width > 64:
103+
raise ValueError("Invalid width for IntegerN")
104+
elif width <= 8:
105+
fmt = "b"
106+
elif width <= 16:
107+
fmt = "<h"
108+
elif width <= 32:
109+
fmt = "<l"
110+
else:
111+
fmt = "<q"
112+
super().__init__(fmt)
113+
114+
def unpack(self, buffer):
115+
mask = 0x80
116+
neg = (buffer[self.size - 1] & mask) > 0
117+
return super().unpack(
118+
buffer + (b'\xff' if neg else b'\x00') * (super().size - self.size)
119+
)
120+
121+
def pack(self, *v):
122+
return super().pack(*v)[:self.size]
123+
124+
@property
125+
def size(self) -> int:
126+
return self.width // 8

canopen/objectdictionary/datatypes_24bit.py

-33
This file was deleted.

0 commit comments

Comments
 (0)