Skip to content

Commit fd226e3

Browse files
Fixed HighLimit and LowLimit for SIGNED values in EDS (#345)
* Fixed incorrect min (LowLImit) and max (HighLimit) values when using signed integer types. * Fixed min/max for non-signed datatypes and added tests * Removed type hints
1 parent 52fbafb commit fd226e3

File tree

3 files changed

+128
-42
lines changed

3 files changed

+128
-42
lines changed

canopen/objectdictionary/eds.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import re
2-
import io
3-
import logging
41
import copy
2+
import logging
3+
import re
4+
5+
from canopen.objectdictionary import datatypes
6+
57
try:
68
from configparser import RawConfigParser, NoOptionError, NoSectionError
79
except ImportError:
@@ -190,6 +192,28 @@ def import_from_node(node_id, network):
190192
return od
191193

192194

195+
def _calc_bit_length(data_type):
196+
if data_type == datatypes.INTEGER8:
197+
return 8
198+
elif data_type == datatypes.INTEGER16:
199+
return 16
200+
elif data_type == datatypes.INTEGER32:
201+
return 32
202+
elif data_type == datatypes.INTEGER64:
203+
return 64
204+
else:
205+
raise ValueError(f"Invalid data_type '{data_type}', expecting a signed integer data_type.")
206+
207+
208+
def _signed_int_from_hex(hex_str, bit_length):
209+
number = int(hex_str, 0)
210+
limit = ((1 << bit_length - 1) - 1)
211+
if number > limit:
212+
return limit - number
213+
else:
214+
return number
215+
216+
193217
def _convert_variable(node_id, var_type, value):
194218
if var_type in (objectdictionary.OCTET_STRING, objectdictionary.DOMAIN):
195219
return bytes.fromhex(value)
@@ -251,12 +275,20 @@ def build_variable(eds, section, node_id, index, subindex=0):
251275

252276
if eds.has_option(section, "LowLimit"):
253277
try:
254-
var.min = int(eds.get(section, "LowLimit"), 0)
278+
min_string = eds.get(section, "LowLimit")
279+
if var.data_type in objectdictionary.SIGNED_TYPES:
280+
var.min = _signed_int_from_hex(min_string, _calc_bit_length(var.data_type))
281+
else:
282+
var.min = int(min_string, 0)
255283
except ValueError:
256284
pass
257285
if eds.has_option(section, "HighLimit"):
258286
try:
259-
var.max = int(eds.get(section, "HighLimit"), 0)
287+
max_string = eds.get(section, "HighLimit")
288+
if var.data_type in objectdictionary.SIGNED_TYPES:
289+
var.max = _signed_int_from_hex(max_string, _calc_bit_length(var.data_type))
290+
else:
291+
var.max = int(max_string, 0)
260292
except ValueError:
261293
pass
262294
if eds.has_option(section, "DefaultValue"):

test/sample.eds

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,3 +902,39 @@ DataType=0x0008
902902
AccessType=ro
903903
DefaultValue=0
904904
PDOMapping=1
905+
906+
[3020]
907+
ParameterName=INTEGER8 only positive values
908+
ObjectType=0x7
909+
DataType=0x02
910+
AccessType=rw
911+
HighLimit=0x7F
912+
LowLimit=0x00
913+
PDOMapping=0
914+
915+
[3021]
916+
ParameterName=UNSIGNED8 value range +2 to +10
917+
ObjectType=0x7
918+
DataType=0x05
919+
AccessType=rw
920+
HighLimit=0x0A
921+
LowLimit=0x02
922+
PDOMapping=0
923+
924+
[3030]
925+
ParameterName=INTEGER32 only negative values
926+
ObjectType=0x7
927+
DataType=0x04
928+
AccessType=rw
929+
HighLimit=0x00000000
930+
LowLimit=0xFFFFFFFF
931+
PDOMapping=0
932+
933+
[3040]
934+
ParameterName=INTEGER64 value range -10 to +10
935+
ObjectType=0x7
936+
DataType=0x15
937+
AccessType=rw
938+
HighLimit=0x000000000000000A
939+
LowLimit=0x8000000000000009
940+
PDOMapping=0

test/test_eds.py

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
EDS_PATH = os.path.join(os.path.dirname(__file__), 'sample.eds')
66

7+
78
class TestEDS(unittest.TestCase):
89

910
def setUp(self):
@@ -47,6 +48,20 @@ def test_record(self):
4748
self.assertEqual(var.data_type, canopen.objectdictionary.UNSIGNED32)
4849
self.assertEqual(var.access_type, 'ro')
4950

51+
def test_record_with_limits(self):
52+
int8 = self.od[0x3020]
53+
self.assertEqual(int8.min, 0)
54+
self.assertEqual(int8.max, 127)
55+
uint8 = self.od[0x3021]
56+
self.assertEqual(uint8.min, 2)
57+
self.assertEqual(uint8.max, 10)
58+
int32 = self.od[0x3030]
59+
self.assertEqual(int32.min, -2147483648)
60+
self.assertEqual(int32.max, 0)
61+
int64 = self.od[0x3040]
62+
self.assertEqual(int64.min, -10)
63+
self.assertEqual(int64.max, +10)
64+
5065
def test_array_compact_subobj(self):
5166
array = self.od[0x1003]
5267
self.assertIsInstance(array, canopen.objectdictionary.Array)
@@ -98,73 +113,76 @@ def test_dummy_variable_undefined(self):
98113

99114
def test_comments(self):
100115
self.assertEqual(self.od.comments,
101-
"""
116+
"""
102117
|-------------|
103118
| Don't panic |
104119
|-------------|
105-
""".strip()
106-
)
107-
120+
""".strip())
108121

109122
def test_export_eds(self):
110123
import tempfile
111124
for doctype in {"eds", "dcf"}:
112-
with tempfile.NamedTemporaryFile(suffix="."+doctype, mode="w+") as tempeds:
125+
with tempfile.NamedTemporaryFile(suffix="." + doctype, mode="w+") as tempeds:
113126
print("exporting %s to " % doctype + tempeds.name)
114127
canopen.export_od(self.od, tempeds, doc_type=doctype)
115128
tempeds.flush()
116129
exported_od = canopen.import_od(tempeds.name)
117130

118131
for index in exported_od:
119132
self.assertIn(exported_od[index].name, self.od)
120-
self.assertIn(index , self.od)
133+
self.assertIn(index, self.od)
121134

122135
for index in self.od:
123136
if index < 0x0008:
124137
# ignore dummies
125138
continue
126139
self.assertIn(self.od[index].name, exported_od)
127-
self.assertIn(index , exported_od)
140+
self.assertIn(index, exported_od)
128141

129-
actual_object = exported_od[index]
130-
expected_object = self.od[index]
142+
actual_object = exported_od[index]
143+
expected_object = self.od[index]
131144
self.assertEqual(type(actual_object), type(expected_object))
132145
self.assertEqual(actual_object.name, expected_object.name)
133146

134147
if type(actual_object) is canopen.objectdictionary.Variable:
135148
expected_vars = [expected_object]
136-
actual_vars = [actual_object ]
137-
else :
149+
actual_vars = [actual_object]
150+
else:
138151
expected_vars = [expected_object[idx] for idx in expected_object]
139-
actual_vars = [actual_object [idx] for idx in actual_object]
152+
actual_vars = [actual_object[idx] for idx in actual_object]
140153

141154
for prop in [
142-
"allowed_baudrates",
143-
"vendor_name",
144-
"vendor_number",
145-
"product_name",
146-
"product_number",
147-
"revision_number",
148-
"order_code",
149-
"simple_boot_up_master",
150-
"simple_boot_up_slave",
151-
"granularity",
152-
"dynamic_channels_supported",
153-
"group_messaging",
154-
"nr_of_RXPDO",
155-
"nr_of_TXPDO",
156-
"LSS_supported",
155+
"allowed_baudrates",
156+
"vendor_name",
157+
"vendor_number",
158+
"product_name",
159+
"product_number",
160+
"revision_number",
161+
"order_code",
162+
"simple_boot_up_master",
163+
"simple_boot_up_slave",
164+
"granularity",
165+
"dynamic_channels_supported",
166+
"group_messaging",
167+
"nr_of_RXPDO",
168+
"nr_of_TXPDO",
169+
"LSS_supported",
157170
]:
158-
self.assertEqual(getattr(self.od.device_information, prop), getattr(exported_od.device_information, prop), f"prop {prop!r} mismatch on DeviceInfo")
159-
160-
161-
for evar,avar in zip(expected_vars,actual_vars):
162-
self. assertEqual(getattr(avar, "data_type" , None) , getattr(evar,"data_type" ,None) , " mismatch on %04X:%X"%(evar.index, evar.subindex))
163-
self. assertEqual(getattr(avar, "default_raw", None) , getattr(evar,"default_raw",None) , " mismatch on %04X:%X"%(evar.index, evar.subindex))
164-
self. assertEqual(getattr(avar, "min" , None) , getattr(evar,"min" ,None) , " mismatch on %04X:%X"%(evar.index, evar.subindex))
165-
self. assertEqual(getattr(avar, "max" , None) , getattr(evar,"max" ,None) , " mismatch on %04X:%X"%(evar.index, evar.subindex))
171+
self.assertEqual(getattr(self.od.device_information, prop),
172+
getattr(exported_od.device_information, prop),
173+
f"prop {prop!r} mismatch on DeviceInfo")
174+
175+
for evar, avar in zip(expected_vars, actual_vars):
176+
self.assertEqual(getattr(avar, "data_type", None), getattr(evar, "data_type", None),
177+
" mismatch on %04X:%X" % (evar.index, evar.subindex))
178+
self.assertEqual(getattr(avar, "default_raw", None), getattr(evar, "default_raw", None),
179+
" mismatch on %04X:%X" % (evar.index, evar.subindex))
180+
self.assertEqual(getattr(avar, "min", None), getattr(evar, "min", None),
181+
" mismatch on %04X:%X" % (evar.index, evar.subindex))
182+
self.assertEqual(getattr(avar, "max", None), getattr(evar, "max", None),
183+
" mismatch on %04X:%X" % (evar.index, evar.subindex))
166184
if doctype == "dcf":
167-
self.assertEqual(getattr(avar, "value" , None) , getattr(evar,"value" ,None) , " mismatch on %04X:%X"%(evar.index, evar.subindex))
185+
self.assertEqual(getattr(avar, "value", None), getattr(evar, "value", None),
186+
" mismatch on %04X:%X" % (evar.index, evar.subindex))
168187

169188
self.assertEqual(self.od.comments, exported_od.comments)
170-

0 commit comments

Comments
 (0)